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 psppire_data_editor_set_property (GObject *object,
224 PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
228 case PROP_SPLIT_WINDOW:
229 psppire_data_editor_split_window (de, g_value_get_boolean (value));
231 case PROP_DATA_STORE:
234 g_signal_handlers_disconnect_by_func (de->data_store,
235 G_CALLBACK (refresh_entry),
237 g_object_unref (de->data_store);
240 de->data_store = g_value_get_pointer (value);
241 g_object_ref (de->data_store);
242 g_print ("NEW STORE\n");
244 g_object_set (de->data_sheet, "data-model", de->data_store, NULL);
245 psppire_data_editor_refresh_model (de);
247 g_signal_connect_swapped (de->data_store, "case-changed",
248 G_CALLBACK (refresh_entry), de);
251 case PROP_DICTIONARY:
253 g_object_unref (de->dict);
254 de->dict = g_value_get_pointer (value);
255 g_object_ref (de->dict);
257 g_object_set (de->data_sheet, "hmodel", de->dict, NULL);
258 g_object_set (de->var_sheet, "data-model", de->dict, NULL);
261 case PROP_VALUE_LABELS:
263 case PROP_UI_MANAGER:
265 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
271 psppire_data_editor_get_property (GObject *object,
276 PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
280 case PROP_SPLIT_WINDOW:
281 g_value_set_boolean (value, de->split);
283 case PROP_DATA_STORE:
284 g_value_set_pointer (value, de->data_store);
286 case PROP_DICTIONARY:
287 g_value_set_pointer (value, de->dict);
289 case PROP_VALUE_LABELS:
291 case PROP_UI_MANAGER:
292 g_value_set_object (value, psppire_data_editor_get_ui_manager (de));
295 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
301 psppire_data_editor_switch_page (GtkNotebook *notebook,
305 GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, w, page_num);
306 psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (notebook));
310 psppire_data_editor_set_focus_child (GtkContainer *container,
313 GTK_CONTAINER_CLASS (parent_class)->set_focus_child (container, widget);
314 psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (container));
318 psppire_data_editor_class_init (PsppireDataEditorClass *klass)
320 GParamSpec *data_store_spec ;
321 GParamSpec *dict_spec ;
322 GParamSpec *value_labels_spec;
323 GParamSpec *split_window_spec;
324 GParamSpec *ui_manager_spec;
325 GObjectClass *object_class = G_OBJECT_CLASS (klass);
326 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
327 GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
329 parent_class = g_type_class_peek_parent (klass);
331 object_class->dispose = psppire_data_editor_dispose;
332 object_class->set_property = psppire_data_editor_set_property;
333 object_class->get_property = psppire_data_editor_get_property;
335 container_class->set_focus_child = psppire_data_editor_set_focus_child;
337 notebook_class->switch_page = psppire_data_editor_switch_page;
340 g_param_spec_pointer ("data-store",
342 "A pointer to the data store associated with this editor",
343 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
345 g_object_class_install_property (object_class,
350 g_param_spec_pointer ("dictionary",
352 "A pointer to the dictionary associated with this editor",
353 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
355 g_object_class_install_property (object_class,
360 g_param_spec_boolean ("value-labels",
362 "Whether or not the data sheet should display labels instead of values",
364 G_PARAM_WRITABLE | G_PARAM_READABLE);
366 g_object_class_install_property (object_class,
372 g_param_spec_boolean ("split",
374 "True iff the data sheet is split",
376 G_PARAM_READABLE | G_PARAM_WRITABLE);
378 g_object_class_install_property (object_class,
383 g_param_spec_object ("ui-manager",
385 "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.",
388 g_object_class_install_property (object_class,
395 on_var_sheet_var_double_clicked (PsppireVarSheet *var_sheet, gint dict_index,
396 PsppireDataEditor *de)
398 gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
399 PSPPIRE_DATA_EDITOR_DATA_VIEW);
401 jmd_sheet_scroll_to (JMD_SHEET (de->data_sheet), dict_index, -1);
405 on_data_sheet_var_double_clicked (JmdSheet *data_sheet, gint dict_index,
406 PsppireDataEditor *de)
408 gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
409 PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
411 jmd_sheet_scroll_to (JMD_SHEET (de->var_sheet), -1, dict_index);
415 /* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
416 active cell or cells. */
418 refresh_entry (PsppireDataEditor *de)
420 g_print ("%s\n", __FUNCTION__);
424 on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
430 disconnect_data_sheets (PsppireDataEditor *de)
434 /* Called when the active cell or the selection in the data sheet changes */
436 on_data_selection_change (PsppireDataEditor *de, JmdRange *sel)
438 gchar *ref_cell_text = NULL;
440 gint n_cases = abs (sel->end_y - sel->start_y) + 1;
441 gint n_vars = abs (sel->end_x - sel->start_x) + 1;
443 if (n_cases == 1 && n_vars == 1)
445 /* A single cell is selected */
446 const struct variable *var = psppire_dict_get_variable (de->dict, sel->start_x);
448 ref_cell_text = g_strdup_printf (_("%d : %s"),
449 sel->start_y + 1, var_get_name (var));
455 /* The glib string library does not understand the ' printf modifier
456 on all platforms, but the "struct string" library does (because
457 Gnulib fixes that problem), so use the latter. */
459 ds_put_format (&s, ngettext ("%'d case", "%'d cases", n_cases),
461 ds_put_byte (&s, ' ');
462 ds_put_unichar (&s, 0xd7); /* U+00D7 MULTIPLICATION SIGN */
463 ds_put_byte (&s, ' ');
464 ds_put_format (&s, ngettext ("%'d variable", "%'d variables",
467 ref_cell_text = ds_steal_cstr (&s);
470 gtk_label_set_label (GTK_LABEL (de->cell_ref_label),
471 ref_cell_text ? ref_cell_text : "");
473 g_free (ref_cell_text);
477 static void set_font_recursively (GtkWidget *w, gpointer data);
480 psppire_data_editor_init (PsppireDataEditor *de)
483 gchar *fontname = NULL;
486 de->ui_manager = NULL;
487 de->old_vbox_widget = NULL;
489 g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
491 de->cell_ref_label = gtk_label_new ("");
492 gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
493 gtk_widget_set_valign (de->cell_ref_label, GTK_ALIGN_CENTER);
495 de->datum_entry = psppire_value_entry_new ();
496 g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
497 "activate", G_CALLBACK (on_datum_entry_activate), de);
499 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
500 gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
501 gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
504 de->data_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
505 GtkWidget *data_button = jmd_sheet_get_button (JMD_SHEET (de->data_sheet));
506 gtk_button_set_label (GTK_BUTTON (data_button), _("Case"));
507 de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
508 gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
509 gtk_box_pack_start (GTK_BOX (de->vbox), de->data_sheet, TRUE, TRUE, 0);
512 g_signal_connect_swapped (de->data_sheet, "selection-changed",
513 G_CALLBACK (on_data_selection_change), de);
515 gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
516 gtk_label_new_with_mnemonic (_("Data View")));
518 gtk_widget_show_all (de->vbox);
520 de->var_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
522 PsppireVarSheetHeader *vsh = g_object_new (PSPPIRE_TYPE_VAR_SHEET_HEADER, NULL);
524 g_object_set (de->var_sheet,
526 "select-renderer-func", select_renderer_func,
530 GtkWidget *var_button = jmd_sheet_get_button (JMD_SHEET (de->var_sheet));
531 gtk_button_set_label (GTK_BUTTON (var_button), _("Variable"));
533 gtk_notebook_append_page (GTK_NOTEBOOK (de), de->var_sheet,
534 gtk_label_new_with_mnemonic (_("Variable View")));
536 gtk_widget_show_all (de->var_sheet);
538 g_signal_connect (de->var_sheet, "row-header-double-clicked",
539 G_CALLBACK (on_var_sheet_var_double_clicked), de);
541 g_signal_connect (de->data_sheet, "column-header-double-clicked",
542 G_CALLBACK (on_data_sheet_var_double_clicked), de);
544 g_object_set (de, "can-focus", FALSE, NULL);
546 if (psppire_conf_get_string (psppire_conf_new (),
547 "Data Editor", "font",
550 de->font = pango_font_description_from_string (fontname);
552 set_font_recursively (GTK_WIDGET (de), de->font);
555 psppire_data_editor_update_ui_manager (de);
559 psppire_data_editor_new (PsppireDict *dict,
560 PsppireDataStore *data_store)
562 return g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
564 "data-store", data_store,
568 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
569 sheet(s) and variable sheet. */
571 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
573 GtkTreeViewGridLines grid;
576 ? GTK_TREE_VIEW_GRID_LINES_BOTH
577 : GTK_TREE_VIEW_GRID_LINES_NONE);
579 pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet), grid);
584 set_font_recursively (GtkWidget *w, gpointer data)
586 PangoFontDescription *font_desc = data;
588 gtk_widget_override_font (w, font_desc);
590 if ( GTK_IS_CONTAINER (w))
591 gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
594 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
596 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
599 set_font_recursively (GTK_WIDGET (de), font_desc);
602 pango_font_description_free (de->font);
603 de->font = pango_font_description_copy (font_desc);
604 font_name = pango_font_description_to_string (de->font);
606 psppire_conf_set_string (psppire_conf_new (),
607 "Data Editor", "font",
612 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
613 If SPLIT is FALSE, un-splits it into a single pane. */
615 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
617 if (split == de->split)
620 disconnect_data_sheets (de);
622 psppire_data_editor_refresh_model (de);
624 gtk_widget_show_all (de->vbox);
627 set_font_recursively (GTK_WIDGET (de), de->font);
630 g_object_notify (G_OBJECT (de), "split");
631 psppire_data_editor_update_ui_manager (de);
634 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
635 visible and selected in the active view in DE. */
637 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
642 /* Returns the UI manager that should be merged into DE's toplevel widget's UI
643 manager to display menu items and toolbar items specific to DE's current
646 DE's toplevel widget can watch for changes by connecting to DE's
647 notify::ui-manager signal. */
649 psppire_data_editor_get_ui_manager (PsppireDataEditor *de)
651 psppire_data_editor_update_ui_manager (de);
652 return de->ui_manager;
656 psppire_data_editor_update_ui_manager (PsppireDataEditor *de)
658 GtkUIManager *ui_manager;
662 switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
664 case PSPPIRE_DATA_EDITOR_DATA_VIEW:
667 case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
671 /* This happens transiently in psppire_data_editor_init(). */
675 if (ui_manager != de->ui_manager)
678 g_object_unref (de->ui_manager);
680 g_object_ref (ui_manager);
681 de->ui_manager = ui_manager;
683 g_object_notify (G_OBJECT (de), "ui-manager");