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