psppire-data-editor: Unref old data_window upon change.
[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 #include <gtk/gtk.h>
19 #include <gtk-contrib/gtkextra-sheet.h>
20 #include "psppire-data-editor.h"
21 #include "psppire-var-sheet.h"
22 #include "psppire.h"
23
24 #include "psppire-data-store.h"
25 #include <libpspp/i18n.h>
26 #include <ui/gui/sheet/psppire-axis.h>
27 #include "executor.h"
28
29 #include <gtk-contrib/gtkxpaned.h>
30 #include <gettext.h>
31 #define _(msgid) gettext (msgid)
32 #define N_(msgid) msgid
33
34
35 static void psppire_data_editor_remove_split (PsppireDataEditor *de);
36 static void psppire_data_editor_set_split (PsppireDataEditor *de);
37
38 enum {
39   DATA_SELECTION_CHANGED,
40   DATA_AVAILABLE_CHANGED,
41   CASES_SELECTED,
42   VARIABLES_SELECTED,
43   n_SIGNALS
44 };
45
46
47 static guint data_editor_signals [n_SIGNALS] = { 0 };
48
49
50 static gboolean data_is_selected (PsppireDataEditor *de);
51
52 static void psppire_data_editor_class_init          (PsppireDataEditorClass *klass);
53 static void psppire_data_editor_init                (PsppireDataEditor      *de);
54
55 GType
56 psppire_data_editor_get_type (void)
57 {
58   static GType de_type = 0;
59
60   if (!de_type)
61     {
62       static const GTypeInfo de_info =
63       {
64         sizeof (PsppireDataEditorClass),
65         NULL, /* base_init */
66         NULL, /* base_finalize */
67         (GClassInitFunc) psppire_data_editor_class_init,
68         NULL, /* class_finalize */
69         NULL, /* class_data */
70         sizeof (PsppireDataEditor),
71         0,
72         (GInstanceInitFunc) psppire_data_editor_init,
73       };
74
75       de_type = g_type_register_static (GTK_TYPE_NOTEBOOK, "PsppireDataEditor",
76                                         &de_info, 0);
77     }
78
79   return de_type;
80 }
81
82 static GObjectClass * parent_class = NULL;
83
84 static void
85 psppire_data_editor_dispose (GObject *obj)
86 {
87   PsppireDataEditor *de = (PsppireDataEditor *) obj;
88
89   if (de->dispose_has_run)
90     return;
91
92   g_object_unref (de->data_window);
93   g_object_unref (de->data_store);
94   g_object_unref (de->var_store);
95
96   /* Make sure dispose does not run twice. */
97   de->dispose_has_run = TRUE;
98
99   /* Chain up to the parent class */
100   G_OBJECT_CLASS (parent_class)->dispose (obj);
101 }
102
103 static void
104 psppire_data_editor_finalize (GObject *obj)
105 {
106    /* Chain up to the parent class */
107    G_OBJECT_CLASS (parent_class)->finalize (obj);
108 }
109
110
111
112 static void popup_variable_column_menu (PsppireSheet *sheet, gint column,
113                                         GdkEventButton *event, gpointer data);
114
115 static void popup_variable_row_menu (PsppireSheet *sheet, gint row,
116                                      GdkEventButton *event, gpointer data);
117
118
119 static void popup_cases_menu (PsppireSheet *sheet, gint row,
120                               GdkEventButton *event, gpointer data);
121
122
123
124
125
126 /* Callback which occurs when the data sheet's column title
127    is double clicked */
128 static gboolean
129 on_data_column_clicked (PsppireDataEditor *de, gint col, gpointer data)
130 {
131   PsppireSheetRange visible_range;
132   gint current_row, current_column;
133
134   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
135                                  PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
136
137   psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->var_sheet),
138                              &current_row, &current_column);
139
140   psppire_sheet_set_active_cell (PSPPIRE_SHEET (de->var_sheet), col, current_column);
141
142
143   psppire_sheet_get_visible_range (PSPPIRE_SHEET (de->var_sheet), &visible_range);
144
145   if ( col < visible_range.row0 || col > visible_range.rowi)
146     psppire_sheet_moveto (PSPPIRE_SHEET (de->var_sheet), col, current_column, 0.5, 0.5);
147
148
149   return FALSE;
150 }
151
152
153 /* Callback which occurs when the var sheet's row title
154    button is double clicked */
155 static gboolean
156 on_var_row_clicked (PsppireDataEditor *de, gint row, gpointer data)
157 {
158   PsppireSheetRange visible_range;
159
160   gint current_row, current_column;
161
162   gtk_notebook_set_current_page (GTK_NOTEBOOK(de), PSPPIRE_DATA_EDITOR_DATA_VIEW);
163
164   psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->data_sheet[0]),
165                              &current_row, &current_column);
166
167   psppire_sheet_set_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), current_row, row);
168
169   psppire_sheet_get_visible_range (PSPPIRE_SHEET (de->data_sheet[0]), &visible_range);
170
171   if ( row < visible_range.col0 || row > visible_range.coli)
172     psppire_sheet_moveto (PSPPIRE_SHEET (de->data_sheet[0]), -1, row, 0.5, 0.5);
173
174   return FALSE;
175 }
176
177
178 /* Moves the focus to a new cell.
179    Returns TRUE iff the move should be disallowed */
180 static gboolean
181 traverse_cell_callback (PsppireSheet *sheet,
182                         PsppireSheetCell *existing_cell,
183                         PsppireSheetCell *new_cell,
184                         gpointer data)
185 {
186   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
187   const PsppireDict *dict = de->data_store->dict;
188
189   if ( new_cell->col >= psppire_dict_get_var_cnt (dict))
190     return TRUE;
191
192   return FALSE;
193 }
194
195
196 enum
197   {
198     PROP_0,
199     PROP_DATA_WINDOW,
200     PROP_DATA_STORE,
201     PROP_VAR_STORE,
202     PROP_VS_ROW_MENU,
203     PROP_DS_COLUMN_MENU,
204     PROP_DS_ROW_MENU,
205     PROP_VALUE_LABELS,
206     PROP_CURRENT_CASE,
207     PROP_CURRENT_VAR,
208     PROP_DATA_SELECTED,
209     PROP_SPLIT_WINDOW
210   };
211
212
213 #define DEFAULT_ROW_HEIGHT 25
214
215 static void
216 new_data_callback (PsppireDataStore *ds, gpointer data)
217 {
218   gint i;
219   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
220
221   casenumber n_cases =  psppire_data_store_get_case_count (ds);
222
223   for (i = 0; i < 2; ++i)
224     {
225       psppire_axis_clear (de->vaxis[i]);
226       psppire_axis_append_n (de->vaxis[i], n_cases, DEFAULT_ROW_HEIGHT);
227     }
228
229   /* All of the data (potentially) changed, so unselect any selected cell(s) in
230      the data sheets.  If we don't do this, then the sheet remembers the value
231      that was in the selected cell and stores it back, wiping out whatever
232      value there is in the new data.  Bug #30502. */
233   if (de->data_sheet[0] != NULL)
234     psppire_sheet_unselect_range (PSPPIRE_SHEET (de->data_sheet[0]));
235 }
236
237 static void
238 case_inserted_callback (PsppireDataStore *ds, gint before, gpointer data)
239 {
240   gint i;
241   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
242
243   for (i = 0; i < 2; ++i)
244     psppire_axis_insert (de->vaxis[i], before, DEFAULT_ROW_HEIGHT);
245 }
246
247
248 static void
249 cases_deleted_callback (PsppireDataStore *ds, gint first, gint n_cases, gpointer data)
250 {
251   gint i;
252   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
253
254   for (i = 0; i < 2; ++i)
255     psppire_axis_delete (de->vaxis[0], first, n_cases);
256 }
257
258
259
260 /* Return the width (in pixels) of an upper case M when rendered in the
261    current font of W
262 */
263 static gint
264 width_of_m (GtkWidget *w)
265 {
266   PangoRectangle rect;
267   PangoLayout *layout = gtk_widget_create_pango_layout (w, "M");
268
269   pango_layout_get_pixel_extents (layout, NULL, &rect);
270
271   g_object_unref (layout);
272
273   return rect.width;
274 }
275
276 /* Callback for the axis' resize signal.
277    Changes the variable's display width */
278 static void
279 rewidth_variable (GtkWidget *w, gint unit, glong size)
280 {
281   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (w);
282
283   const PsppireDict *dict = de->data_store->dict;
284   struct variable *var = psppire_dict_get_variable (dict, unit);
285
286   if (NULL == var)
287     return;
288
289   var_set_display_width (var, size / (float) width_of_m (w));
290 }
291
292
293 static void
294 new_variables_callback (PsppireDict *dict, gpointer data)
295 {
296   gint v;
297   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
298   gint m_width = width_of_m (GTK_WIDGET (de));
299
300   PsppireAxis *vaxis;
301   g_object_get (de->var_sheet, "vertical-axis", &vaxis, NULL);
302
303   psppire_axis_clear (vaxis);
304   psppire_axis_append_n (vaxis, 1 + psppire_dict_get_var_cnt (dict), DEFAULT_ROW_HEIGHT);
305
306   g_signal_connect_swapped (de->haxis, "resize-unit",
307                             G_CALLBACK (rewidth_variable), de);
308
309   psppire_axis_clear (de->haxis);
310
311   for (v = 0 ; v < psppire_dict_get_var_cnt (dict); ++v)
312     {
313       const struct variable *var = psppire_dict_get_variable (dict, v);
314
315       psppire_axis_append (de->haxis, m_width * var_get_display_width (var));
316     }
317 }
318
319 static void
320 insert_variable_callback (PsppireDict *dict, gint x, gpointer data)
321 {
322   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
323
324   gint m_width  = width_of_m (GTK_WIDGET (de));
325
326   PsppireAxis *var_vaxis;
327
328   const struct variable *var = psppire_dict_get_variable (dict, x);
329
330   g_object_get (de->var_sheet, "vertical-axis", &var_vaxis, NULL);
331
332   psppire_axis_insert (var_vaxis, x, DEFAULT_ROW_HEIGHT);
333
334
335   psppire_axis_insert (de->haxis, x, m_width * var_get_display_width (var));
336 }
337
338
339 static void
340 delete_variable_callback (PsppireDict *dict,
341                           const struct variable *var UNUSED,
342                           gint dict_idx, gint case_idx UNUSED, gpointer data)
343 {
344   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
345
346   PsppireAxis *var_vaxis;
347   g_object_get (de->var_sheet, "vertical-axis", &var_vaxis, NULL);
348
349   psppire_axis_delete (var_vaxis, dict_idx, 1);
350
351   psppire_axis_delete (de->haxis, dict_idx, 1);
352 }
353
354
355 static void
356 rewidth_variable_callback (PsppireDict *dict, gint posn, gpointer data)
357 {
358   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
359   gint m_width = width_of_m (GTK_WIDGET (de));
360
361   const struct variable *var = psppire_dict_get_variable (dict, posn);
362
363   gint var_width = var_get_display_width (var);
364
365   /* Don't allow zero width */
366   if ( var_width < 1 )
367     var_width = 1;
368
369   psppire_axis_resize (de->haxis, posn, m_width * var_width);
370 }
371
372
373 static void
374 psppire_data_editor_set_property (GObject         *object,
375                                   guint            prop_id,
376                                   const GValue    *value,
377                                   GParamSpec      *pspec)
378 {
379   int i;
380   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
381
382   switch (prop_id)
383     {
384     case PROP_SPLIT_WINDOW:
385       psppire_data_editor_split_window (de, g_value_get_boolean (value));
386       break;
387     case PROP_DATA_WINDOW:
388       if (de->data_window)
389         g_object_unref (de->data_window);
390       de->data_window = g_value_get_pointer (value);
391       g_object_ref (de->data_window);
392       break;
393     case PROP_DATA_STORE:
394       if ( de->data_store) g_object_unref (de->data_store);
395       de->data_store = g_value_get_pointer (value);
396       g_object_ref (de->data_store);
397
398       for (i = 0 ; i < 4 ; ++i )
399         {
400           g_object_set (de->data_sheet[i],
401                         "model", de->data_store,
402                         NULL);
403
404           g_signal_connect_swapped (de->data_store->dict, "filter-changed",
405                                     G_CALLBACK (gtk_widget_queue_draw),
406                                     de->data_sheet[i]);
407         }
408
409       g_signal_connect (de->data_store->dict, "backend-changed",
410                         G_CALLBACK (new_variables_callback), de);
411
412       g_signal_connect (de->data_store->dict, "variable-inserted",
413                         G_CALLBACK (insert_variable_callback), de);
414
415       g_signal_connect (de->data_store->dict, "variable-deleted",
416                         G_CALLBACK (delete_variable_callback), de);
417
418       g_signal_connect (de->data_store->dict, "variable-display-width-changed",
419                         G_CALLBACK (rewidth_variable_callback), de);
420
421       g_signal_connect (de->data_store, "backend-changed",
422                         G_CALLBACK (new_data_callback), de);
423
424       g_signal_connect (de->data_store, "case-inserted",
425                         G_CALLBACK (case_inserted_callback), de);
426
427       g_signal_connect (de->data_store, "cases-deleted",
428                         G_CALLBACK (cases_deleted_callback), de);
429
430       break;
431     case PROP_VAR_STORE:
432       if ( de->var_store) g_object_unref (de->var_store);
433       de->var_store = g_value_get_pointer (value);
434       g_object_ref (de->var_store);
435
436       g_object_set (de->var_sheet,
437                     "model", de->var_store,
438                     NULL);
439       break;
440     case PROP_VS_ROW_MENU:
441       {
442         GObject *menu = g_value_get_object (value);
443
444         g_signal_connect (de->var_sheet, "button-event-row",
445                           G_CALLBACK (popup_variable_row_menu), menu);
446       }
447       break;
448     case PROP_DS_COLUMN_MENU:
449       {
450         GObject *menu = g_value_get_object (value);
451
452         g_signal_connect (de->data_sheet[0], "button-event-column",
453                           G_CALLBACK (popup_variable_column_menu), menu);
454       }
455       break;
456     case PROP_DS_ROW_MENU:
457       {
458         GObject *menu = g_value_get_object (value);
459
460         g_signal_connect (de->data_sheet[0], "button-event-row",
461                           G_CALLBACK (popup_cases_menu), menu);
462       }
463       break;
464     case PROP_CURRENT_VAR:
465       {
466         gint row, col;
467         gint var = g_value_get_long (value);
468         switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (object)))
469           {
470           case PSPPIRE_DATA_EDITOR_DATA_VIEW:
471             psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), &row, &col);
472             psppire_sheet_set_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), row, var);
473             psppire_sheet_moveto (PSPPIRE_SHEET (de->data_sheet[0]), -1, var, 0.5, 0.5);
474             break;
475           case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
476             psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->var_sheet), &row, &col);
477             psppire_sheet_set_active_cell (PSPPIRE_SHEET (de->var_sheet), var, col);
478             psppire_sheet_moveto (PSPPIRE_SHEET (de->var_sheet), var, -1,  0.5, 0.5);
479             break;
480           default:
481             g_assert_not_reached ();
482             break;
483           };
484       }
485       break;
486     case PROP_CURRENT_CASE:
487       {
488         gint row, col;
489         gint case_num = g_value_get_long (value);
490         psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), &row, &col);
491         psppire_sheet_set_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), case_num, col);
492         psppire_sheet_moveto (PSPPIRE_SHEET (de->data_sheet[0]), case_num, -1, 0.5, 0.5);
493       }
494       break;
495     case PROP_VALUE_LABELS:
496       {
497         psppire_data_store_show_labels (de->data_store,
498                                         g_value_get_boolean (value));
499       }
500       break;
501     default:
502       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
503       break;
504     };
505 }
506
507 static void
508 psppire_data_editor_get_property (GObject         *object,
509                                   guint            prop_id,
510                                   GValue          *value,
511                                   GParamSpec      *pspec)
512 {
513   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
514
515   switch (prop_id)
516     {
517     case PROP_SPLIT_WINDOW:
518       g_value_set_boolean (value, de->split);
519       break;
520     case PROP_DATA_WINDOW:
521       g_value_set_pointer (value, de->data_window);
522       break;
523     case PROP_DATA_STORE:
524       g_value_set_pointer (value, de->data_store);
525       break;
526     case PROP_VAR_STORE:
527       g_value_set_pointer (value, de->var_store);
528       break;
529     case PROP_CURRENT_CASE:
530       {
531         gint row, column;
532         psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), &row, &column);
533         g_value_set_long (value, row);
534       }
535       break;
536     case PROP_CURRENT_VAR:
537       {
538         gint row, column;
539         psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), &row, &column);
540         g_value_set_long (value, column);
541       }
542       break;
543     case PROP_DATA_SELECTED:
544       g_value_set_boolean (value, data_is_selected (de));
545       break;
546     default:
547       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
548       break;
549     };
550 }
551
552
553 static void
554 psppire_data_editor_class_init (PsppireDataEditorClass *klass)
555 {
556   GParamSpec *data_window_spec ;
557   GParamSpec *data_store_spec ;
558   GParamSpec *var_store_spec ;
559   GParamSpec *column_menu_spec;
560   GParamSpec *ds_row_menu_spec;
561   GParamSpec *vs_row_menu_spec;
562   GParamSpec *value_labels_spec;
563   GParamSpec *current_case_spec;
564   GParamSpec *current_var_spec;
565   GParamSpec *data_selected_spec;
566   GParamSpec *split_window_spec;
567   GObjectClass *object_class = G_OBJECT_CLASS (klass);
568
569   parent_class = g_type_class_peek_parent (klass);
570
571   object_class->dispose = psppire_data_editor_dispose;
572   object_class->finalize = psppire_data_editor_finalize;
573
574   object_class->set_property = psppire_data_editor_set_property;
575   object_class->get_property = psppire_data_editor_get_property;
576
577   
578
579   data_window_spec =
580     g_param_spec_pointer ("data-window",
581                           "Data Window",
582                           "A pointer to the data window associated with this editor",
583                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
584
585   g_object_class_install_property (object_class,
586                                    PROP_DATA_WINDOW,
587                                    data_window_spec);
588
589   data_store_spec =
590     g_param_spec_pointer ("data-store",
591                           "Data Store",
592                           "A pointer to the data store associated with this editor",
593                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
594
595   g_object_class_install_property (object_class,
596                                    PROP_DATA_STORE,
597                                    data_store_spec);
598
599   var_store_spec =
600     g_param_spec_pointer ("var-store",
601                           "Variable Store",
602                           "A pointer to the variable store associated with this editor",
603                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
604
605   g_object_class_install_property (object_class,
606                                    PROP_VAR_STORE,
607                                    var_store_spec);
608
609   column_menu_spec =
610     g_param_spec_object ("datasheet-column-menu",
611                          "Data Sheet Column Menu",
612                          "A menu to be displayed when button 3 is pressed in thedata sheet's column title buttons",
613                          GTK_TYPE_MENU,
614                          G_PARAM_WRITABLE);
615
616   g_object_class_install_property (object_class,
617                                    PROP_DS_COLUMN_MENU,
618                                    column_menu_spec);
619
620
621   ds_row_menu_spec =
622     g_param_spec_object ("datasheet-row-menu",
623                          "Data Sheet Row Menu",
624                          "A menu to be displayed when button 3 is pressed in the data sheet's row title buttons",
625                          GTK_TYPE_MENU,
626                          G_PARAM_WRITABLE);
627
628   g_object_class_install_property (object_class,
629                                    PROP_DS_ROW_MENU,
630                                    ds_row_menu_spec);
631
632
633   vs_row_menu_spec =
634     g_param_spec_object ("varsheet-row-menu",
635                          "Variable Sheet Row Menu",
636                          "A menu to be displayed when button 3 is pressed in the variable sheet's row title buttons",
637                          GTK_TYPE_MENU,
638                          G_PARAM_WRITABLE);
639
640   g_object_class_install_property (object_class,
641                                    PROP_VS_ROW_MENU,
642                                    vs_row_menu_spec);
643
644
645   value_labels_spec =
646     g_param_spec_boolean ("value-labels",
647                          "Value Labels",
648                          "Whether or not the data sheet should display labels instead of values",
649                           FALSE,
650                          G_PARAM_WRITABLE | G_PARAM_READABLE);
651
652   g_object_class_install_property (object_class,
653                                    PROP_VALUE_LABELS,
654                                    value_labels_spec);
655
656
657   current_case_spec =
658     g_param_spec_long ("current-case",
659                        "Current Case",
660                        "Zero based number of the selected case",
661                        0, CASENUMBER_MAX,
662                        0,
663                        G_PARAM_WRITABLE | G_PARAM_READABLE);
664
665   g_object_class_install_property (object_class,
666                                    PROP_CURRENT_CASE,
667                                    current_case_spec);
668
669
670   current_var_spec =
671     g_param_spec_long ("current-variable",
672                        "Current Variable",
673                        "Zero based number of the selected variable",
674                        0, G_MAXINT,
675                        0,
676                        G_PARAM_WRITABLE | G_PARAM_READABLE);
677
678   g_object_class_install_property (object_class,
679                                    PROP_CURRENT_VAR,
680                                    current_var_spec);
681
682
683   data_selected_spec =
684     g_param_spec_boolean ("data-selected",
685                           "Data Selected",
686                           "True iff the data view is active and  one or more cells of data have been selected.",
687                           FALSE,
688                           G_PARAM_READABLE);
689
690   g_object_class_install_property (object_class,
691                                    PROP_DATA_SELECTED,
692                                    data_selected_spec);
693
694
695
696   split_window_spec =
697     g_param_spec_boolean ("split",
698                           "Split Window",
699                           "True iff the data sheet is split",
700                           FALSE,
701                           G_PARAM_READABLE | G_PARAM_WRITABLE);
702
703   g_object_class_install_property (object_class,
704                                    PROP_SPLIT_WINDOW,
705                                    split_window_spec);
706
707   data_editor_signals [DATA_SELECTION_CHANGED] =
708     g_signal_new ("data-selection-changed",
709                   G_TYPE_FROM_CLASS (klass),
710                   G_SIGNAL_RUN_FIRST,
711                   0,
712                   NULL, NULL,
713                   g_cclosure_marshal_VOID__BOOLEAN,
714                   G_TYPE_NONE,
715                   1,
716                   G_TYPE_BOOLEAN);
717
718   data_editor_signals [CASES_SELECTED] =
719     g_signal_new ("cases-selected",
720                   G_TYPE_FROM_CLASS (klass),
721                   G_SIGNAL_RUN_FIRST,
722                   0,
723                   NULL, NULL,
724                   g_cclosure_marshal_VOID__INT,
725                   G_TYPE_NONE,
726                   1,
727                   G_TYPE_INT);
728
729
730   data_editor_signals [VARIABLES_SELECTED] =
731     g_signal_new ("variables-selected",
732                   G_TYPE_FROM_CLASS (klass),
733                   G_SIGNAL_RUN_FIRST,
734                   0,
735                   NULL, NULL,
736                   g_cclosure_marshal_VOID__INT,
737                   G_TYPE_NONE,
738                   1,
739                   G_TYPE_INT);
740
741
742   data_editor_signals [DATA_AVAILABLE_CHANGED] =
743     g_signal_new ("data-available-changed",
744                   G_TYPE_FROM_CLASS (klass),
745                   G_SIGNAL_RUN_FIRST,
746                   0,
747                   NULL, NULL,
748                   g_cclosure_marshal_VOID__BOOLEAN,
749                   G_TYPE_NONE,
750                   1,
751                   G_TYPE_BOOLEAN);
752 }
753
754 /* Update the data_ref_entry with the reference of the active cell */
755 static gint
756 update_data_ref_entry (const PsppireSheet *sheet,
757                        gint row, gint col,
758                        gint old_row, gint old_col,
759                        gpointer data)
760 {
761   PsppireDataEditor *de = data;
762
763   PsppireDataStore *data_store =
764     PSPPIRE_DATA_STORE (psppire_sheet_get_model (sheet));
765
766   if (data_store)
767     {
768       const struct variable *var =
769         psppire_dict_get_variable (data_store->dict, col);
770
771       if ( var )
772         {
773           gchar *text = g_strdup_printf ("%d: %s", row + FIRST_CASE_NUMBER,
774                                          var_get_name (var));
775
776
777           gtk_entry_set_text (GTK_ENTRY (de->cell_ref_entry), text);
778
779           g_free (text);
780         }
781       else
782         goto blank_entry;
783
784       if ( var )
785         {
786           gchar *text =
787             psppire_data_store_get_string (data_store, row,
788                                            var_get_dict_index(var));
789
790           if ( ! text )
791             goto blank_entry;
792
793           g_strchug (text);
794
795           gtk_entry_set_text (GTK_ENTRY (de->datum_entry), text);
796
797           g_free (text);
798         }
799       else
800         goto blank_entry;
801
802     }
803
804   return FALSE;
805
806  blank_entry:
807   gtk_entry_set_text (GTK_ENTRY (de->datum_entry), "");
808
809   return FALSE;
810 }
811
812
813 static void
814 datum_entry_activate (GtkEntry *entry, gpointer data)
815 {
816   gint row, column;
817   PsppireDataEditor *de = data;
818
819   const gchar *text = gtk_entry_get_text (entry);
820
821   psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), &row, &column);
822
823   if ( row == -1 || column == -1)
824     return;
825
826   psppire_data_store_set_string (de->data_store, text, row, column);
827 }
828
829 static void on_activate (PsppireDataEditor *de);
830 static gboolean on_switch_page (PsppireDataEditor *de, GtkNotebookPage *p, gint pagenum, gpointer data);
831 static void on_select_range (PsppireDataEditor *de);
832
833 static void on_select_row (PsppireSheet *, gint, PsppireDataEditor *);
834 static void on_select_variable (PsppireSheet *, gint, PsppireDataEditor *);
835
836
837 static void on_owner_change (GtkClipboard *,
838                              GdkEventOwnerChange *, gpointer);
839
840 static void
841 on_map (GtkWidget *w)
842 {
843   GtkClipboard *clip = gtk_widget_get_clipboard (w, GDK_SELECTION_CLIPBOARD);
844
845   g_signal_connect (clip, "owner-change", G_CALLBACK (on_owner_change), w);
846 }
847
848
849 static void
850 init_sheet (PsppireDataEditor *de, int i,
851             GtkAdjustment *hadj, GtkAdjustment *vadj,
852             PsppireAxis *vaxis,
853             PsppireAxis *haxis
854             )
855 {
856   de->sheet_bin[i] = gtk_scrolled_window_new (hadj, vadj);
857
858   de->data_sheet[i] = psppire_sheet_new (NULL);
859
860   g_object_set (de->sheet_bin[i],
861                 "border-width", 3,
862                 "shadow-type",  GTK_SHADOW_ETCHED_IN,
863                 NULL);
864
865   g_object_set (haxis, "default-size", 75, NULL);
866   g_object_set (vaxis, "default-size", 25, NULL);
867
868   g_object_set (de->data_sheet[i],
869                 "horizontal-axis", haxis,
870                 "vertical-axis", vaxis,
871                 NULL);
872
873   gtk_container_add (GTK_CONTAINER (de->sheet_bin[i]), de->data_sheet[i]);
874
875   g_signal_connect (de->data_sheet[i], "traverse",
876                     G_CALLBACK (traverse_cell_callback), de);
877
878   gtk_widget_show (de->sheet_bin[i]);
879 }
880
881
882 static void
883 init_data_sheet (PsppireDataEditor *de)
884 {
885   GtkAdjustment *vadj0, *hadj0;
886   GtkAdjustment *vadj1, *hadj1;
887   GtkWidget *sheet ;
888
889   de->vaxis[0] = psppire_axis_new ();
890   de->vaxis[1] = psppire_axis_new ();
891
892   /* There's only one horizontal axis, since the
893      column widths are parameters of the variables */
894   de->haxis = psppire_axis_new ();
895
896   de->split = TRUE;
897   de->paned = gtk_xpaned_new ();
898
899   init_sheet (de, 0, NULL, NULL, de->vaxis[0], de->haxis);
900   gtk_widget_show (de->sheet_bin[0]);
901   vadj0 = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (de->sheet_bin[0]));
902   hadj0 = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (de->sheet_bin[0]));
903
904   g_object_set (de->sheet_bin[0], "vscrollbar-policy", GTK_POLICY_NEVER, NULL);
905   g_object_set (de->sheet_bin[0], "hscrollbar-policy", GTK_POLICY_NEVER, NULL);
906
907   init_sheet (de, 1, NULL, vadj0, de->vaxis[0], de->haxis);
908   gtk_widget_show (de->sheet_bin[1]);
909   sheet = gtk_bin_get_child (GTK_BIN (de->sheet_bin[1]));
910   psppire_sheet_hide_row_titles (PSPPIRE_SHEET (sheet));
911   hadj1 = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (de->sheet_bin[1]));
912   g_object_set (de->sheet_bin[1], "vscrollbar-policy", GTK_POLICY_ALWAYS, NULL);
913   g_object_set (de->sheet_bin[1], "hscrollbar-policy", GTK_POLICY_NEVER, NULL);
914
915   init_sheet (de, 2, hadj0, NULL, de->vaxis[1], de->haxis);
916   gtk_widget_show (de->sheet_bin[2]);
917   sheet = gtk_bin_get_child (GTK_BIN (de->sheet_bin[2]));
918   psppire_sheet_hide_column_titles (PSPPIRE_SHEET (sheet));
919   g_object_set (de->sheet_bin[2], "vscrollbar-policy", GTK_POLICY_NEVER, NULL);
920   g_object_set (de->sheet_bin[2], "hscrollbar-policy", GTK_POLICY_ALWAYS, NULL);
921   vadj1 = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (de->sheet_bin[2]));
922
923   init_sheet (de, 3, hadj1, vadj1, de->vaxis[1], de->haxis);
924   gtk_widget_show (de->sheet_bin[3]);
925   sheet = gtk_bin_get_child (GTK_BIN (de->sheet_bin[3]));
926   psppire_sheet_hide_column_titles (PSPPIRE_SHEET (sheet));
927   psppire_sheet_hide_row_titles (PSPPIRE_SHEET (sheet));
928   g_object_set (de->sheet_bin[3], "vscrollbar-policy", GTK_POLICY_ALWAYS, NULL);
929   g_object_set (de->sheet_bin[3], "hscrollbar-policy", GTK_POLICY_ALWAYS, NULL);
930
931   gtk_xpaned_pack_top_left (GTK_XPANED (de->paned), de->sheet_bin[0], TRUE, TRUE);
932   gtk_xpaned_pack_top_right (GTK_XPANED (de->paned), de->sheet_bin[1], TRUE, TRUE);
933   gtk_xpaned_pack_bottom_left (GTK_XPANED (de->paned), de->sheet_bin[2], TRUE, TRUE);
934   gtk_xpaned_pack_bottom_right (GTK_XPANED (de->paned), de->sheet_bin[3], TRUE, TRUE);
935
936   gtk_xpaned_set_position_y (GTK_XPANED (de->paned), 150);
937   gtk_xpaned_set_position_x (GTK_XPANED (de->paned), 350);
938 }
939
940
941 static void
942 psppire_data_editor_init (PsppireDataEditor *de)
943 {
944   GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
945   GtkWidget *sw_vs = gtk_scrolled_window_new (NULL, NULL);
946
947   init_data_sheet (de);
948
949   de->data_vbox = gtk_vbox_new (FALSE, 0);
950   de->var_sheet = psppire_var_sheet_new ();
951
952   g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
953
954   de->datum_entry = gtk_entry_new ();
955   de->cell_ref_entry = gtk_entry_new ();
956
957   g_object_set (de->cell_ref_entry,
958                 "sensitive", FALSE,
959                 "editable",  FALSE,
960                 "width_chars", 25,
961                 NULL);
962
963   gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_entry, FALSE, FALSE, 0);
964   gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
965
966
967   gtk_container_add (GTK_CONTAINER (sw_vs), de->var_sheet);
968   gtk_widget_show_all (sw_vs);
969
970
971   gtk_box_pack_start (GTK_BOX (de->data_vbox), hbox, FALSE, FALSE, 0);
972   gtk_box_pack_start (GTK_BOX (de->data_vbox), de->paned, TRUE, TRUE, 0);
973
974
975   psppire_data_editor_remove_split (de);
976
977   gtk_widget_show_all (de->data_vbox);
978
979   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->data_vbox,
980                             gtk_label_new_with_mnemonic (_("Data View")));
981
982   gtk_notebook_append_page (GTK_NOTEBOOK (de), sw_vs,
983                             gtk_label_new_with_mnemonic (_("Variable View")));
984
985   g_signal_connect (de->data_sheet[0], "activate",
986                     G_CALLBACK (update_data_ref_entry),
987                     de);
988
989   g_signal_connect (de->datum_entry, "activate",
990                     G_CALLBACK (datum_entry_activate),
991                     de);
992
993
994   g_signal_connect_swapped (de->data_sheet[0],
995                     "double-click-column",
996                     G_CALLBACK (on_data_column_clicked),
997                     de);
998
999   g_signal_connect_swapped (de->var_sheet,
1000                     "double-click-row",
1001                     G_CALLBACK (on_var_row_clicked),
1002                     de);
1003
1004   g_signal_connect_swapped (de->data_sheet[0], "activate",
1005                             G_CALLBACK (on_activate),
1006                             de);
1007
1008   g_signal_connect_swapped (de->data_sheet[0], "select-range",
1009                             G_CALLBACK (on_select_range),
1010                             de);
1011
1012   g_signal_connect (de->data_sheet[0], "select-row",
1013                     G_CALLBACK (on_select_row), de);
1014
1015   g_signal_connect (de->data_sheet[0], "select-column",
1016                     G_CALLBACK (on_select_variable), de);
1017
1018
1019   g_signal_connect (de->var_sheet, "select-row",
1020                     G_CALLBACK (on_select_variable), de);
1021
1022
1023   g_signal_connect_after (de, "switch-page",
1024                     G_CALLBACK (on_switch_page),
1025                     NULL);
1026
1027   g_object_set (de, "can-focus", FALSE, NULL);
1028
1029   g_signal_connect (de, "map", G_CALLBACK (on_map), NULL);
1030
1031
1032   //     psppire_sheet_hide_column_titles (de->var_sheet);
1033   //  psppire_sheet_hide_row_titles (de->data_sheet);
1034
1035
1036   de->dispose_has_run = FALSE;
1037 }
1038
1039
1040 GtkWidget*
1041 psppire_data_editor_new (PsppireDataWindow *data_window,
1042                          PsppireVarStore *var_store,
1043                          PsppireDataStore *data_store)
1044 {
1045   return  g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
1046                         "data-window", data_window,
1047                         "var-store",  var_store,
1048                         "data-store",  data_store,
1049                         NULL);
1050 }
1051
1052
1053 static void
1054 psppire_data_editor_remove_split (PsppireDataEditor *de)
1055 {
1056   if ( !de->split ) return;
1057   de->split = FALSE;
1058
1059   g_object_ref (de->sheet_bin[0]);
1060   gtk_container_remove (GTK_CONTAINER (de->paned), de->sheet_bin[0]);
1061
1062   g_object_ref (de->paned);
1063   gtk_container_remove (GTK_CONTAINER (de->data_vbox), de->paned);
1064
1065   gtk_box_pack_start (GTK_BOX (de->data_vbox), de->sheet_bin[0],
1066                       TRUE, TRUE, 0);
1067
1068   g_object_unref (de->sheet_bin[0]);
1069
1070   g_object_set (de->sheet_bin[0], "vscrollbar-policy",
1071                 GTK_POLICY_ALWAYS, NULL);
1072
1073   g_object_set (de->sheet_bin[0], "hscrollbar-policy",
1074                 GTK_POLICY_ALWAYS, NULL);
1075 }
1076
1077
1078 static void
1079 psppire_data_editor_set_split (PsppireDataEditor *de)
1080 {
1081   if ( de->split ) return;
1082   de->split = TRUE;
1083
1084   g_object_ref (de->sheet_bin[0]);
1085   gtk_container_remove (GTK_CONTAINER (de->data_vbox), de->sheet_bin[0]);
1086
1087   gtk_xpaned_pack_top_left (GTK_XPANED (de->paned), de->sheet_bin [0],
1088                             TRUE, TRUE);
1089
1090   gtk_box_pack_start (GTK_BOX (de->data_vbox), de->paned,
1091                       TRUE, TRUE, 0);
1092
1093   g_object_unref (de->paned);
1094
1095   g_object_set (de->sheet_bin[0], "vscrollbar-policy",
1096                 GTK_POLICY_NEVER, NULL);
1097
1098   g_object_set (de->sheet_bin[0], "hscrollbar-policy",
1099                 GTK_POLICY_NEVER, NULL);
1100 }
1101
1102 void
1103 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
1104 {
1105   if (split )
1106     psppire_data_editor_set_split (de);
1107   else
1108     psppire_data_editor_remove_split (de);
1109
1110   gtk_widget_show_all (de->data_vbox);
1111 }
1112
1113 static void data_sheet_set_clip (PsppireSheet *sheet);
1114 static void data_sheet_contents_received_callback (GtkClipboard *clipboard,
1115                                                    GtkSelectionData *sd,
1116                                                    gpointer data);
1117
1118
1119 void
1120 psppire_data_editor_clip_copy (PsppireDataEditor *de)
1121 {
1122   data_sheet_set_clip (PSPPIRE_SHEET (de->data_sheet[0]));
1123 }
1124
1125 void
1126 psppire_data_editor_clip_paste (PsppireDataEditor *de)
1127 {
1128   GdkDisplay *display = gtk_widget_get_display ( GTK_WIDGET (de));
1129   GtkClipboard *clipboard =
1130     gtk_clipboard_get_for_display (display, GDK_SELECTION_CLIPBOARD);
1131
1132   gtk_clipboard_request_contents (clipboard,
1133                                   gdk_atom_intern ("UTF8_STRING", TRUE),
1134                                   data_sheet_contents_received_callback,
1135                                   de);
1136 }
1137
1138
1139
1140 void
1141 psppire_data_editor_clip_cut (PsppireDataEditor *de)
1142 {
1143   gint max_rows, max_columns;
1144   gint r;
1145   PsppireSheetRange range;
1146   PsppireDataStore *ds = de->data_store;
1147
1148   data_sheet_set_clip (PSPPIRE_SHEET (de->data_sheet[0]));
1149
1150   /* Now blank all the cells */
1151   psppire_sheet_get_selected_range (PSPPIRE_SHEET (de->data_sheet[0]), &range);
1152
1153    /* If nothing selected, then use active cell */
1154   if ( range.row0 < 0 || range.col0 < 0 )
1155     {
1156       gint row, col;
1157       psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), &row, &col);
1158
1159       range.row0 = range.rowi = row;
1160       range.col0 = range.coli = col;
1161     }
1162
1163   /* The sheet range can include cells that do not include data.
1164      Exclude them from the range. */
1165   max_rows = psppire_data_store_get_case_count (ds);
1166   if (range.rowi >= max_rows)
1167     {
1168       if (max_rows == 0)
1169         return;
1170       range.rowi = max_rows - 1;
1171     }
1172
1173   max_columns = dict_get_var_cnt (ds->dict->dict);
1174   if (range.coli >= max_columns)
1175     {
1176       if (max_columns == 0)
1177         return;
1178       range.coli = max_columns - 1;
1179     }
1180
1181   g_return_if_fail (range.rowi >= range.row0);
1182   g_return_if_fail (range.row0 >= 0);
1183   g_return_if_fail (range.coli >= range.col0);
1184   g_return_if_fail (range.col0 >= 0);
1185
1186
1187   for (r = range.row0; r <= range.rowi ; ++r )
1188     {
1189       gint c;
1190
1191       for (c = range.col0 ; c <= range.coli; ++c)
1192         {
1193           psppire_data_store_set_string (ds, "", r, c);
1194         }
1195     }
1196
1197   /* and remove the selection */
1198   psppire_sheet_unselect_range (PSPPIRE_SHEET (de->data_sheet[0]));
1199 }
1200
1201
1202 \f
1203
1204 /* Popup menu related stuff */
1205
1206 static void
1207 popup_variable_column_menu (PsppireSheet *sheet, gint column,
1208                      GdkEventButton *event, gpointer data)
1209 {
1210   GtkMenu *menu = GTK_MENU (data);
1211
1212   PsppireDataStore *data_store =
1213     PSPPIRE_DATA_STORE (psppire_sheet_get_model (sheet));
1214
1215   const struct variable *v =
1216     psppire_dict_get_variable (data_store->dict, column);
1217
1218   if ( v && event->button == 3)
1219     {
1220       psppire_sheet_select_column (sheet, column);
1221
1222       gtk_menu_popup (menu,
1223                       NULL, NULL, NULL, NULL,
1224                       event->button, event->time);
1225     }
1226 }
1227
1228
1229 static void
1230 popup_variable_row_menu (PsppireSheet *sheet, gint row,
1231                      GdkEventButton *event, gpointer data)
1232 {
1233   GtkMenu *menu = GTK_MENU (data);
1234
1235   PsppireVarStore *var_store =
1236     PSPPIRE_VAR_STORE (psppire_sheet_get_model (sheet));
1237   
1238   PsppireDict *dict;
1239   const struct variable *v ;
1240
1241   g_object_get (var_store, "dictionary", &dict, NULL);
1242
1243   v = psppire_dict_get_variable (dict, row);
1244
1245   if ( v && event->button == 3)
1246     {
1247       psppire_sheet_select_row (sheet, row);
1248
1249       gtk_menu_popup (menu,
1250                       NULL, NULL, NULL, NULL,
1251                       event->button, event->time);
1252     }
1253 }
1254
1255
1256 static void
1257 popup_cases_menu (PsppireSheet *sheet, gint row,
1258                   GdkEventButton *event, gpointer data)
1259 {
1260   GtkMenu *menu = GTK_MENU (data);
1261
1262   PsppireDataStore *data_store =
1263     PSPPIRE_DATA_STORE (psppire_sheet_get_model (sheet));
1264
1265   if ( row <= psppire_data_store_get_case_count (data_store) &&
1266        event->button == 3)
1267     {
1268       psppire_sheet_select_row (sheet, row);
1269
1270       gtk_menu_popup (menu,
1271                       NULL, NULL, NULL, NULL,
1272                       event->button, event->time);
1273     }
1274 }
1275
1276 \f
1277
1278 /* Sorting */
1279
1280 static void
1281 do_sort (PsppireDataEditor *de, int var, gboolean descend)
1282 {
1283   const struct variable *v
1284     = psppire_dict_get_variable (de->data_store->dict, var);
1285   gchar *syntax;
1286
1287   syntax = g_strdup_printf ("SORT CASES BY %s%s.",
1288                             var_get_name (v), descend ? " (D)" : "");
1289   g_free (execute_syntax_string (de->data_window, syntax));
1290 }
1291
1292
1293 /* Sort the data by the the variable which the editor has currently
1294    selected */
1295 void
1296 psppire_data_editor_sort_ascending  (PsppireDataEditor *de)
1297 {
1298   PsppireSheetRange range;
1299   psppire_sheet_get_selected_range (PSPPIRE_SHEET(de->data_sheet[0]), &range);
1300
1301   do_sort (de,  range.col0, FALSE);
1302 }
1303
1304
1305 /* Sort the data by the the variable which the editor has currently
1306    selected */
1307 void
1308 psppire_data_editor_sort_descending (PsppireDataEditor *de)
1309 {
1310   PsppireSheetRange range;
1311   psppire_sheet_get_selected_range (PSPPIRE_SHEET(de->data_sheet[0]), &range);
1312
1313   do_sort (de,  range.col0, TRUE);
1314 }
1315
1316
1317 \f
1318
1319
1320 /* Insert a new variable  before the currently selected position */
1321 void
1322 psppire_data_editor_insert_variable (PsppireDataEditor *de)
1323 {
1324   glong posn = 0;
1325
1326   switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
1327     {
1328     case PSPPIRE_DATA_EDITOR_DATA_VIEW:
1329       if ( PSPPIRE_SHEET (de->data_sheet[0])->select_status
1330            == PSPPIRE_SHEET_COLUMN_SELECTED )
1331         posn = PSPPIRE_SHEET (de->data_sheet[0])->range.col0;
1332       else
1333         posn = PSPPIRE_SHEET (de->data_sheet[0])->active_cell.col;
1334       break;
1335     case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
1336       if ( PSPPIRE_SHEET (de->var_sheet)->select_status
1337            == PSPPIRE_SHEET_ROW_SELECTED )
1338         posn = PSPPIRE_SHEET (de->var_sheet)->range.row0;
1339       else
1340         posn = PSPPIRE_SHEET (de->var_sheet)->active_cell.row;
1341       break;
1342     default:
1343       g_assert_not_reached ();
1344       break;
1345     };
1346
1347   psppire_dict_insert_variable (de->data_store->dict, posn, NULL);
1348 }
1349
1350 /* Insert a new case before the currently selected position */
1351 void
1352 psppire_data_editor_insert_case (PsppireDataEditor *de)
1353 {
1354   glong posn = -1;
1355
1356   if ( PSPPIRE_SHEET (de->data_sheet[0])->select_status == PSPPIRE_SHEET_ROW_SELECTED )
1357     {
1358       posn = PSPPIRE_SHEET (de->data_sheet[0])->range.row0;
1359     }
1360   else
1361     {
1362       posn = PSPPIRE_SHEET (de->data_sheet[0])->active_cell.row;
1363     }
1364
1365   if ( posn == -1 ) posn = 0;
1366
1367   psppire_data_store_insert_new_case (de->data_store, posn);
1368 }
1369
1370 /* Delete the cases currently selected in the data sheet */
1371 void
1372 psppire_data_editor_delete_cases    (PsppireDataEditor *de)
1373 {
1374   gint first = PSPPIRE_SHEET (de->data_sheet[0])->range.row0;
1375   gint n = PSPPIRE_SHEET (de->data_sheet[0])->range.rowi - first + 1;
1376
1377   psppire_data_store_delete_cases (de->data_store, first, n);
1378
1379   psppire_sheet_unselect_range (PSPPIRE_SHEET (de->data_sheet[0]));
1380 }
1381
1382 /* Delete the variables currently selected in the
1383    datasheet or variable sheet */
1384 void
1385 psppire_data_editor_delete_variables (PsppireDataEditor *de)
1386 {
1387   PsppireDict *dict = NULL;
1388   gint first, n;
1389
1390   switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
1391     {
1392     case PSPPIRE_DATA_EDITOR_DATA_VIEW:
1393       first = PSPPIRE_SHEET (de->data_sheet[0])->range.col0;
1394       n = PSPPIRE_SHEET (de->data_sheet[0])->range.coli - first + 1;
1395       break;
1396     case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
1397       first = PSPPIRE_SHEET (de->var_sheet)->range.row0;
1398       n = PSPPIRE_SHEET (de->var_sheet)->range.rowi - first + 1;
1399       break;
1400     default:
1401       g_assert_not_reached ();
1402       break;
1403     }
1404
1405   g_object_get (de->var_store, "dictionary", &dict, NULL);
1406
1407   psppire_dict_delete_variables (dict, first, n);
1408
1409   psppire_sheet_unselect_range (PSPPIRE_SHEET (de->data_sheet[0]));
1410   psppire_sheet_unselect_range (PSPPIRE_SHEET (de->var_sheet));
1411 }
1412
1413
1414 void
1415 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
1416 {
1417   psppire_sheet_show_grid (PSPPIRE_SHEET (de->var_sheet), grid_visible);
1418   psppire_sheet_show_grid (PSPPIRE_SHEET (de->data_sheet[0]), grid_visible);
1419 }
1420
1421
1422 static void
1423 set_font (GtkWidget *w, gpointer data)
1424 {
1425   PangoFontDescription *font_desc = data;
1426   GtkRcStyle *style = gtk_widget_get_modifier_style (w);
1427
1428   pango_font_description_free (style->font_desc);
1429   style->font_desc = pango_font_description_copy (font_desc);
1430
1431   gtk_widget_modify_style (w, style);
1432
1433   if ( GTK_IS_CONTAINER (w))
1434     gtk_container_foreach (GTK_CONTAINER (w), set_font, font_desc);
1435 }
1436
1437 void
1438 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
1439 {
1440   set_font (GTK_WIDGET (de), font_desc);
1441 }
1442
1443
1444 \f
1445
1446
1447 static void
1448 emit_selected_signal (PsppireDataEditor *de)
1449 {
1450   gboolean data_selected = data_is_selected (de);
1451
1452   g_signal_emit (de, data_editor_signals[DATA_SELECTION_CHANGED], 0, data_selected);
1453 }
1454
1455
1456 static void
1457 on_activate (PsppireDataEditor *de)
1458 {
1459   gint row, col;
1460   psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), &row, &col);
1461
1462
1463   if ( row < psppire_data_store_get_case_count (de->data_store)
1464        &&
1465        col < psppire_var_store_get_var_cnt (de->var_store))
1466     {
1467       emit_selected_signal (de);
1468       return ;
1469     }
1470
1471   emit_selected_signal (de);
1472 }
1473
1474
1475 static void
1476 on_select_range (PsppireDataEditor *de)
1477 {
1478   PsppireSheetRange range;
1479
1480   psppire_sheet_get_selected_range (PSPPIRE_SHEET (de->data_sheet[0]), &range);
1481
1482   if ( range.rowi < psppire_data_store_get_case_count (de->data_store)
1483        &&
1484        range.coli < psppire_var_store_get_var_cnt (de->var_store))
1485     {
1486       emit_selected_signal (de);
1487       return;
1488     }
1489
1490   emit_selected_signal (de);
1491 }
1492
1493
1494 static gboolean
1495 on_switch_page (PsppireDataEditor *de, GtkNotebookPage *p,
1496                 gint pagenum, gpointer data)
1497 {
1498   switch (pagenum)
1499     {
1500     case PSPPIRE_DATA_EDITOR_DATA_VIEW:
1501       gtk_widget_grab_focus (de->data_vbox);
1502       on_select_range (de);
1503       break;
1504     case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
1505       gtk_widget_grab_focus (de->var_sheet);
1506       emit_selected_signal (de);
1507       break;
1508     default:
1509       break;
1510     };
1511
1512   return TRUE;
1513 }
1514
1515
1516
1517 static gboolean
1518 data_is_selected (PsppireDataEditor *de)
1519 {
1520   PsppireSheetRange range;
1521   gint row, col;
1522
1523   if ( gtk_notebook_get_current_page (GTK_NOTEBOOK (de)) != PSPPIRE_DATA_EDITOR_DATA_VIEW)
1524     return FALSE;
1525
1526   psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), &row, &col);
1527
1528   if ( row >= psppire_data_store_get_case_count (de->data_store)
1529        ||
1530        col >= psppire_var_store_get_var_cnt (de->var_store))
1531     {
1532       return FALSE;
1533     }
1534
1535   psppire_sheet_get_selected_range (PSPPIRE_SHEET (de->data_sheet[0]), &range);
1536
1537   if ( range.rowi >= psppire_data_store_get_case_count (de->data_store)
1538        ||
1539        range.coli >= psppire_var_store_get_var_cnt (de->var_store))
1540     {
1541       return FALSE;
1542     }
1543
1544   return TRUE;
1545 }
1546
1547
1548 static void
1549 on_select_row (PsppireSheet *sheet, gint row, PsppireDataEditor *de)
1550 {
1551   g_signal_emit (de, data_editor_signals[CASES_SELECTED], 0, row);
1552 }
1553
1554
1555 static void
1556 on_select_variable (PsppireSheet *sheet, gint var, PsppireDataEditor *de)
1557 {
1558   g_signal_emit (de, data_editor_signals[VARIABLES_SELECTED], 0, var);
1559 }
1560
1561
1562 \f
1563
1564 /* Clipboard stuff */
1565
1566
1567 #include <data/casereader.h>
1568 #include <data/case-map.h>
1569 #include <data/casewriter.h>
1570
1571 #include <data/data-out.h>
1572 #include "xalloc.h"
1573
1574 /* A casereader and dictionary holding the data currently in the clip */
1575 static struct casereader *clip_datasheet = NULL;
1576 static struct dictionary *clip_dict = NULL;
1577
1578
1579 static void data_sheet_update_clipboard (PsppireSheet *);
1580
1581 /* Set the clip according to the currently
1582    selected range in the data sheet */
1583 static void
1584 data_sheet_set_clip (PsppireSheet *sheet)
1585 {
1586   int i;
1587   struct casewriter *writer ;
1588   PsppireSheetRange range;
1589   PsppireDataStore *ds;
1590   struct case_map *map = NULL;
1591   casenumber max_rows;
1592   size_t max_columns;
1593   gint row0, rowi;
1594   gint col0, coli;
1595
1596   ds = PSPPIRE_DATA_STORE (psppire_sheet_get_model (sheet));
1597
1598   psppire_sheet_get_selected_range (sheet, &range);
1599
1600   col0 = MIN (range.col0, range.coli);
1601   coli = MAX (range.col0, range.coli);
1602   row0 = MIN (range.row0, range.rowi);
1603   rowi = MAX (range.row0, range.rowi);
1604
1605    /* If nothing selected, then use active cell */
1606   if ( row0 < 0 || col0 < 0 )
1607     {
1608       gint row, col;
1609       psppire_sheet_get_active_cell (sheet, &row, &col);
1610
1611       row0 = rowi = row;
1612       col0 = coli = col;
1613     }
1614
1615   /* The sheet range can include cells that do not include data.
1616      Exclude them from the range. */
1617   max_rows = psppire_data_store_get_case_count (ds);
1618   if (rowi >= max_rows)
1619     {
1620       if (max_rows == 0)
1621         return;
1622       rowi = max_rows - 1;
1623     }
1624   max_columns = dict_get_var_cnt (ds->dict->dict);
1625   if (coli >= max_columns)
1626     {
1627       if (max_columns == 0)
1628         return;
1629       coli = max_columns - 1;
1630     }
1631
1632   /* Destroy any existing clip */
1633   if ( clip_datasheet )
1634     {
1635       casereader_destroy (clip_datasheet);
1636       clip_datasheet = NULL;
1637     }
1638
1639   if ( clip_dict )
1640     {
1641       dict_destroy (clip_dict);
1642       clip_dict = NULL;
1643     }
1644
1645   /* Construct clip dictionary. */
1646   clip_dict = dict_create (dict_get_encoding (ds->dict->dict));
1647   for (i = col0; i <= coli; i++)
1648     dict_clone_var_assert (clip_dict, dict_get_var (ds->dict->dict, i));
1649
1650   /* Construct clip data. */
1651   map = case_map_by_name (ds->dict->dict, clip_dict);
1652   writer = autopaging_writer_create (dict_get_proto (clip_dict));
1653   for (i = row0; i <= rowi ; ++i )
1654     {
1655       struct ccase *old = psppire_data_store_get_case (ds, i);
1656       if (old != NULL)
1657         casewriter_write (writer, case_map_execute (map, old));
1658       else
1659         casewriter_force_error (writer);
1660     }
1661   case_map_destroy (map);
1662
1663   clip_datasheet = casewriter_make_reader (writer);
1664
1665   data_sheet_update_clipboard (sheet);
1666 }
1667
1668 enum {
1669   SELECT_FMT_NULL,
1670   SELECT_FMT_TEXT,
1671   SELECT_FMT_HTML
1672 };
1673
1674
1675 /* Perform data_out for case CC, variable V, appending to STRING */
1676 static void
1677 data_out_g_string (GString *string, const struct variable *v,
1678                    const struct ccase *cc)
1679 {
1680   const struct fmt_spec *fs = var_get_print_format (v);
1681   const union value *val = case_data (cc, v);
1682
1683   char *s = data_out (val, var_get_encoding (v), fs);
1684
1685   g_string_append (string, s);
1686
1687   g_free (s);
1688 }
1689
1690 static GString *
1691 clip_to_text (void)
1692 {
1693   casenumber r;
1694   GString *string;
1695
1696   const size_t val_cnt = caseproto_get_n_widths (casereader_get_proto (clip_datasheet));
1697   const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
1698   const size_t var_cnt = dict_get_var_cnt (clip_dict);
1699
1700   string = g_string_sized_new (10 * val_cnt * case_cnt);
1701
1702   for (r = 0 ; r < case_cnt ; ++r )
1703     {
1704       int c;
1705       struct ccase *cc;
1706
1707       cc = casereader_peek (clip_datasheet, r);
1708       if (cc == NULL)
1709         {
1710           g_warning ("Clipboard seems to have inexplicably shrunk");
1711           break;
1712         }
1713
1714       for (c = 0 ; c < var_cnt ; ++c)
1715         {
1716           const struct variable *v = dict_get_var (clip_dict, c);
1717           data_out_g_string (string, v, cc);
1718           if ( c < val_cnt - 1 )
1719             g_string_append (string, "\t");
1720         }
1721
1722       if ( r < case_cnt)
1723         g_string_append (string, "\n");
1724
1725       case_unref (cc);
1726     }
1727
1728   return string;
1729 }
1730
1731
1732 static GString *
1733 clip_to_html (void)
1734 {
1735   casenumber r;
1736   GString *string;
1737
1738   const size_t val_cnt = caseproto_get_n_widths (casereader_get_proto (clip_datasheet));
1739   const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
1740   const size_t var_cnt = dict_get_var_cnt (clip_dict);
1741
1742   /* Guestimate the size needed */
1743   string = g_string_sized_new (80 + 20 * val_cnt * case_cnt);
1744
1745   g_string_append (string,
1746                    "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n");
1747
1748   g_string_append (string, "<table>\n");
1749   for (r = 0 ; r < case_cnt ; ++r )
1750     {
1751       int c;
1752       struct ccase *cc = casereader_peek (clip_datasheet, r);
1753       if (cc == NULL)
1754         {
1755           g_warning ("Clipboard seems to have inexplicably shrunk");
1756           break;
1757         }
1758       g_string_append (string, "<tr>\n");
1759
1760       for (c = 0 ; c < var_cnt ; ++c)
1761         {
1762           const struct variable *v = dict_get_var (clip_dict, c);
1763           g_string_append (string, "<td>");
1764           data_out_g_string (string, v, cc);
1765           g_string_append (string, "</td>\n");
1766         }
1767
1768       g_string_append (string, "</tr>\n");
1769
1770       case_unref (cc);
1771     }
1772   g_string_append (string, "</table>\n");
1773
1774   return string;
1775 }
1776
1777
1778
1779 static void
1780 clipboard_get_cb (GtkClipboard     *clipboard,
1781                   GtkSelectionData *selection_data,
1782                   guint             info,
1783                   gpointer          data)
1784 {
1785   GString *string = NULL;
1786
1787   switch (info)
1788     {
1789     case SELECT_FMT_TEXT:
1790       string = clip_to_text ();
1791       break;
1792     case SELECT_FMT_HTML:
1793       string = clip_to_html ();
1794       break;
1795     default:
1796       g_assert_not_reached ();
1797     }
1798
1799   gtk_selection_data_set (selection_data, selection_data->target,
1800                           8,
1801                           (const guchar *) string->str, string->len);
1802
1803   g_string_free (string, TRUE);
1804 }
1805
1806 static void
1807 clipboard_clear_cb (GtkClipboard *clipboard,
1808                     gpointer data)
1809 {
1810   dict_destroy (clip_dict);
1811   clip_dict = NULL;
1812
1813   casereader_destroy (clip_datasheet);
1814   clip_datasheet = NULL;
1815 }
1816
1817
1818 static const GtkTargetEntry targets[] = {
1819   { "UTF8_STRING",   0, SELECT_FMT_TEXT },
1820   { "STRING",        0, SELECT_FMT_TEXT },
1821   { "TEXT",          0, SELECT_FMT_TEXT },
1822   { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
1823   { "text/plain;charset=utf-8", 0, SELECT_FMT_TEXT },
1824   { "text/plain",    0, SELECT_FMT_TEXT },
1825   { "text/html",     0, SELECT_FMT_HTML }
1826 };
1827
1828
1829
1830 static void
1831 data_sheet_update_clipboard (PsppireSheet *sheet)
1832 {
1833   GtkClipboard *clipboard =
1834     gtk_widget_get_clipboard (GTK_WIDGET (sheet),
1835                               GDK_SELECTION_CLIPBOARD);
1836
1837   if (!gtk_clipboard_set_with_owner (clipboard, targets,
1838                                      G_N_ELEMENTS (targets),
1839                                      clipboard_get_cb, clipboard_clear_cb,
1840                                      G_OBJECT (sheet)))
1841     clipboard_clear_cb (clipboard, sheet);
1842 }
1843
1844
1845
1846 /* A callback for when the clipboard contents have been received */
1847 static void
1848 data_sheet_contents_received_callback (GtkClipboard *clipboard,
1849                                       GtkSelectionData *sd,
1850                                       gpointer data)
1851 {
1852   gint count = 0;
1853   gint row, column;
1854   gint next_row, next_column;
1855   gint first_column;
1856   char *c;
1857   PsppireDataEditor *data_editor = data;
1858
1859   if ( sd->length < 0 )
1860     return;
1861
1862   if ( sd->type != gdk_atom_intern ("UTF8_STRING", FALSE))
1863     return;
1864
1865   c = (char *) sd->data;
1866
1867   /* Paste text to selected position */
1868   psppire_sheet_get_active_cell (PSPPIRE_SHEET (data_editor->data_sheet[0]),
1869                              &row, &column);
1870
1871   g_return_if_fail (row >= 0);
1872   g_return_if_fail (column >= 0);
1873
1874   first_column = column;
1875   next_row = row;
1876   next_column = column;
1877   while (count < sd->length)
1878     {
1879       char *s = c;
1880
1881       row = next_row;
1882       column = next_column;
1883       while (*c != '\t' && *c != '\n' && count < sd->length)
1884         {
1885           c++;
1886           count++;
1887         }
1888       if ( *c == '\t')
1889         {
1890           next_row = row ;
1891           next_column = column + 1;
1892         }
1893       else if ( *c == '\n')
1894         {
1895           next_row = row + 1;
1896           next_column = first_column;
1897         }
1898       *c++ = '\0';
1899       count++;
1900
1901
1902       /* Append some new cases if pasting beyond the last row */
1903       if ( row >= psppire_data_store_get_case_count (data_editor->data_store))
1904         psppire_data_store_insert_new_case (data_editor->data_store, row);
1905
1906       psppire_data_store_set_string (data_editor->data_store, s, row, column);
1907     }
1908 }
1909
1910
1911 static void
1912 on_owner_change (GtkClipboard *clip, GdkEventOwnerChange *event, gpointer data)
1913 {
1914   gint i;
1915   gboolean compatible_target = FALSE;
1916   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
1917
1918   for (i = 0 ; i < sizeof (targets) / sizeof(targets[0]) ; ++i )
1919     {
1920       GdkAtom atom = gdk_atom_intern (targets[i].target, TRUE);
1921       if ( gtk_clipboard_wait_is_target_available (clip, atom))
1922         {
1923           compatible_target = TRUE;
1924           break;
1925         }
1926     }
1927
1928   g_signal_emit (de, data_editor_signals[DATA_AVAILABLE_CHANGED], 0,
1929                  compatible_target);
1930 }