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