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,
401 GtkWidget *button = jmd_sheet_get_button (JMD_SHEET (de->data_sheet));
402 gtk_button_set_label (GTK_BUTTON (button), _("Case"));
403 de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
404 gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
405 gtk_box_pack_start (GTK_BOX (de->vbox), de->data_sheet, TRUE, TRUE, 0);
407 gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
408 gtk_label_new_with_mnemonic (_("Data View")));
410 gtk_widget_show_all (de->vbox);
412 de->var_sheet = GTK_WIDGET (psppire_var_sheet_new ());
414 pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet),
415 GTK_TREE_VIEW_GRID_LINES_BOTH);
416 var_sheet_scroller = gtk_scrolled_window_new (NULL, NULL);
417 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (var_sheet_scroller),
418 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
419 gtk_container_add (GTK_CONTAINER (var_sheet_scroller), de->var_sheet);
420 gtk_widget_show_all (var_sheet_scroller);
422 gtk_notebook_append_page (GTK_NOTEBOOK (de), var_sheet_scroller,
423 gtk_label_new_with_mnemonic (_("Variable View")));
425 g_signal_connect (de->var_sheet, "var-double-clicked",
426 G_CALLBACK (on_var_sheet_var_double_clicked), de);
428 g_object_set (de, "can-focus", FALSE, NULL);
430 if (psppire_conf_get_string (psppire_conf_new (),
431 "Data Editor", "font",
434 de->font = pango_font_description_from_string (fontname);
436 set_font_recursively (GTK_WIDGET (de), de->font);
439 psppire_data_editor_update_ui_manager (de);
443 psppire_data_editor_new (PsppireDict *dict,
444 PsppireDataStore *data_store)
446 return g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
448 "data-store", data_store,
452 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
453 sheet(s) and variable sheet. */
455 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
457 GtkTreeViewGridLines grid;
461 ? GTK_TREE_VIEW_GRID_LINES_BOTH
462 : GTK_TREE_VIEW_GRID_LINES_NONE);
464 pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet), grid);
469 set_font_recursively (GtkWidget *w, gpointer data)
471 PangoFontDescription *font_desc = data;
473 gtk_widget_override_font (w, font_desc);
475 if ( GTK_IS_CONTAINER (w))
476 gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
479 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
481 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
484 set_font_recursively (GTK_WIDGET (de), font_desc);
487 pango_font_description_free (de->font);
488 de->font = pango_font_description_copy (font_desc);
489 font_name = pango_font_description_to_string (de->font);
491 psppire_conf_set_string (psppire_conf_new (),
492 "Data Editor", "font",
497 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
498 If SPLIT is FALSE, un-splits it into a single pane. */
500 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
502 GtkTreeViewGridLines grid_lines;
505 if (split == de->split)
510 disconnect_data_sheets (de);
512 psppire_data_editor_refresh_model (de);
514 gtk_widget_show_all (de->vbox);
517 set_font_recursively (GTK_WIDGET (de), de->font);
520 g_object_notify (G_OBJECT (de), "split");
521 psppire_data_editor_update_ui_manager (de);
524 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
525 visible and selected in the active view in DE. */
527 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
530 switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
533 case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
534 psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
541 /* Returns the UI manager that should be merged into DE's toplevel widget's UI
542 manager to display menu items and toolbar items specific to DE's current
545 DE's toplevel widget can watch for changes by connecting to DE's
546 notify::ui-manager signal. */
548 psppire_data_editor_get_ui_manager (PsppireDataEditor *de)
550 psppire_data_editor_update_ui_manager (de);
551 return de->ui_manager;
555 psppire_data_editor_update_ui_manager (PsppireDataEditor *de)
557 GtkUIManager *ui_manager;
561 switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
563 case PSPPIRE_DATA_EDITOR_DATA_VIEW:
566 case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
567 ui_manager = psppire_var_sheet_get_ui_manager (
568 PSPPIRE_VAR_SHEET (de->var_sheet));
572 /* This happens transiently in psppire_data_editor_init(). */
576 if (ui_manager != de->ui_manager)
579 g_object_unref (de->ui_manager);
581 g_object_ref (ui_manager);
582 de->ui_manager = ui_manager;
584 g_object_notify (G_OBJECT (de), "ui-manager");