Merge 'master' into 'gtk3'.
[pspp] / src / ui / gui / psppire-data-editor.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
3
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.
8
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.
13
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/>. */
16
17 #include <config.h>
18
19 #include "ui/gui/psppire-data-editor.h"
20
21 #include <gtk/gtk.h>
22 #include <gtk-contrib/gtkxpaned.h>
23
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-sheet.h"
31 #include "ui/gui/psppire-data-store.h"
32 #include "ui/gui/psppire-value-entry.h"
33 #include "ui/gui/psppire-var-sheet.h"
34 #include "ui/gui/psppire.h"
35 #include "ui/gui/psppire-conf.h"
36
37 #include <gettext.h>
38 #define _(msgid) gettext (msgid)
39
40 #define FOR_EACH_DATA_SHEET(DATA_SHEET, IDX, DATA_EDITOR)       \
41   for ((IDX) = 0;                                               \
42        (IDX) < 4                                                \
43          && ((DATA_SHEET) = PSPPIRE_DATA_SHEET (                \
44                (DATA_EDITOR)->data_sheets[IDX])) != NULL;       \
45        (IDX)++)
46
47 static void psppire_data_editor_class_init          (PsppireDataEditorClass *klass);
48 static void psppire_data_editor_init                (PsppireDataEditor      *de);
49
50 static void disconnect_data_sheets (PsppireDataEditor *);
51 static void refresh_entry (PsppireDataEditor *);
52 static void psppire_data_editor_update_ui_manager (PsppireDataEditor *);
53
54 GType
55 psppire_data_editor_get_type (void)
56 {
57   static GType de_type = 0;
58
59   if (!de_type)
60     {
61       static const GTypeInfo de_info =
62       {
63         sizeof (PsppireDataEditorClass),
64         NULL, /* base_init */
65         NULL, /* base_finalize */
66         (GClassInitFunc) psppire_data_editor_class_init,
67         NULL, /* class_finalize */
68         NULL, /* class_data */
69         sizeof (PsppireDataEditor),
70         0,
71         (GInstanceInitFunc) psppire_data_editor_init,
72       };
73
74       de_type = g_type_register_static (GTK_TYPE_NOTEBOOK, "PsppireDataEditor",
75                                         &de_info, 0);
76     }
77
78   return de_type;
79 }
80
81 static GObjectClass * parent_class = NULL;
82
83 static void
84 psppire_data_editor_dispose (GObject *obj)
85 {
86   PsppireDataEditor *de = (PsppireDataEditor *) obj;
87
88   disconnect_data_sheets (de);
89
90   if (de->data_store)
91     {
92       g_object_unref (de->data_store);
93       de->data_store = NULL;
94     }
95
96   if (de->dict)
97     {
98       g_object_unref (de->dict);
99       de->dict = NULL;
100     }
101
102   if (de->font != NULL)
103     {
104       pango_font_description_free (de->font);
105       de->font = NULL;
106     }
107
108   if (de->ui_manager)
109     {
110       g_object_unref (de->ui_manager);
111       de->ui_manager = NULL;
112     }
113
114   /* Chain up to the parent class */
115   G_OBJECT_CLASS (parent_class)->dispose (obj);
116 }
117
118 enum
119   {
120     PROP_0,
121     PROP_DATA_STORE,
122     PROP_DICTIONARY,
123     PROP_VALUE_LABELS,
124     PROP_SPLIT_WINDOW,
125     PROP_UI_MANAGER
126   };
127
128 static void
129 psppire_data_editor_refresh_model (PsppireDataEditor *de)
130 {
131   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (de->var_sheet);
132   PsppireDataSheet *data_sheet;
133   int i;
134
135   FOR_EACH_DATA_SHEET (data_sheet, i, de)
136     psppire_data_sheet_set_data_store (data_sheet, de->data_store);
137   psppire_var_sheet_set_dictionary (var_sheet, de->dict);
138 }
139
140 static void
141 psppire_data_editor_set_property (GObject         *object,
142                                   guint            prop_id,
143                                   const GValue    *value,
144                                   GParamSpec      *pspec)
145 {
146   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
147   PsppireDataSheet *data_sheet;
148   int i;
149
150   switch (prop_id)
151     {
152     case PROP_SPLIT_WINDOW:
153       psppire_data_editor_split_window (de, g_value_get_boolean (value));
154       break;
155     case PROP_DATA_STORE:
156       if ( de->data_store)
157         {
158           g_signal_handlers_disconnect_by_func (de->data_store,
159                                                 G_CALLBACK (refresh_entry),
160                                                 de);
161           g_object_unref (de->data_store);
162         }
163
164       de->data_store = g_value_get_pointer (value);
165       g_object_ref (de->data_store);
166       psppire_data_editor_refresh_model (de);
167
168       g_signal_connect_swapped (de->data_store, "case-changed",
169                                 G_CALLBACK (refresh_entry), de);
170
171       break;
172     case PROP_DICTIONARY:
173       if (de->dict)
174         g_object_unref (de->dict);
175       de->dict = g_value_get_pointer (value);
176       g_object_ref (de->dict);
177
178       psppire_var_sheet_set_dictionary (PSPPIRE_VAR_SHEET (de->var_sheet),
179                                         de->dict);
180       break;
181     case PROP_VALUE_LABELS:
182       FOR_EACH_DATA_SHEET (data_sheet, i, de)
183         psppire_data_sheet_set_value_labels (data_sheet,
184                                           g_value_get_boolean (value));
185       break;
186     case PROP_UI_MANAGER:
187     default:
188       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
189       break;
190     };
191 }
192
193 static void
194 psppire_data_editor_get_property (GObject         *object,
195                                   guint            prop_id,
196                                   GValue          *value,
197                                   GParamSpec      *pspec)
198 {
199   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
200
201   switch (prop_id)
202     {
203     case PROP_SPLIT_WINDOW:
204       g_value_set_boolean (value, de->split);
205       break;
206     case PROP_DATA_STORE:
207       g_value_set_pointer (value, de->data_store);
208       break;
209     case PROP_DICTIONARY:
210       g_value_set_pointer (value, de->dict);
211       break;
212     case PROP_VALUE_LABELS:
213       g_value_set_boolean (value,
214                            psppire_data_sheet_get_value_labels (
215                              PSPPIRE_DATA_SHEET (de->data_sheets[0])));
216       break;
217     case PROP_UI_MANAGER:
218       g_value_set_object (value, psppire_data_editor_get_ui_manager (de));
219       break;
220     default:
221       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
222       break;
223     }
224 }
225
226 static void
227 psppire_data_editor_switch_page (GtkNotebook     *notebook,
228                                  GtkWidget *w,
229                                  guint            page_num)
230 {
231   GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, w, page_num);
232   psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (notebook));
233 }
234
235 static void
236 psppire_data_editor_set_focus_child (GtkContainer *container,
237                                      GtkWidget    *widget)
238 {
239   GTK_CONTAINER_CLASS (parent_class)->set_focus_child (container, widget);
240   psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (container));
241 }
242
243 static void
244 psppire_data_editor_class_init (PsppireDataEditorClass *klass)
245 {
246   GParamSpec *data_store_spec ;
247   GParamSpec *dict_spec ;
248   GParamSpec *value_labels_spec;
249   GParamSpec *split_window_spec;
250   GParamSpec *ui_manager_spec;
251   GObjectClass *object_class = G_OBJECT_CLASS (klass);
252   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
253   GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
254
255   parent_class = g_type_class_peek_parent (klass);
256
257   object_class->dispose = psppire_data_editor_dispose;
258   object_class->set_property = psppire_data_editor_set_property;
259   object_class->get_property = psppire_data_editor_get_property;
260
261   container_class->set_focus_child = psppire_data_editor_set_focus_child;
262
263   notebook_class->switch_page = psppire_data_editor_switch_page;
264
265   data_store_spec =
266     g_param_spec_pointer ("data-store",
267                           "Data Store",
268                           "A pointer to the data store associated with this editor",
269                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
270
271   g_object_class_install_property (object_class,
272                                    PROP_DATA_STORE,
273                                    data_store_spec);
274
275   dict_spec =
276     g_param_spec_pointer ("dictionary",
277                           "Dictionary",
278                           "A pointer to the dictionary associated with this editor",
279                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
280
281   g_object_class_install_property (object_class,
282                                    PROP_DICTIONARY,
283                                    dict_spec);
284
285   value_labels_spec =
286     g_param_spec_boolean ("value-labels",
287                          "Value Labels",
288                          "Whether or not the data sheet should display labels instead of values",
289                           FALSE,
290                          G_PARAM_WRITABLE | G_PARAM_READABLE);
291
292   g_object_class_install_property (object_class,
293                                    PROP_VALUE_LABELS,
294                                    value_labels_spec);
295
296
297   split_window_spec =
298     g_param_spec_boolean ("split",
299                           "Split Window",
300                           "True iff the data sheet is split",
301                           FALSE,
302                           G_PARAM_READABLE | G_PARAM_WRITABLE);
303
304   g_object_class_install_property (object_class,
305                                    PROP_SPLIT_WINDOW,
306                                    split_window_spec);
307
308   ui_manager_spec =
309     g_param_spec_object ("ui-manager",
310                          "UI Manager",
311                          "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.",
312                          GTK_TYPE_UI_MANAGER,
313                          G_PARAM_READABLE);
314   g_object_class_install_property (object_class,
315                                    PROP_UI_MANAGER,
316                                    ui_manager_spec);
317 }
318
319 static gboolean
320 on_data_sheet_var_double_clicked (PsppireDataSheet *data_sheet,
321                                   gint dict_index,
322                                   PsppireDataEditor *de)
323 {
324   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
325                                  PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
326
327   psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
328                                    dict_index);
329
330   return TRUE;
331 }
332
333 static gboolean
334 on_var_sheet_var_double_clicked (PsppireVarSheet *var_sheet, gint dict_index,
335                                  PsppireDataEditor *de)
336 {
337   PsppireDataSheet *data_sheet;
338
339   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
340                                  PSPPIRE_DATA_EDITOR_DATA_VIEW);
341
342   data_sheet = psppire_data_editor_get_active_data_sheet (de);
343   psppire_data_sheet_goto_variable (data_sheet, dict_index);
344
345   return TRUE;
346 }
347
348 /* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
349    active cell or cells. */
350 static void
351 refresh_entry (PsppireDataEditor *de)
352 {
353   PsppireDataSheet *data_sheet = psppire_data_editor_get_active_data_sheet (de);
354   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
355   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
356
357   gchar *ref_cell_text;
358   GList *selected_columns, *iter;
359   struct variable *var;
360   gint n_cases;
361   gint n_vars;
362
363   selected_columns = pspp_sheet_selection_get_selected_columns (selection);
364   n_vars = 0;
365   var = NULL;
366   for (iter = selected_columns; iter != NULL; iter = iter->next)
367     {
368       PsppSheetViewColumn *column = iter->data;
369       struct variable *v = g_object_get_data (G_OBJECT (column), "variable");
370       if (v != NULL)
371         {
372           var = v;
373           n_vars++;
374         }
375     }
376   g_list_free (selected_columns);
377
378   n_cases = pspp_sheet_selection_count_selected_rows (selection);
379   if (n_cases > 0)
380     {
381       /* The final row is selectable but it isn't a case (it's just used to add
382          more cases), so don't count it. */
383       GtkTreePath *path;
384       gint case_count;
385
386       case_count = psppire_data_store_get_case_count (de->data_store);
387       path = gtk_tree_path_new_from_indices (case_count, -1);
388       if (pspp_sheet_selection_path_is_selected (selection, path))
389         n_cases--;
390       gtk_tree_path_free (path);
391     }
392
393   ref_cell_text = NULL;
394   if (n_cases == 1 && n_vars == 1)
395     {
396       PsppireValueEntry *value_entry = PSPPIRE_VALUE_ENTRY (de->datum_entry);
397       struct range_set *selected_rows;
398       gboolean show_value_labels;
399       union value value;
400       int width;
401       gint row;
402
403       selected_rows = pspp_sheet_selection_get_range_set (selection);
404       row = range_set_scan (selected_rows, 0);
405       range_set_destroy (selected_rows);
406
407       ref_cell_text = g_strdup_printf ("%d : %s", row + 1, var_get_name (var));
408
409       show_value_labels = psppire_data_sheet_get_value_labels (data_sheet);
410
411       psppire_value_entry_set_variable (value_entry, var);
412       psppire_value_entry_set_show_value_label (value_entry,
413                                                 show_value_labels);
414
415       width = var_get_width (var);
416       value_init (&value, width);
417       datasheet_get_value (de->data_store->datasheet,
418                            row, var_get_case_index (var), &value);
419       psppire_value_entry_set_value (value_entry, &value, width);
420       value_destroy (&value, width);
421
422       gtk_widget_set_sensitive (de->datum_entry, TRUE);
423     }
424   else
425     {
426       if (n_cases == 0 || n_vars == 0)
427         {
428           ref_cell_text = NULL;
429         }
430       else
431         {
432           struct string s;
433
434           /* The glib string library does not understand the ' printf modifier
435              on all platforms, but the "struct string" library does (because
436              Gnulib fixes that problem), so use the latter.  */
437           ds_init_empty (&s);
438           ds_put_format (&s, ngettext ("%'d case", "%'d cases", n_cases),
439                          n_cases);
440           ds_put_byte (&s, ' ');
441           ds_put_unichar (&s, 0xd7); /* U+00D7 MULTIPLICATION SIGN */
442           ds_put_byte (&s, ' ');
443           ds_put_format (&s, ngettext ("%'d variable", "%'d variables",
444                                        n_vars),
445                          n_vars);
446           ref_cell_text = ds_steal_cstr (&s);
447         }
448
449       psppire_value_entry_set_variable (PSPPIRE_VALUE_ENTRY (de->datum_entry),
450                                         NULL);
451       gtk_entry_set_text (
452         GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))), "");
453       gtk_widget_set_sensitive (de->datum_entry, FALSE);
454     }
455
456   gtk_label_set_label (GTK_LABEL (de->cell_ref_label),
457                        ref_cell_text ? ref_cell_text : "");
458   g_free (ref_cell_text);
459 }
460
461 static void
462 on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
463 {
464   PsppireDataSheet *data_sheet = psppire_data_editor_get_active_data_sheet (de);
465   struct variable *var;
466   union value value;
467   int width;
468   gint row;
469
470   row = psppire_data_sheet_get_current_case (data_sheet);
471   var = psppire_data_sheet_get_current_variable (data_sheet);
472   if (row < 0 || !var)
473     return;
474
475   width = var_get_width (var);
476   value_init (&value, width);
477   if (psppire_value_entry_get_value (PSPPIRE_VALUE_ENTRY (de->datum_entry),
478                                      &value, width))
479     psppire_data_store_set_value (de->data_store, row, var, &value);
480   value_destroy (&value, width);
481 }
482
483 static void
484 on_data_sheet_selection_changed (PsppSheetSelection *selection,
485                                  PsppireDataEditor *de)
486 {
487   /* In a split view, ensure that only a single data sheet has a nonempty
488      selection.  */
489   if (de->split
490       && pspp_sheet_selection_count_selected_rows (selection)
491       && pspp_sheet_selection_count_selected_columns (selection))
492     {
493       PsppireDataSheet *ds;
494       int i;
495
496       FOR_EACH_DATA_SHEET (ds, i, de)
497         {
498           PsppSheetSelection *s;
499
500           s = pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (ds));
501           if (s != selection)
502             pspp_sheet_selection_unselect_all (s);
503         }
504     }
505
506   refresh_entry (de);
507 }
508
509 /* Ensures that rows in the right-hand panes in the split view have the same
510    row height as the left-hand panes.  Otherwise, the rows in the right-hand
511    pane tend to be smaller, because the right-hand pane doesn't have buttons
512    for case numbers. */
513 static void
514 on_data_sheet_fixed_height_notify (PsppireDataSheet *ds,
515                                    GParamSpec *pspec,
516                                    PsppireDataEditor *de)
517 {
518   enum
519     {
520       TL = GTK_XPANED_TOP_LEFT,
521       TR = GTK_XPANED_TOP_RIGHT,
522       BL = GTK_XPANED_BOTTOM_LEFT,
523       BR = GTK_XPANED_BOTTOM_RIGHT
524     };
525
526   int fixed_height = pspp_sheet_view_get_fixed_height (PSPP_SHEET_VIEW (ds));
527
528   pspp_sheet_view_set_fixed_height (PSPP_SHEET_VIEW (de->data_sheets[TR]),
529                                     fixed_height);
530   pspp_sheet_view_set_fixed_height (PSPP_SHEET_VIEW (de->data_sheets[BR]),
531                                     fixed_height);
532 }
533
534 static void
535 disconnect_data_sheets (PsppireDataEditor *de)
536 {
537   PsppireDataSheet *ds;
538   int i;
539
540   FOR_EACH_DATA_SHEET (ds, i, de)
541     {
542       PsppSheetSelection *selection;
543
544       if (ds == NULL)
545         {
546           /* This can only happen if 'dispose' runs more than once. */
547           continue;
548         }
549
550       if (i == GTK_XPANED_TOP_LEFT)
551         g_signal_handlers_disconnect_by_func (
552           ds, G_CALLBACK (on_data_sheet_fixed_height_notify), de);
553
554       g_signal_handlers_disconnect_by_func (
555         ds, G_CALLBACK (refresh_entry), de);
556       g_signal_handlers_disconnect_by_func (
557         ds, G_CALLBACK (on_data_sheet_var_double_clicked), de);
558
559       selection = pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (ds));
560       g_signal_handlers_disconnect_by_func (
561         selection, G_CALLBACK (on_data_sheet_selection_changed), de);
562
563       de->data_sheets[i] = NULL;
564     }
565 }
566
567 static GtkWidget *
568 make_data_sheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines,
569                  gboolean show_value_labels)
570 {
571   PsppSheetSelection *selection;
572   GtkWidget *ds;
573
574   ds = psppire_data_sheet_new ();
575   pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (ds), grid_lines);
576   psppire_data_sheet_set_value_labels (PSPPIRE_DATA_SHEET (ds),
577                                        show_value_labels);
578
579   g_signal_connect_swapped (ds, "notify::value-labels",
580                             G_CALLBACK (refresh_entry), de);
581   g_signal_connect (ds, "var-double-clicked",
582                     G_CALLBACK (on_data_sheet_var_double_clicked), de);
583
584   selection = pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (ds));
585   g_signal_connect (selection, "changed",
586                     G_CALLBACK (on_data_sheet_selection_changed), de);
587
588   return ds;
589 }
590
591 static GtkWidget *
592 make_single_datasheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines,
593                        gboolean show_value_labels)
594 {
595   GtkWidget *data_sheet_scroller;
596
597   de->data_sheets[0] = make_data_sheet (de, grid_lines, show_value_labels);
598   de->data_sheets[1] = de->data_sheets[2] = de->data_sheets[3] = NULL;
599
600   /* Put data sheet in scroller. */
601   data_sheet_scroller = gtk_scrolled_window_new (NULL, NULL);
602   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (data_sheet_scroller),
603                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
604   gtk_container_add (GTK_CONTAINER (data_sheet_scroller), de->data_sheets[0]);
605
606   return data_sheet_scroller;
607 }
608
609 static GtkWidget *
610 make_split_datasheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines,
611                       gboolean show_value_labels)
612 {
613   /* Panes, in the order in which we want to create them. */
614   enum
615     {
616       TL,                       /* top left */
617       TR,                       /* top right */
618       BL,                       /* bottom left */
619       BR                        /* bottom right */
620     };
621
622   PsppSheetView *ds[4];
623   GtkXPaned *xpaned;
624   int i;
625
626   xpaned = GTK_XPANED (gtk_xpaned_new ());
627
628   for (i = 0; i < 4; i++)
629     {
630       GtkAdjustment *hadjust, *vadjust;
631       GtkPolicyType hpolicy, vpolicy;
632       GtkWidget *scroller;
633
634       de->data_sheets[i] = make_data_sheet (de, grid_lines, show_value_labels);
635       ds[i] = PSPP_SHEET_VIEW (de->data_sheets[i]);
636
637       if (i == BL)
638         hadjust = pspp_sheet_view_get_hadjustment (ds[TL]);
639       else if (i == BR)
640         hadjust = pspp_sheet_view_get_hadjustment (ds[TR]);
641       else
642         hadjust = NULL;
643
644       if (i == TR)
645         vadjust = pspp_sheet_view_get_vadjustment (ds[TL]);
646       else if (i == BR)
647         vadjust = pspp_sheet_view_get_vadjustment (ds[BL]);
648       else
649         vadjust = NULL;
650
651       scroller = gtk_scrolled_window_new (hadjust, vadjust);
652       gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroller),
653                                            GTK_SHADOW_ETCHED_IN);
654       hpolicy = i == TL || i == TR ? GTK_POLICY_NEVER : GTK_POLICY_ALWAYS;
655       vpolicy = i == TL || i == BL ? GTK_POLICY_NEVER : GTK_POLICY_ALWAYS;
656       gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroller),
657                                       hpolicy, vpolicy);
658       gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (ds[i]));
659
660       switch (i)
661         {
662         case TL:
663           gtk_xpaned_pack_top_left (xpaned, scroller, TRUE, TRUE);
664           break;
665
666         case TR:
667           gtk_xpaned_pack_top_right (xpaned, scroller, TRUE, TRUE);
668           break;
669
670         case BL:
671           gtk_xpaned_pack_bottom_left (xpaned, scroller, TRUE, TRUE);
672           break;
673
674         case BR:
675           gtk_xpaned_pack_bottom_right (xpaned, scroller, TRUE, TRUE);
676           break;
677
678         default:
679           g_warn_if_reached ();
680         }
681     }
682
683   /* Bottom sheets don't display variable names. */
684   pspp_sheet_view_set_headers_visible (ds[BL], FALSE);
685   pspp_sheet_view_set_headers_visible (ds[BR], FALSE);
686
687   /* Right sheets don't display case numbers. */
688   psppire_data_sheet_set_case_numbers (PSPPIRE_DATA_SHEET (ds[TR]), FALSE);
689   psppire_data_sheet_set_case_numbers (PSPPIRE_DATA_SHEET (ds[BR]), FALSE);
690
691   g_signal_connect (ds[TL], "notify::fixed-height",
692                     G_CALLBACK (on_data_sheet_fixed_height_notify), de);
693
694   return GTK_WIDGET (xpaned);
695 }
696
697 static void set_font_recursively (GtkWidget *w, gpointer data);
698
699 static void
700 psppire_data_editor_init (PsppireDataEditor *de)
701 {
702   GtkWidget *var_sheet_scroller;
703   GtkWidget *hbox;
704   gchar *fontname = NULL;
705
706   de->font = NULL;
707   de->ui_manager = NULL;
708   de->old_vbox_widget = NULL;
709
710   g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
711
712   de->cell_ref_label = gtk_label_new ("");
713   gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
714   gtk_misc_set_alignment (GTK_MISC (de->cell_ref_label), 0.0, 0.5);
715
716   de->datum_entry = psppire_value_entry_new ();
717   g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
718                     "activate", G_CALLBACK (on_datum_entry_activate), de);
719
720   hbox = gtk_hbox_new (FALSE, 0);
721   gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
722   gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
723
724   de->split = FALSE;
725   de->datasheet_vbox_widget
726     = make_single_datasheet (de, GTK_TREE_VIEW_GRID_LINES_BOTH, FALSE);
727
728   de->vbox = gtk_vbox_new (FALSE, 0);
729   gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
730   gtk_box_pack_start (GTK_BOX (de->vbox), de->datasheet_vbox_widget,
731                       TRUE, TRUE, 0);
732
733   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
734                             gtk_label_new_with_mnemonic (_("Data View")));
735
736   gtk_widget_show_all (de->vbox);
737
738   de->var_sheet = GTK_WIDGET (psppire_var_sheet_new ());
739   pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet),
740                                   GTK_TREE_VIEW_GRID_LINES_BOTH);
741   var_sheet_scroller = gtk_scrolled_window_new (NULL, NULL);
742   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (var_sheet_scroller),
743                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
744   gtk_container_add (GTK_CONTAINER (var_sheet_scroller), de->var_sheet);
745   gtk_widget_show_all (var_sheet_scroller);
746   gtk_notebook_append_page (GTK_NOTEBOOK (de), var_sheet_scroller,
747                             gtk_label_new_with_mnemonic (_("Variable View")));
748
749   g_signal_connect (de->var_sheet, "var-double-clicked",
750                     G_CALLBACK (on_var_sheet_var_double_clicked), de);
751
752   g_object_set (de, "can-focus", FALSE, NULL);
753
754   if (psppire_conf_get_string (psppire_conf_new (),
755                            "Data Editor", "font",
756                                 &fontname) )
757     {
758       de->font = pango_font_description_from_string (fontname);
759       g_free (fontname);
760       set_font_recursively (GTK_WIDGET (de), de->font);
761     }
762
763   psppire_data_editor_update_ui_manager (de);
764 }
765
766 GtkWidget*
767 psppire_data_editor_new (PsppireDict *dict,
768                          PsppireDataStore *data_store)
769 {
770   return  g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
771                         "dictionary",  dict,
772                         "data-store",  data_store,
773                         NULL);
774 }
775 \f
776 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
777    sheet(s) and variable sheet. */
778 void
779 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
780 {
781   GtkTreeViewGridLines grid;
782   PsppireDataSheet *data_sheet;
783   int i;
784
785   grid = (grid_visible
786           ? GTK_TREE_VIEW_GRID_LINES_BOTH
787           : GTK_TREE_VIEW_GRID_LINES_NONE);
788
789   FOR_EACH_DATA_SHEET (data_sheet, i, de)
790     pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (data_sheet), grid);
791   pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet), grid);
792 }
793
794
795 static void
796 set_font_recursively (GtkWidget *w, gpointer data)
797 {
798   PangoFontDescription *font_desc = data;
799   GtkRcStyle *style = gtk_widget_get_modifier_style (w);
800
801   pango_font_description_free (style->font_desc);
802   style->font_desc = pango_font_description_copy (font_desc);
803
804   gtk_widget_modify_style (w, style);
805
806   if ( GTK_IS_CONTAINER (w))
807     gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
808 }
809
810 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
811 void
812 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
813 {
814   gchar *font_name;
815   set_font_recursively (GTK_WIDGET (de), font_desc);
816
817   if (de->font)
818     pango_font_description_free (de->font);
819   de->font = pango_font_description_copy (font_desc);
820   font_name = pango_font_description_to_string (de->font);
821
822   psppire_conf_set_string (psppire_conf_new (),
823                            "Data Editor", "font",
824                            font_name);
825
826 }
827
828 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
829    If SPLIT is FALSE, un-splits it into a single pane. */
830 void
831 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
832 {
833   GtkTreeViewGridLines grid_lines;
834   gboolean labels;
835
836   if (split == de->split)
837     return;
838
839
840   grid_lines = pspp_sheet_view_get_grid_lines (
841     PSPP_SHEET_VIEW (de->data_sheets[0]));
842   labels = psppire_data_sheet_get_value_labels (PSPPIRE_DATA_SHEET (
843                                                   de->data_sheets[0]));
844
845   disconnect_data_sheets (de);
846   if (de->old_vbox_widget)
847     g_object_unref (de->old_vbox_widget);
848   de->old_vbox_widget = de->datasheet_vbox_widget;
849   g_object_ref (de->old_vbox_widget);
850   /* FIXME:  old_vbox_widget needs to be unreffed in dispose.
851         (currently it seems to provoke an error if I do that.  
852         I don't know why. */
853   gtk_container_remove (GTK_CONTAINER (de->vbox), de->datasheet_vbox_widget);
854
855   if (split)
856     de->datasheet_vbox_widget = make_split_datasheet (de, grid_lines, labels);
857   else
858     de->datasheet_vbox_widget = make_single_datasheet (de, grid_lines, labels);
859
860   psppire_data_editor_refresh_model (de);
861
862   gtk_box_pack_start (GTK_BOX (de->vbox), de->datasheet_vbox_widget,
863                       TRUE, TRUE, 0);
864   gtk_widget_show_all (de->vbox);
865
866   if (de->font)
867     set_font_recursively (GTK_WIDGET (de), de->font);
868
869   de->split = split;
870   g_object_notify (G_OBJECT (de), "split");
871   psppire_data_editor_update_ui_manager (de);
872 }
873
874 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
875    visible and selected in the active view in DE. */
876 void
877 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
878 {
879   PsppireDataSheet *data_sheet;
880
881   switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
882     {
883     case PSPPIRE_DATA_EDITOR_DATA_VIEW:
884       data_sheet = psppire_data_editor_get_active_data_sheet (de);
885       psppire_data_sheet_goto_variable (data_sheet, dict_index);
886       break;
887
888     case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
889       psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
890                                        dict_index);
891       break;
892     }
893 }
894
895 /* Returns the "active" data sheet in DE.  If DE is in single-paned mode, this
896    is the only data sheet.  If DE is in split mode (showing four data sheets),
897    this is the focused data sheet or, if none is focused, the data sheet with
898    selected cells or, if none has selected cells, the upper-left data sheet. */
899 PsppireDataSheet *
900 psppire_data_editor_get_active_data_sheet (PsppireDataEditor *de)
901 {
902   if (de->split)
903     {
904       PsppireDataSheet *data_sheet;
905       GtkWidget *scroller;
906       int i;
907
908       /* If one of the datasheet's scrollers is focused, choose that one. */
909       scroller = gtk_container_get_focus_child (
910         GTK_CONTAINER (de->datasheet_vbox_widget));
911       if (scroller != NULL)
912         return PSPPIRE_DATA_SHEET (gtk_bin_get_child (GTK_BIN (scroller)));
913
914       /* Otherwise if there's a nonempty selection in some data sheet, choose
915          that one. */
916       FOR_EACH_DATA_SHEET (data_sheet, i, de)
917         {
918           PsppSheetSelection *selection;
919
920           selection = pspp_sheet_view_get_selection (
921             PSPP_SHEET_VIEW (data_sheet));
922           if (pspp_sheet_selection_count_selected_rows (selection)
923               && pspp_sheet_selection_count_selected_columns (selection))
924             return data_sheet;
925         }
926     }
927
928   return PSPPIRE_DATA_SHEET (de->data_sheets[0]);
929 }
930
931 /* Returns the UI manager that should be merged into DE's toplevel widget's UI
932    manager to display menu items and toolbar items specific to DE's current
933    page and data sheet.
934
935    DE's toplevel widget can watch for changes by connecting to DE's
936    notify::ui-manager signal. */
937 GtkUIManager *
938 psppire_data_editor_get_ui_manager (PsppireDataEditor *de)
939 {
940   psppire_data_editor_update_ui_manager (de);
941   return de->ui_manager;
942 }
943
944 static void
945 psppire_data_editor_update_ui_manager (PsppireDataEditor *de)
946 {
947   PsppireDataSheet *data_sheet;
948   GtkUIManager *ui_manager;
949
950   ui_manager = NULL;
951
952   switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
953     {
954     case PSPPIRE_DATA_EDITOR_DATA_VIEW:
955       data_sheet = psppire_data_editor_get_active_data_sheet (de);
956       if (data_sheet != NULL)
957         ui_manager = psppire_data_sheet_get_ui_manager (data_sheet);
958       else
959         {
960           /* This happens transiently in psppire_data_editor_split_window(). */
961         }
962       break;
963
964     case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
965       ui_manager = psppire_var_sheet_get_ui_manager (
966         PSPPIRE_VAR_SHEET (de->var_sheet));
967       break;
968
969     default:
970       /* This happens transiently in psppire_data_editor_init(). */
971       break;
972     }
973
974   if (ui_manager != de->ui_manager)
975     {
976       if (de->ui_manager)
977         g_object_unref (de->ui_manager);
978       if (ui_manager)
979         g_object_ref (ui_manager);
980       de->ui_manager = ui_manager;
981
982       g_object_notify (G_OBJECT (de), "ui-manager");
983     }
984 }