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