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