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