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