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"
35 #include "ui/gui/efficient-sheet/jmd-sheet.h"
38 #define _(msgid) gettext (msgid)
41 static void psppire_data_editor_class_init (PsppireDataEditorClass *klass);
42 static void psppire_data_editor_init (PsppireDataEditor *de);
44 static void disconnect_data_sheets (PsppireDataEditor *);
45 static void refresh_entry (PsppireDataEditor *);
46 static void psppire_data_editor_update_ui_manager (PsppireDataEditor *);
49 psppire_data_editor_get_type (void)
51 static GType de_type = 0;
55 static const GTypeInfo de_info =
57 sizeof (PsppireDataEditorClass),
59 NULL, /* base_finalize */
60 (GClassInitFunc) psppire_data_editor_class_init,
61 NULL, /* class_finalize */
62 NULL, /* class_data */
63 sizeof (PsppireDataEditor),
65 (GInstanceInitFunc) psppire_data_editor_init,
68 de_type = g_type_register_static (GTK_TYPE_NOTEBOOK, "PsppireDataEditor",
75 static GObjectClass * parent_class = NULL;
78 psppire_data_editor_dispose (GObject *obj)
80 PsppireDataEditor *de = (PsppireDataEditor *) obj;
82 disconnect_data_sheets (de);
86 g_object_unref (de->data_store);
87 de->data_store = NULL;
92 g_object_unref (de->dict);
98 pango_font_description_free (de->font);
104 g_object_unref (de->ui_manager);
105 de->ui_manager = NULL;
108 /* Chain up to the parent class */
109 G_OBJECT_CLASS (parent_class)->dispose (obj);
123 psppire_data_editor_refresh_model (PsppireDataEditor *de)
125 PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (de->var_sheet);
128 psppire_var_sheet_set_dictionary (var_sheet, de->dict);
132 psppire_data_editor_set_property (GObject *object,
137 PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
143 case PROP_SPLIT_WINDOW:
144 psppire_data_editor_split_window (de, g_value_get_boolean (value));
146 case PROP_DATA_STORE:
149 g_signal_handlers_disconnect_by_func (de->data_store,
150 G_CALLBACK (refresh_entry),
152 g_object_unref (de->data_store);
155 de->data_store = g_value_get_pointer (value);
156 g_object_ref (de->data_store);
157 g_print ("NEW STORE\n");
159 g_object_set (de->data_sheet, "data-model", de->data_store, NULL);
160 psppire_data_editor_refresh_model (de);
162 g_signal_connect_swapped (de->data_store, "case-changed",
163 G_CALLBACK (refresh_entry), de);
166 case PROP_DICTIONARY:
168 g_object_unref (de->dict);
169 de->dict = g_value_get_pointer (value);
170 g_object_ref (de->dict);
172 g_object_set (de->data_sheet, "hmodel", de->dict, NULL);
174 psppire_var_sheet_set_dictionary (PSPPIRE_VAR_SHEET (de->var_sheet),
177 case PROP_VALUE_LABELS:
179 case PROP_UI_MANAGER:
181 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
187 psppire_data_editor_get_property (GObject *object,
192 PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
196 case PROP_SPLIT_WINDOW:
197 g_value_set_boolean (value, de->split);
199 case PROP_DATA_STORE:
200 g_value_set_pointer (value, de->data_store);
202 case PROP_DICTIONARY:
203 g_value_set_pointer (value, de->dict);
205 case PROP_VALUE_LABELS:
207 case PROP_UI_MANAGER:
208 g_value_set_object (value, psppire_data_editor_get_ui_manager (de));
211 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
217 psppire_data_editor_switch_page (GtkNotebook *notebook,
221 GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, w, page_num);
222 psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (notebook));
226 psppire_data_editor_set_focus_child (GtkContainer *container,
229 GTK_CONTAINER_CLASS (parent_class)->set_focus_child (container, widget);
230 psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (container));
234 psppire_data_editor_class_init (PsppireDataEditorClass *klass)
236 GParamSpec *data_store_spec ;
237 GParamSpec *dict_spec ;
238 GParamSpec *value_labels_spec;
239 GParamSpec *split_window_spec;
240 GParamSpec *ui_manager_spec;
241 GObjectClass *object_class = G_OBJECT_CLASS (klass);
242 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
243 GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
245 parent_class = g_type_class_peek_parent (klass);
247 object_class->dispose = psppire_data_editor_dispose;
248 object_class->set_property = psppire_data_editor_set_property;
249 object_class->get_property = psppire_data_editor_get_property;
251 container_class->set_focus_child = psppire_data_editor_set_focus_child;
253 notebook_class->switch_page = psppire_data_editor_switch_page;
256 g_param_spec_pointer ("data-store",
258 "A pointer to the data store associated with this editor",
259 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
261 g_object_class_install_property (object_class,
266 g_param_spec_pointer ("dictionary",
268 "A pointer to the dictionary associated with this editor",
269 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
271 g_object_class_install_property (object_class,
276 g_param_spec_boolean ("value-labels",
278 "Whether or not the data sheet should display labels instead of values",
280 G_PARAM_WRITABLE | G_PARAM_READABLE);
282 g_object_class_install_property (object_class,
288 g_param_spec_boolean ("split",
290 "True iff the data sheet is split",
292 G_PARAM_READABLE | G_PARAM_WRITABLE);
294 g_object_class_install_property (object_class,
299 g_param_spec_object ("ui-manager",
301 "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.",
304 g_object_class_install_property (object_class,
310 on_data_sheet_var_double_clicked (GtkWidget *data_sheet,
312 PsppireDataEditor *de)
314 gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
315 PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
317 psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
324 on_var_sheet_var_double_clicked (PsppireVarSheet *var_sheet, gint dict_index,
325 PsppireDataEditor *de)
328 gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
329 PSPPIRE_DATA_EDITOR_DATA_VIEW);
335 /* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
336 active cell or cells. */
338 refresh_entry (PsppireDataEditor *de)
343 on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
348 on_data_sheet_selection_changed (PsppSheetSelection *selection,
349 PsppireDataEditor *de)
351 /* In a split view, ensure that only a single data sheet has a nonempty
354 && pspp_sheet_selection_count_selected_rows (selection)
355 && pspp_sheet_selection_count_selected_columns (selection))
364 disconnect_data_sheets (PsppireDataEditor *de)
371 static void set_font_recursively (GtkWidget *w, gpointer data);
374 psppire_data_editor_init (PsppireDataEditor *de)
376 GtkWidget *var_sheet_scroller;
378 gchar *fontname = NULL;
381 de->ui_manager = NULL;
382 de->old_vbox_widget = NULL;
384 g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
386 de->cell_ref_label = gtk_label_new ("");
387 gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
388 gtk_widget_set_valign (de->cell_ref_label, GTK_ALIGN_CENTER);
390 de->datum_entry = psppire_value_entry_new ();
391 g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
392 "activate", G_CALLBACK (on_datum_entry_activate), de);
394 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
395 gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
396 gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
399 de->data_sheet = g_object_new (JMD_TYPE_SHEET,
400 "splitter", GTK_TYPE_XPANED,
402 GtkWidget *button = jmd_sheet_get_button (JMD_SHEET (de->data_sheet));
403 gtk_button_set_label (GTK_BUTTON (button), _("Case"));
404 de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
405 gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
406 gtk_box_pack_start (GTK_BOX (de->vbox), de->data_sheet, TRUE, TRUE, 0);
408 gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
409 gtk_label_new_with_mnemonic (_("Data View")));
411 gtk_widget_show_all (de->vbox);
413 de->var_sheet = GTK_WIDGET (psppire_var_sheet_new ());
415 pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet),
416 GTK_TREE_VIEW_GRID_LINES_BOTH);
417 var_sheet_scroller = gtk_scrolled_window_new (NULL, NULL);
418 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (var_sheet_scroller),
419 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
420 gtk_container_add (GTK_CONTAINER (var_sheet_scroller), de->var_sheet);
421 gtk_widget_show_all (var_sheet_scroller);
423 gtk_notebook_append_page (GTK_NOTEBOOK (de), var_sheet_scroller,
424 gtk_label_new_with_mnemonic (_("Variable View")));
426 g_signal_connect (de->var_sheet, "var-double-clicked",
427 G_CALLBACK (on_var_sheet_var_double_clicked), de);
429 g_object_set (de, "can-focus", FALSE, NULL);
431 if (psppire_conf_get_string (psppire_conf_new (),
432 "Data Editor", "font",
435 de->font = pango_font_description_from_string (fontname);
437 set_font_recursively (GTK_WIDGET (de), de->font);
440 psppire_data_editor_update_ui_manager (de);
444 psppire_data_editor_new (PsppireDict *dict,
445 PsppireDataStore *data_store)
447 return g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
449 "data-store", data_store,
453 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
454 sheet(s) and variable sheet. */
456 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
458 GtkTreeViewGridLines grid;
462 ? GTK_TREE_VIEW_GRID_LINES_BOTH
463 : GTK_TREE_VIEW_GRID_LINES_NONE);
465 pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet), grid);
470 set_font_recursively (GtkWidget *w, gpointer data)
472 PangoFontDescription *font_desc = data;
474 gtk_widget_override_font (w, font_desc);
476 if ( GTK_IS_CONTAINER (w))
477 gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
480 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
482 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
485 set_font_recursively (GTK_WIDGET (de), font_desc);
488 pango_font_description_free (de->font);
489 de->font = pango_font_description_copy (font_desc);
490 font_name = pango_font_description_to_string (de->font);
492 psppire_conf_set_string (psppire_conf_new (),
493 "Data Editor", "font",
498 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
499 If SPLIT is FALSE, un-splits it into a single pane. */
501 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
503 GtkTreeViewGridLines grid_lines;
506 if (split == de->split)
511 disconnect_data_sheets (de);
513 psppire_data_editor_refresh_model (de);
515 gtk_widget_show_all (de->vbox);
518 set_font_recursively (GTK_WIDGET (de), de->font);
521 g_object_notify (G_OBJECT (de), "split");
522 psppire_data_editor_update_ui_manager (de);
525 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
526 visible and selected in the active view in DE. */
528 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
531 switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
534 case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
535 psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
542 /* Returns the UI manager that should be merged into DE's toplevel widget's UI
543 manager to display menu items and toolbar items specific to DE's current
546 DE's toplevel widget can watch for changes by connecting to DE's
547 notify::ui-manager signal. */
549 psppire_data_editor_get_ui_manager (PsppireDataEditor *de)
551 psppire_data_editor_update_ui_manager (de);
552 return de->ui_manager;
556 psppire_data_editor_update_ui_manager (PsppireDataEditor *de)
558 GtkUIManager *ui_manager;
562 switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
564 case PSPPIRE_DATA_EDITOR_DATA_VIEW:
567 case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
568 ui_manager = psppire_var_sheet_get_ui_manager (
569 PSPPIRE_VAR_SHEET (de->var_sheet));
573 /* This happens transiently in psppire_data_editor_init(). */
577 if (ui_manager != de->ui_manager)
580 g_object_unref (de->ui_manager);
582 g_object_ref (ui_manager);
583 de->ui_manager = ui_manager;
585 g_object_notify (G_OBJECT (de), "ui-manager");