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