Adapt to new sheet
[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-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
35 #include "ui/gui/efficient-sheet/jmd-sheet.h"
36
37 #include <gettext.h>
38 #define _(msgid) gettext (msgid)
39
40
41 static void psppire_data_editor_class_init          (PsppireDataEditorClass *klass);
42 static void psppire_data_editor_init                (PsppireDataEditor      *de);
43
44 static void disconnect_data_sheets (PsppireDataEditor *);
45 static void refresh_entry (PsppireDataEditor *);
46 static void psppire_data_editor_update_ui_manager (PsppireDataEditor *);
47
48 GType
49 psppire_data_editor_get_type (void)
50 {
51   static GType de_type = 0;
52
53   if (!de_type)
54     {
55       static const GTypeInfo de_info =
56       {
57         sizeof (PsppireDataEditorClass),
58         NULL, /* base_init */
59         NULL, /* base_finalize */
60         (GClassInitFunc) psppire_data_editor_class_init,
61         NULL, /* class_finalize */
62         NULL, /* class_data */
63         sizeof (PsppireDataEditor),
64         0,
65         (GInstanceInitFunc) psppire_data_editor_init,
66       };
67
68       de_type = g_type_register_static (GTK_TYPE_NOTEBOOK, "PsppireDataEditor",
69                                         &de_info, 0);
70     }
71
72   return de_type;
73 }
74
75 static GObjectClass * parent_class = NULL;
76
77 static void
78 psppire_data_editor_dispose (GObject *obj)
79 {
80   PsppireDataEditor *de = (PsppireDataEditor *) obj;
81
82   disconnect_data_sheets (de);
83
84   if (de->data_store)
85     {
86       g_object_unref (de->data_store);
87       de->data_store = NULL;
88     }
89
90   if (de->dict)
91     {
92       g_object_unref (de->dict);
93       de->dict = NULL;
94     }
95
96   if (de->font != NULL)
97     {
98       pango_font_description_free (de->font);
99       de->font = NULL;
100     }
101
102   if (de->ui_manager)
103     {
104       g_object_unref (de->ui_manager);
105       de->ui_manager = NULL;
106     }
107
108   /* Chain up to the parent class */
109   G_OBJECT_CLASS (parent_class)->dispose (obj);
110 }
111
112 enum
113   {
114     PROP_0,
115     PROP_DATA_STORE,
116     PROP_DICTIONARY,
117     PROP_VALUE_LABELS,
118     PROP_SPLIT_WINDOW,
119     PROP_UI_MANAGER
120   };
121
122 static void
123 psppire_data_editor_refresh_model (PsppireDataEditor *de)
124 {
125   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (de->var_sheet);
126     int i;
127
128   psppire_var_sheet_set_dictionary (var_sheet, de->dict);
129 }
130
131 static void
132 psppire_data_editor_set_property (GObject         *object,
133                                   guint            prop_id,
134                                   const GValue    *value,
135                                   GParamSpec      *pspec)
136 {
137   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
138
139   int i;
140
141   switch (prop_id)
142     {
143     case PROP_SPLIT_WINDOW:
144       psppire_data_editor_split_window (de, g_value_get_boolean (value));
145       break;
146     case PROP_DATA_STORE:
147       if ( de->data_store)
148         {
149           g_signal_handlers_disconnect_by_func (de->data_store,
150                                                 G_CALLBACK (refresh_entry),
151                                                 de);
152           g_object_unref (de->data_store);
153         }
154
155       de->data_store = g_value_get_pointer (value);
156       g_object_ref (de->data_store);
157       g_print ("NEW STORE\n");
158
159       g_object_set (de->data_sheet, "data-model", de->data_store, NULL);
160       psppire_data_editor_refresh_model (de);
161
162       g_signal_connect_swapped (de->data_store, "case-changed",
163                                 G_CALLBACK (refresh_entry), de);
164
165       break;
166     case PROP_DICTIONARY:
167       if (de->dict)
168         g_object_unref (de->dict);
169       de->dict = g_value_get_pointer (value);
170       g_object_ref (de->dict);
171
172       g_object_set (de->data_sheet, "hmodel", de->dict, NULL);
173
174       psppire_var_sheet_set_dictionary (PSPPIRE_VAR_SHEET (de->var_sheet),
175                                         de->dict);
176       break;
177     case PROP_VALUE_LABELS:
178       break;
179     case PROP_UI_MANAGER:
180     default:
181       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
182       break;
183     };
184 }
185
186 static void
187 psppire_data_editor_get_property (GObject         *object,
188                                   guint            prop_id,
189                                   GValue          *value,
190                                   GParamSpec      *pspec)
191 {
192   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
193
194   switch (prop_id)
195     {
196     case PROP_SPLIT_WINDOW:
197       g_value_set_boolean (value, de->split);
198       break;
199     case PROP_DATA_STORE:
200       g_value_set_pointer (value, de->data_store);
201       break;
202     case PROP_DICTIONARY:
203       g_value_set_pointer (value, de->dict);
204       break;
205     case PROP_VALUE_LABELS:
206       break;
207     case PROP_UI_MANAGER:
208       g_value_set_object (value, psppire_data_editor_get_ui_manager (de));
209       break;
210     default:
211       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
212       break;
213     }
214 }
215
216 static void
217 psppire_data_editor_switch_page (GtkNotebook     *notebook,
218                                  GtkWidget *w,
219                                  guint            page_num)
220 {
221   GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, w, page_num);
222   psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (notebook));
223 }
224
225 static void
226 psppire_data_editor_set_focus_child (GtkContainer *container,
227                                      GtkWidget    *widget)
228 {
229   GTK_CONTAINER_CLASS (parent_class)->set_focus_child (container, widget);
230   psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (container));
231 }
232
233 static void
234 psppire_data_editor_class_init (PsppireDataEditorClass *klass)
235 {
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);
244
245   parent_class = g_type_class_peek_parent (klass);
246
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;
250
251   container_class->set_focus_child = psppire_data_editor_set_focus_child;
252
253   notebook_class->switch_page = psppire_data_editor_switch_page;
254
255   data_store_spec =
256     g_param_spec_pointer ("data-store",
257                           "Data Store",
258                           "A pointer to the data store associated with this editor",
259                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
260
261   g_object_class_install_property (object_class,
262                                    PROP_DATA_STORE,
263                                    data_store_spec);
264
265   dict_spec =
266     g_param_spec_pointer ("dictionary",
267                           "Dictionary",
268                           "A pointer to the dictionary 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_DICTIONARY,
273                                    dict_spec);
274
275   value_labels_spec =
276     g_param_spec_boolean ("value-labels",
277                          "Value Labels",
278                          "Whether or not the data sheet should display labels instead of values",
279                           FALSE,
280                          G_PARAM_WRITABLE | G_PARAM_READABLE);
281
282   g_object_class_install_property (object_class,
283                                    PROP_VALUE_LABELS,
284                                    value_labels_spec);
285
286
287   split_window_spec =
288     g_param_spec_boolean ("split",
289                           "Split Window",
290                           "True iff the data sheet is split",
291                           FALSE,
292                           G_PARAM_READABLE | G_PARAM_WRITABLE);
293
294   g_object_class_install_property (object_class,
295                                    PROP_SPLIT_WINDOW,
296                                    split_window_spec);
297
298   ui_manager_spec =
299     g_param_spec_object ("ui-manager",
300                          "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.",
302                          GTK_TYPE_UI_MANAGER,
303                          G_PARAM_READABLE);
304   g_object_class_install_property (object_class,
305                                    PROP_UI_MANAGER,
306                                    ui_manager_spec);
307 }
308
309 static gboolean
310 on_data_sheet_var_double_clicked (GtkWidget *data_sheet,
311                                   gint dict_index,
312                                   PsppireDataEditor *de)
313 {
314   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
315                                  PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
316
317   psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
318                                    dict_index);
319
320   return TRUE;
321 }
322
323 static gboolean
324 on_var_sheet_var_double_clicked (PsppireVarSheet *var_sheet, gint dict_index,
325                                  PsppireDataEditor *de)
326 {
327
328   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
329                                  PSPPIRE_DATA_EDITOR_DATA_VIEW);
330
331
332   return TRUE;
333 }
334
335 /* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
336    active cell or cells. */
337 static void
338 refresh_entry (PsppireDataEditor *de)
339 {
340 }
341
342 static void
343 on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
344 {
345 }
346
347 static void
348 on_data_sheet_selection_changed (PsppSheetSelection *selection,
349                                  PsppireDataEditor *de)
350 {
351   /* In a split view, ensure that only a single data sheet has a nonempty
352      selection.  */
353   if (de->split
354       && pspp_sheet_selection_count_selected_rows (selection)
355       && pspp_sheet_selection_count_selected_columns (selection))
356     {
357     }
358
359   refresh_entry (de);
360 }
361
362
363 static void
364 disconnect_data_sheets (PsppireDataEditor *de)
365 {
366   int i;
367
368 }
369
370
371 static void set_font_recursively (GtkWidget *w, gpointer data);
372
373 static void
374 psppire_data_editor_init (PsppireDataEditor *de)
375 {
376   GtkWidget *var_sheet_scroller;
377   GtkWidget *hbox;
378   gchar *fontname = NULL;
379
380   de->font = NULL;
381   de->ui_manager = NULL;
382   de->old_vbox_widget = NULL;
383
384   g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
385
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);
389
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);
393
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);
397
398   de->split = FALSE;
399   de->data_sheet = g_object_new (JMD_TYPE_SHEET,
400                                  "splitter", GTK_TYPE_XPANED,
401                                  NULL);
402   GtkWidget *button = jmd_sheet_get_button (JMD_SHEET (de->data_sheet));
403   gtk_button_set_label (GTK_BUTTON (button), _("Case"));
404   de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
405   gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
406   gtk_box_pack_start (GTK_BOX (de->vbox), de->data_sheet, TRUE, TRUE, 0);
407
408   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
409                             gtk_label_new_with_mnemonic (_("Data View")));
410
411   gtk_widget_show_all (de->vbox);
412
413   de->var_sheet = GTK_WIDGET (psppire_var_sheet_new ());
414
415   pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet),
416                                   GTK_TREE_VIEW_GRID_LINES_BOTH);
417   var_sheet_scroller = gtk_scrolled_window_new (NULL, NULL);
418   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (var_sheet_scroller),
419                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
420   gtk_container_add (GTK_CONTAINER (var_sheet_scroller), de->var_sheet);
421   gtk_widget_show_all (var_sheet_scroller);
422
423   gtk_notebook_append_page (GTK_NOTEBOOK (de), var_sheet_scroller,
424                             gtk_label_new_with_mnemonic (_("Variable View")));
425
426   g_signal_connect (de->var_sheet, "var-double-clicked",
427                     G_CALLBACK (on_var_sheet_var_double_clicked), de);
428
429   g_object_set (de, "can-focus", FALSE, NULL);
430
431   if (psppire_conf_get_string (psppire_conf_new (),
432                            "Data Editor", "font",
433                                 &fontname) )
434     {
435       de->font = pango_font_description_from_string (fontname);
436       g_free (fontname);
437       set_font_recursively (GTK_WIDGET (de), de->font);
438     }
439
440   psppire_data_editor_update_ui_manager (de);
441 }
442
443 GtkWidget*
444 psppire_data_editor_new (PsppireDict *dict,
445                          PsppireDataStore *data_store)
446 {
447   return  g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
448                         "dictionary",  dict,
449                         "data-store",  data_store,
450                         NULL);
451 }
452 \f
453 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
454    sheet(s) and variable sheet. */
455 void
456 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
457 {
458   GtkTreeViewGridLines grid;
459   int i;
460
461   grid = (grid_visible
462           ? GTK_TREE_VIEW_GRID_LINES_BOTH
463           : GTK_TREE_VIEW_GRID_LINES_NONE);
464
465   pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet), grid);
466 }
467
468
469 static void
470 set_font_recursively (GtkWidget *w, gpointer data)
471 {
472   PangoFontDescription *font_desc = data;
473
474   gtk_widget_override_font (w, font_desc);
475
476   if ( GTK_IS_CONTAINER (w))
477     gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
478 }
479
480 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
481 void
482 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
483 {
484   gchar *font_name;
485   set_font_recursively (GTK_WIDGET (de), font_desc);
486
487   if (de->font)
488     pango_font_description_free (de->font);
489   de->font = pango_font_description_copy (font_desc);
490   font_name = pango_font_description_to_string (de->font);
491
492   psppire_conf_set_string (psppire_conf_new (),
493                            "Data Editor", "font",
494                            font_name);
495
496 }
497
498 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
499    If SPLIT is FALSE, un-splits it into a single pane. */
500 void
501 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
502 {
503   GtkTreeViewGridLines grid_lines;
504   gboolean labels;
505
506   if (split == de->split)
507     return;
508
509
510
511   disconnect_data_sheets (de);
512
513   psppire_data_editor_refresh_model (de);
514
515   gtk_widget_show_all (de->vbox);
516
517   if (de->font)
518     set_font_recursively (GTK_WIDGET (de), de->font);
519
520   de->split = split;
521   g_object_notify (G_OBJECT (de), "split");
522   psppire_data_editor_update_ui_manager (de);
523 }
524
525 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
526    visible and selected in the active view in DE. */
527 void
528 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
529 {
530
531   switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
532     {
533
534     case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
535       psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
536                                        dict_index);
537       break;
538     }
539 }
540
541
542 /* Returns the UI manager that should be merged into DE's toplevel widget's UI
543    manager to display menu items and toolbar items specific to DE's current
544    page and data sheet.
545
546    DE's toplevel widget can watch for changes by connecting to DE's
547    notify::ui-manager signal. */
548 GtkUIManager *
549 psppire_data_editor_get_ui_manager (PsppireDataEditor *de)
550 {
551   psppire_data_editor_update_ui_manager (de);
552   return de->ui_manager;
553 }
554
555 static void
556 psppire_data_editor_update_ui_manager (PsppireDataEditor *de)
557 {
558   GtkUIManager *ui_manager;
559
560   ui_manager = NULL;
561
562   switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
563     {
564     case PSPPIRE_DATA_EDITOR_DATA_VIEW:
565       break;
566
567     case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
568       ui_manager = psppire_var_sheet_get_ui_manager (
569         PSPPIRE_VAR_SHEET (de->var_sheet));
570       break;
571
572     default:
573       /* This happens transiently in psppire_data_editor_init(). */
574       break;
575     }
576
577   if (ui_manager != de->ui_manager)
578     {
579       if (de->ui_manager)
580         g_object_unref (de->ui_manager);
581       if (ui_manager)
582         g_object_ref (ui_manager);
583       de->ui_manager = ui_manager;
584
585       g_object_notify (G_OBJECT (de), "ui-manager");
586     }
587 }
588
589