Fix things
[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                                  NULL);
401   GtkWidget *button = jmd_sheet_get_button (JMD_SHEET (de->data_sheet));
402   gtk_button_set_label (GTK_BUTTON (button), _("Case"));
403   de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
404   gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
405   gtk_box_pack_start (GTK_BOX (de->vbox), de->data_sheet, TRUE, TRUE, 0);
406
407   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
408                             gtk_label_new_with_mnemonic (_("Data View")));
409
410   gtk_widget_show_all (de->vbox);
411
412   de->var_sheet = GTK_WIDGET (psppire_var_sheet_new ());
413
414   pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet),
415                                   GTK_TREE_VIEW_GRID_LINES_BOTH);
416   var_sheet_scroller = gtk_scrolled_window_new (NULL, NULL);
417   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (var_sheet_scroller),
418                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
419   gtk_container_add (GTK_CONTAINER (var_sheet_scroller), de->var_sheet);
420   gtk_widget_show_all (var_sheet_scroller);
421
422   gtk_notebook_append_page (GTK_NOTEBOOK (de), var_sheet_scroller,
423                             gtk_label_new_with_mnemonic (_("Variable View")));
424
425   g_signal_connect (de->var_sheet, "var-double-clicked",
426                     G_CALLBACK (on_var_sheet_var_double_clicked), de);
427
428   g_object_set (de, "can-focus", FALSE, NULL);
429
430   if (psppire_conf_get_string (psppire_conf_new (),
431                            "Data Editor", "font",
432                                 &fontname) )
433     {
434       de->font = pango_font_description_from_string (fontname);
435       g_free (fontname);
436       set_font_recursively (GTK_WIDGET (de), de->font);
437     }
438
439   psppire_data_editor_update_ui_manager (de);
440 }
441
442 GtkWidget*
443 psppire_data_editor_new (PsppireDict *dict,
444                          PsppireDataStore *data_store)
445 {
446   return  g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
447                         "dictionary",  dict,
448                         "data-store",  data_store,
449                         NULL);
450 }
451 \f
452 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
453    sheet(s) and variable sheet. */
454 void
455 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
456 {
457   GtkTreeViewGridLines grid;
458   int i;
459
460   grid = (grid_visible
461           ? GTK_TREE_VIEW_GRID_LINES_BOTH
462           : GTK_TREE_VIEW_GRID_LINES_NONE);
463
464   pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet), grid);
465 }
466
467
468 static void
469 set_font_recursively (GtkWidget *w, gpointer data)
470 {
471   PangoFontDescription *font_desc = data;
472
473   gtk_widget_override_font (w, font_desc);
474
475   if ( GTK_IS_CONTAINER (w))
476     gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
477 }
478
479 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
480 void
481 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
482 {
483   gchar *font_name;
484   set_font_recursively (GTK_WIDGET (de), font_desc);
485
486   if (de->font)
487     pango_font_description_free (de->font);
488   de->font = pango_font_description_copy (font_desc);
489   font_name = pango_font_description_to_string (de->font);
490
491   psppire_conf_set_string (psppire_conf_new (),
492                            "Data Editor", "font",
493                            font_name);
494
495 }
496
497 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
498    If SPLIT is FALSE, un-splits it into a single pane. */
499 void
500 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
501 {
502   GtkTreeViewGridLines grid_lines;
503   gboolean labels;
504
505   if (split == de->split)
506     return;
507
508
509
510   disconnect_data_sheets (de);
511
512   psppire_data_editor_refresh_model (de);
513
514   gtk_widget_show_all (de->vbox);
515
516   if (de->font)
517     set_font_recursively (GTK_WIDGET (de), de->font);
518
519   de->split = split;
520   g_object_notify (G_OBJECT (de), "split");
521   psppire_data_editor_update_ui_manager (de);
522 }
523
524 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
525    visible and selected in the active view in DE. */
526 void
527 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
528 {
529
530   switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
531     {
532
533     case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
534       psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
535                                        dict_index);
536       break;
537     }
538 }
539
540
541 /* Returns the UI manager that should be merged into DE's toplevel widget's UI
542    manager to display menu items and toolbar items specific to DE's current
543    page and data sheet.
544
545    DE's toplevel widget can watch for changes by connecting to DE's
546    notify::ui-manager signal. */
547 GtkUIManager *
548 psppire_data_editor_get_ui_manager (PsppireDataEditor *de)
549 {
550   psppire_data_editor_update_ui_manager (de);
551   return de->ui_manager;
552 }
553
554 static void
555 psppire_data_editor_update_ui_manager (PsppireDataEditor *de)
556 {
557   GtkUIManager *ui_manager;
558
559   ui_manager = NULL;
560
561   switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
562     {
563     case PSPPIRE_DATA_EDITOR_DATA_VIEW:
564       break;
565
566     case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
567       ui_manager = psppire_var_sheet_get_ui_manager (
568         PSPPIRE_VAR_SHEET (de->var_sheet));
569       break;
570
571     default:
572       /* This happens transiently in psppire_data_editor_init(). */
573       break;
574     }
575
576   if (ui_manager != de->ui_manager)
577     {
578       if (de->ui_manager)
579         g_object_unref (de->ui_manager);
580       if (ui_manager)
581         g_object_ref (ui_manager);
582       de->ui_manager = ui_manager;
583
584       g_object_notify (G_OBJECT (de), "ui-manager");
585     }
586 }
587
588