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 void psppire_data_editor_class_init (PsppireDataEditorClass *klass);
43 static void psppire_data_editor_init (PsppireDataEditor *de);
45 static void disconnect_data_sheets (PsppireDataEditor *);
46 static void refresh_entry (PsppireDataEditor *);
47 static void psppire_data_editor_update_ui_manager (PsppireDataEditor *);
50 psppire_data_editor_get_type (void)
52 static GType de_type = 0;
56 static const GTypeInfo de_info =
58 sizeof (PsppireDataEditorClass),
60 NULL, /* base_finalize */
61 (GClassInitFunc) psppire_data_editor_class_init,
62 NULL, /* class_finalize */
63 NULL, /* class_data */
64 sizeof (PsppireDataEditor),
66 (GInstanceInitFunc) psppire_data_editor_init,
69 de_type = g_type_register_static (GTK_TYPE_NOTEBOOK, "PsppireDataEditor",
76 static GObjectClass * parent_class = NULL;
79 psppire_data_editor_dispose (GObject *obj)
81 PsppireDataEditor *de = (PsppireDataEditor *) obj;
83 disconnect_data_sheets (de);
87 g_object_unref (de->data_store);
88 de->data_store = NULL;
93 g_object_unref (de->dict);
99 pango_font_description_free (de->font);
105 g_object_unref (de->ui_manager);
106 de->ui_manager = NULL;
109 /* Chain up to the parent class */
110 G_OBJECT_CLASS (parent_class)->dispose (obj);
124 psppire_data_editor_refresh_model (PsppireDataEditor *de)
129 psppire_data_editor_set_property (GObject *object,
134 PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
138 case PROP_SPLIT_WINDOW:
139 psppire_data_editor_split_window (de, g_value_get_boolean (value));
141 case PROP_DATA_STORE:
144 g_signal_handlers_disconnect_by_func (de->data_store,
145 G_CALLBACK (refresh_entry),
147 g_object_unref (de->data_store);
150 de->data_store = g_value_get_pointer (value);
151 g_object_ref (de->data_store);
152 g_print ("NEW STORE\n");
154 g_object_set (de->data_sheet, "data-model", de->data_store, NULL);
155 psppire_data_editor_refresh_model (de);
157 g_signal_connect_swapped (de->data_store, "case-changed",
158 G_CALLBACK (refresh_entry), de);
161 case PROP_DICTIONARY:
163 g_object_unref (de->dict);
164 de->dict = g_value_get_pointer (value);
165 g_object_ref (de->dict);
167 g_object_set (de->data_sheet, "hmodel", de->dict, NULL);
168 g_object_set (de->var_sheet, "data-model", de->dict, NULL);
171 case PROP_VALUE_LABELS:
173 case PROP_UI_MANAGER:
175 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
181 psppire_data_editor_get_property (GObject *object,
186 PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
190 case PROP_SPLIT_WINDOW:
191 g_value_set_boolean (value, de->split);
193 case PROP_DATA_STORE:
194 g_value_set_pointer (value, de->data_store);
196 case PROP_DICTIONARY:
197 g_value_set_pointer (value, de->dict);
199 case PROP_VALUE_LABELS:
201 case PROP_UI_MANAGER:
202 g_value_set_object (value, psppire_data_editor_get_ui_manager (de));
205 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
211 psppire_data_editor_switch_page (GtkNotebook *notebook,
215 GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, w, page_num);
216 psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (notebook));
220 psppire_data_editor_set_focus_child (GtkContainer *container,
223 GTK_CONTAINER_CLASS (parent_class)->set_focus_child (container, widget);
224 psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (container));
228 psppire_data_editor_class_init (PsppireDataEditorClass *klass)
230 GParamSpec *data_store_spec ;
231 GParamSpec *dict_spec ;
232 GParamSpec *value_labels_spec;
233 GParamSpec *split_window_spec;
234 GParamSpec *ui_manager_spec;
235 GObjectClass *object_class = G_OBJECT_CLASS (klass);
236 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
237 GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
239 parent_class = g_type_class_peek_parent (klass);
241 object_class->dispose = psppire_data_editor_dispose;
242 object_class->set_property = psppire_data_editor_set_property;
243 object_class->get_property = psppire_data_editor_get_property;
245 container_class->set_focus_child = psppire_data_editor_set_focus_child;
247 notebook_class->switch_page = psppire_data_editor_switch_page;
250 g_param_spec_pointer ("data-store",
252 "A pointer to the data store associated with this editor",
253 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
255 g_object_class_install_property (object_class,
260 g_param_spec_pointer ("dictionary",
262 "A pointer to the dictionary associated with this editor",
263 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
265 g_object_class_install_property (object_class,
270 g_param_spec_boolean ("value-labels",
272 "Whether or not the data sheet should display labels instead of values",
274 G_PARAM_WRITABLE | G_PARAM_READABLE);
276 g_object_class_install_property (object_class,
282 g_param_spec_boolean ("split",
284 "True iff the data sheet is split",
286 G_PARAM_READABLE | G_PARAM_WRITABLE);
288 g_object_class_install_property (object_class,
293 g_param_spec_object ("ui-manager",
295 "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.",
298 g_object_class_install_property (object_class,
305 on_var_sheet_var_double_clicked (PsppireVarSheet *var_sheet, gint dict_index,
306 PsppireDataEditor *de)
308 gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
309 PSPPIRE_DATA_EDITOR_DATA_VIEW);
311 jmd_sheet_scroll_to (de->data_sheet, dict_index, -1);
315 on_data_sheet_var_double_clicked (JmdSheet *data_sheet, gint dict_index,
316 PsppireDataEditor *de)
318 gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
319 PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
321 jmd_sheet_scroll_to (de->var_sheet, -1, dict_index);
325 /* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
326 active cell or cells. */
328 refresh_entry (PsppireDataEditor *de)
330 g_print ("%s\n", __FUNCTION__);
334 on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
340 disconnect_data_sheets (PsppireDataEditor *de)
344 /* Called when the active cell or the selection in the data sheet changes */
346 on_data_selection_change (PsppireDataEditor *de, JmdRange *sel)
348 gchar *ref_cell_text = NULL;
350 gint n_cases = abs (sel->end_y - sel->start_y) + 1;
351 gint n_vars = abs (sel->end_x - sel->start_x) + 1;
353 if (n_cases == 1 && n_vars == 1)
355 /* A single cell is selected */
356 const struct variable *var = psppire_dict_get_variable (de->dict, sel->start_x);
358 ref_cell_text = g_strdup_printf (_("%d : %s"),
359 sel->start_y + 1, var_get_name (var));
365 /* The glib string library does not understand the ' printf modifier
366 on all platforms, but the "struct string" library does (because
367 Gnulib fixes that problem), so use the latter. */
369 ds_put_format (&s, ngettext ("%'d case", "%'d cases", n_cases),
371 ds_put_byte (&s, ' ');
372 ds_put_unichar (&s, 0xd7); /* U+00D7 MULTIPLICATION SIGN */
373 ds_put_byte (&s, ' ');
374 ds_put_format (&s, ngettext ("%'d variable", "%'d variables",
377 ref_cell_text = ds_steal_cstr (&s);
380 gtk_label_set_label (GTK_LABEL (de->cell_ref_label),
381 ref_cell_text ? ref_cell_text : "");
383 g_free (ref_cell_text);
387 static void set_font_recursively (GtkWidget *w, gpointer data);
390 psppire_data_editor_init (PsppireDataEditor *de)
393 gchar *fontname = NULL;
396 de->ui_manager = NULL;
397 de->old_vbox_widget = NULL;
399 g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
401 de->cell_ref_label = gtk_label_new ("");
402 gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
403 gtk_widget_set_valign (de->cell_ref_label, GTK_ALIGN_CENTER);
405 de->datum_entry = psppire_value_entry_new ();
406 g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
407 "activate", G_CALLBACK (on_datum_entry_activate), de);
409 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
410 gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
411 gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
414 de->data_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
415 GtkWidget *data_button = jmd_sheet_get_button (JMD_SHEET (de->data_sheet));
416 gtk_button_set_label (GTK_BUTTON (data_button), _("Case"));
417 de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
418 gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
419 gtk_box_pack_start (GTK_BOX (de->vbox), de->data_sheet, TRUE, TRUE, 0);
422 g_signal_connect_swapped (de->data_sheet, "selection-changed",
423 G_CALLBACK (on_data_selection_change), de);
425 gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
426 gtk_label_new_with_mnemonic (_("Data View")));
428 gtk_widget_show_all (de->vbox);
430 de->var_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
432 PsppireVarSheetHeader *vsh = g_object_new (PSPPIRE_TYPE_VAR_SHEET_HEADER, NULL);
434 g_object_set (de->var_sheet, "hmodel", vsh, NULL);
437 GtkWidget *var_button = jmd_sheet_get_button (JMD_SHEET (de->var_sheet));
438 gtk_button_set_label (GTK_BUTTON (var_button), _("Variable"));
440 gtk_notebook_append_page (GTK_NOTEBOOK (de), de->var_sheet,
441 gtk_label_new_with_mnemonic (_("Variable View")));
443 gtk_widget_show_all (de->var_sheet);
445 g_signal_connect (de->var_sheet, "row-header-double-clicked",
446 G_CALLBACK (on_var_sheet_var_double_clicked), de);
448 g_signal_connect (de->data_sheet, "column-header-double-clicked",
449 G_CALLBACK (on_data_sheet_var_double_clicked), de);
451 g_object_set (de, "can-focus", FALSE, NULL);
453 if (psppire_conf_get_string (psppire_conf_new (),
454 "Data Editor", "font",
457 de->font = pango_font_description_from_string (fontname);
459 set_font_recursively (GTK_WIDGET (de), de->font);
462 psppire_data_editor_update_ui_manager (de);
466 psppire_data_editor_new (PsppireDict *dict,
467 PsppireDataStore *data_store)
469 return g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
471 "data-store", data_store,
475 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
476 sheet(s) and variable sheet. */
478 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
480 GtkTreeViewGridLines grid;
483 ? GTK_TREE_VIEW_GRID_LINES_BOTH
484 : GTK_TREE_VIEW_GRID_LINES_NONE);
486 pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet), grid);
491 set_font_recursively (GtkWidget *w, gpointer data)
493 PangoFontDescription *font_desc = data;
495 gtk_widget_override_font (w, font_desc);
497 if ( GTK_IS_CONTAINER (w))
498 gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
501 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
503 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
506 set_font_recursively (GTK_WIDGET (de), font_desc);
509 pango_font_description_free (de->font);
510 de->font = pango_font_description_copy (font_desc);
511 font_name = pango_font_description_to_string (de->font);
513 psppire_conf_set_string (psppire_conf_new (),
514 "Data Editor", "font",
519 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
520 If SPLIT is FALSE, un-splits it into a single pane. */
522 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
524 if (split == de->split)
527 disconnect_data_sheets (de);
529 psppire_data_editor_refresh_model (de);
531 gtk_widget_show_all (de->vbox);
534 set_font_recursively (GTK_WIDGET (de), de->font);
537 g_object_notify (G_OBJECT (de), "split");
538 psppire_data_editor_update_ui_manager (de);
541 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
542 visible and selected in the active view in DE. */
544 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
549 /* Returns the UI manager that should be merged into DE's toplevel widget's UI
550 manager to display menu items and toolbar items specific to DE's current
553 DE's toplevel widget can watch for changes by connecting to DE's
554 notify::ui-manager signal. */
556 psppire_data_editor_get_ui_manager (PsppireDataEditor *de)
558 psppire_data_editor_update_ui_manager (de);
559 return de->ui_manager;
563 psppire_data_editor_update_ui_manager (PsppireDataEditor *de)
565 GtkUIManager *ui_manager;
569 switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
571 case PSPPIRE_DATA_EDITOR_DATA_VIEW:
574 case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
578 /* This happens transiently in psppire_data_editor_init(). */
582 if (ui_manager != de->ui_manager)
585 g_object_unref (de->ui_manager);
587 g_object_ref (ui_manager);
588 de->ui_manager = ui_manager;
590 g_object_notify (G_OBJECT (de), "ui-manager");