psppire-data-sheet: Make clicking on Cases column select all data.
[pspp] / src / ui / gui / psppire-data-sheet.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2011, 2012 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "ui/gui/psppire-data-sheet.h"
20
21 #include "data/case-map.h"
22 #include "data/casereader.h"
23 #include "data/casewriter.h"
24 #include "data/data-out.h"
25 #include "data/datasheet.h"
26 #include "data/format.h"
27 #include "data/value-labels.h"
28 #include "libpspp/intern.h"
29 #include "libpspp/range-set.h"
30 #include "ui/gui/executor.h"
31 #include "ui/gui/find-dialog.h"
32 #include "ui/gui/goto-case-dialog.h"
33 #include "ui/gui/builder-wrapper.h"
34 #include "ui/gui/helper.h"
35 #include "ui/gui/pspp-sheet-selection.h"
36 #include "ui/gui/psppire-cell-renderer-button.h"
37 #include "ui/gui/psppire-data-store.h"
38 #include "ui/gui/psppire-data-window.h"
39 #include "ui/gui/psppire-dialog-action-var-info.h"
40 #include "ui/gui/psppire-empty-list-store.h"
41 #include "ui/gui/psppire-marshal.h"
42
43 #include "gl/intprops.h"
44 #include "gl/xalloc.h"
45
46 #include <gettext.h>
47 #define _(msgid) gettext (msgid)
48 #define N_(msgid) msgid
49
50 static void psppire_data_sheet_dispose (GObject *);
51 static void psppire_data_sheet_unset_data_store (PsppireDataSheet *);
52
53 static void psppire_data_sheet_update_clip_actions (PsppireDataSheet *);
54 static void psppire_data_sheet_set_clip (PsppireDataSheet *);
55
56 static void on_selection_changed (PsppSheetSelection *, gpointer);
57
58 G_DEFINE_TYPE (PsppireDataSheet, psppire_data_sheet, PSPP_TYPE_SHEET_VIEW);
59
60 static gboolean
61 get_tooltip_location (GtkWidget *widget, GtkTooltip *tooltip,
62                       gint wx, gint wy,
63                       size_t *row, PsppSheetViewColumn **columnp)
64 {
65   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
66   gint bx, by;
67   GtkTreePath *path;
68   GtkTreeIter iter;
69   PsppSheetViewColumn *tree_column;
70   GtkTreeModel *tree_model;
71   bool ok;
72
73   /* Check that WIDGET is really visible on the screen before we
74      do anything else.  This is a bug fix for a sticky situation:
75      when text_data_import_assistant() returns, it frees the data
76      necessary to compose the tool tip message, but there may be
77      a tool tip under preparation at that point (even if there is
78      no visible tool tip) that will call back into us a little
79      bit later.  Perhaps the correct solution to this problem is
80      to make the data related to the tool tips part of a GObject
81      that only gets destroyed when all references are released,
82      but this solution appears to be effective too. */
83   if (!gtk_widget_get_mapped (widget))
84     return FALSE;
85
86   pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view,
87                                                      wx, wy, &bx, &by);
88   if (!pspp_sheet_view_get_path_at_pos (tree_view, bx, by,
89                                       &path, &tree_column, NULL, NULL))
90     return FALSE;
91
92   *columnp = tree_column;
93
94   pspp_sheet_view_set_tooltip_cell (tree_view, tooltip, path, tree_column,
95                                     NULL);
96
97   tree_model = pspp_sheet_view_get_model (tree_view);
98   ok = gtk_tree_model_get_iter (tree_model, &iter, path);
99   gtk_tree_path_free (path);
100   if (!ok)
101     return FALSE;
102
103   *row = GPOINTER_TO_INT (iter.user_data);
104   return TRUE;
105 }
106
107 static gboolean
108 on_query_tooltip (GtkWidget *widget, gint wx, gint wy,
109                   gboolean keyboard_mode UNUSED,
110                   GtkTooltip *tooltip, gpointer data UNUSED)
111 {
112   PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (widget);
113   PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
114   PsppSheetViewColumn *column;
115   struct variable *var;
116   const char *label;
117   union value v;
118   size_t row;
119   int width;
120
121   g_return_val_if_fail (data_store != NULL, FALSE);
122
123   if (!get_tooltip_location (widget, tooltip, wx, wy, &row, &column))
124     return FALSE;
125
126   var = g_object_get_data (G_OBJECT (column), "variable");
127   if (var == NULL)
128     {
129       if (g_object_get_data (G_OBJECT (column), "new-var-column") == NULL)
130         return FALSE;
131
132       gtk_tooltip_set_text (tooltip,
133                             _("Enter a number to add a new variable."));
134       return TRUE;
135     }
136   else if (row >= datasheet_get_n_rows (data_store->datasheet))
137     {
138       gtk_tooltip_set_text (tooltip, _("Enter a number to add a new case."));
139       return TRUE;
140     }
141
142   width = var_get_width (var);
143
144   value_init (&v, width);
145   datasheet_get_value (data_store->datasheet, row, var_get_case_index (var),
146                        &v);
147
148   label = var_lookup_value_label (var, &v);
149   if (label != NULL)
150     {
151       if (data_sheet->show_value_labels)
152         {
153           char *s = value_to_text (v, var);
154           gtk_tooltip_set_text (tooltip, s);
155           free (s);
156         }
157       else
158         gtk_tooltip_set_text (tooltip, label);
159     }
160   value_destroy (&v, width);
161
162   return label != NULL;
163 }
164
165 static void
166 render_row_number_cell (PsppSheetViewColumn *tree_column,
167                         GtkCellRenderer *cell,
168                         GtkTreeModel *model,
169                         GtkTreeIter *iter,
170                         gpointer store_)
171 {
172   PsppireDataStore *store = store_;
173   GValue gvalue = { 0, };
174   gint row;
175
176   row = GPOINTER_TO_INT (iter->user_data);
177
178   g_value_init (&gvalue, G_TYPE_INT);
179   g_value_set_int (&gvalue, row + 1);
180   g_object_set_property (G_OBJECT (cell), "label", &gvalue);
181   g_value_unset (&gvalue);
182
183   if (row < datasheet_get_n_rows (store->datasheet))
184     g_object_set (cell, "editable", TRUE, NULL);
185   else
186     g_object_set (cell, "editable", FALSE, NULL);
187
188   g_object_set (cell,
189                 "slash", psppire_data_store_filtered (store, row),
190                 NULL);
191 }
192
193 static void
194 on_row_number_clicked (PsppireCellRendererButton *button,
195                        gchar *path_string,
196                        PsppSheetView *sheet_view)
197 {
198   PsppSheetSelection *selection;
199   GtkTreePath *path;
200
201   path = gtk_tree_path_new_from_string (path_string);
202
203   selection = pspp_sheet_view_get_selection (sheet_view);
204   pspp_sheet_selection_unselect_all (selection);
205   pspp_sheet_selection_select_path (selection, path);
206   pspp_sheet_selection_select_all_columns (selection);
207
208   gtk_tree_path_free (path);
209 }
210
211 static void
212 make_row_number_column (PsppireDataSheet *data_sheet,
213                         PsppireDataStore *ds)
214 {
215   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
216   PsppSheetViewColumn *column;
217   GtkCellRenderer *renderer;
218
219   renderer = psppire_cell_renderer_button_new ();
220   g_object_set (renderer, "xalign", 1.0, NULL);
221   g_signal_connect (renderer, "clicked", G_CALLBACK (on_row_number_clicked),
222                     sheet_view);
223
224   column = pspp_sheet_view_column_new_with_attributes (_("Case"),
225                                                        renderer, NULL);
226   pspp_sheet_view_column_set_selectable (column, TRUE);
227   pspp_sheet_view_column_set_row_head (column, TRUE);
228   pspp_sheet_view_column_set_clickable (column, TRUE);
229   pspp_sheet_view_column_set_cell_data_func (
230     column, renderer, render_row_number_cell, ds, NULL);
231   pspp_sheet_view_column_set_fixed_width (column, 50);
232   pspp_sheet_view_column_set_visible (column, data_sheet->show_case_numbers);
233   pspp_sheet_view_append_column (sheet_view, column);
234 }
235
236 static void
237 render_data_cell (PsppSheetViewColumn *tree_column,
238                   GtkCellRenderer *cell,
239                   GtkTreeModel *model,
240                   GtkTreeIter *iter,
241                   gpointer data_sheet_)
242 {
243   PsppireDataSheet *data_sheet = data_sheet_;
244   PsppireDataStore *store = psppire_data_sheet_get_data_store (data_sheet);
245   struct variable *var;
246   gchar *string;
247   gint row;
248
249   double xalign;
250
251   row = GPOINTER_TO_INT (iter->user_data);
252   var = g_object_get_data (G_OBJECT (tree_column), "variable");
253
254   string = psppire_data_store_get_string (store, row, var,
255                                           data_sheet->show_value_labels);
256   if (string != NULL)
257     {
258       GValue gvalue = { 0 };
259
260       g_value_init (&gvalue, G_TYPE_STRING);
261       g_value_take_string (&gvalue, string);
262       g_object_set_property (G_OBJECT (cell), "text", &gvalue);
263       g_value_unset (&gvalue);
264     }
265   else
266     g_object_set (G_OBJECT (cell), "text", "", NULL);
267
268   switch (var_get_alignment (var))
269     {
270     case ALIGN_LEFT: xalign = 0.0; break;
271     case ALIGN_RIGHT: xalign = 1.0; break;
272     case ALIGN_CENTRE: xalign = 0.5; break;
273     default: xalign = 0.0; break;
274     }
275   g_object_set (cell,
276                 "xalign", xalign,
277                 "editable", TRUE,
278                 NULL);
279 }
280
281 static gint
282 get_string_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
283                   const char *string)
284 {
285   gint width;
286   g_object_set (G_OBJECT (renderer), "text", string, (void *) NULL);
287   gtk_cell_renderer_get_size (renderer, GTK_WIDGET (treeview),
288                               NULL, NULL, NULL, &width, NULL);
289   return width;
290 }
291
292 static gint
293 get_monospace_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
294                      size_t char_cnt)
295 {
296   struct string s;
297   gint width;
298
299   ds_init_empty (&s);
300   ds_put_byte_multiple (&s, '0', char_cnt);
301   ds_put_byte (&s, ' ');
302   width = get_string_width (treeview, renderer, ds_cstr (&s));
303   ds_destroy (&s);
304
305   return width;
306 }
307
308 static void
309 on_data_column_editing_started (GtkCellRenderer *cell,
310                                 GtkCellEditable *editable,
311                                 const gchar     *path,
312                                 gpointer         user_data)
313 {
314   PsppSheetViewColumn *column = g_object_get_data (G_OBJECT (cell), "column");
315   PsppireDataSheet *data_sheet = g_object_get_data (G_OBJECT (cell), "data-sheet");
316   PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
317   struct variable *var;
318
319   g_return_if_fail (column);
320   g_return_if_fail (data_sheet);
321   g_return_if_fail (data_store);
322
323
324   g_object_ref (editable);
325   g_object_set_data_full (G_OBJECT (cell), "data-sheet-editable",
326                           editable, g_object_unref);
327
328   var = g_object_get_data (G_OBJECT (column), "variable");
329   g_return_if_fail (var);
330
331   if (var_has_value_labels (var) && GTK_IS_COMBO_BOX (editable))
332     {
333       const struct val_labs *labels = var_get_value_labels (var);
334       const struct val_lab *vl;
335       GtkListStore *list_store;
336
337       list_store = gtk_list_store_new (1, G_TYPE_STRING);
338       for (vl = val_labs_first (labels); vl != NULL;
339            vl = val_labs_next (labels, vl))
340         {
341           GtkTreeIter iter;
342
343           gtk_list_store_append (list_store, &iter);
344           gtk_list_store_set (list_store, &iter,
345                               0, val_lab_get_label (vl),
346                               -1);
347         }
348
349       gtk_combo_box_set_model (GTK_COMBO_BOX (editable),
350                                GTK_TREE_MODEL (list_store));
351       g_object_unref (list_store);
352     }
353 }
354
355 static void
356 scroll_to_bottom (GtkWidget      *widget,
357                   GtkRequisition *requisition,
358                   gpointer        unused UNUSED)
359 {
360   PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (widget);
361   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (widget);
362   GtkAdjustment *vadjust;
363
364   vadjust = pspp_sheet_view_get_vadjustment (sheet_view);
365   gtk_adjustment_set_value (vadjust, gtk_adjustment_get_upper (vadjust));
366
367   if (data_sheet->scroll_to_bottom_signal)
368     {
369       g_signal_handler_disconnect (data_sheet,
370                                    data_sheet->scroll_to_bottom_signal);
371       data_sheet->scroll_to_bottom_signal = 0;
372     }
373 }
374
375 static void
376 on_data_column_edited (GtkCellRendererText *cell,
377                        gchar               *path_string,
378                        gchar               *new_text,
379                        gpointer             user_data)
380 {
381   PsppSheetViewColumn *column = g_object_get_data (G_OBJECT (cell), "column");
382   PsppireDataSheet *data_sheet = g_object_get_data (G_OBJECT (cell), "data-sheet");
383   PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
384   GtkEditable *editable;
385   struct variable *var;
386   GtkTreePath *path;
387   gboolean is_val_lab;
388   gboolean new_row;
389   gint row;
390
391   path = gtk_tree_path_new_from_string (path_string);
392   row = gtk_tree_path_get_indices (path)[0];
393   gtk_tree_path_free (path);
394
395   var = g_object_get_data (G_OBJECT (column), "variable");
396
397   new_row = row == psppire_data_store_get_case_count (data_store);
398   if (new_row && new_text[0] == '\0')
399     return;
400
401   editable = g_object_steal_data (G_OBJECT (cell), "data-sheet-editable");
402   g_return_if_fail (editable != NULL);
403   is_val_lab = (GTK_IS_COMBO_BOX (editable)
404                 && gtk_combo_box_get_active (GTK_COMBO_BOX (editable)) >= 0);
405   g_object_unref (editable);
406
407   psppire_data_store_set_string (data_store, new_text, row, var, is_val_lab);
408
409   if (new_row && !data_sheet->scroll_to_bottom_signal)
410     {
411       gtk_widget_queue_resize (GTK_WIDGET (data_sheet));
412       data_sheet->scroll_to_bottom_signal =
413         g_signal_connect (data_sheet, "size-request",
414                           G_CALLBACK (scroll_to_bottom), NULL);
415     }
416   else
417     {
418       /* We could be more specific about what to redraw, if it seems
419          important for performance. */
420       gtk_widget_queue_draw (GTK_WIDGET (data_sheet));
421     }
422 }
423
424 static void
425 scroll_to_right (GtkWidget      *widget,
426                  PsppireDataSheet  *data_sheet)
427 {
428   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
429   PsppSheetViewColumn *column, *prev;
430   GList *columns, *iter;
431
432   column = NULL;
433   prev = NULL;
434   columns = pspp_sheet_view_get_columns (sheet_view);
435   for (iter = columns; iter; iter = iter->next)
436     {
437       PsppSheetViewColumn *c = iter->data;
438       if (g_object_get_data (G_OBJECT (c), "new-var-column"))
439         {
440           column = c;
441           break;
442         }
443       prev = c;
444     }
445   g_list_free (columns);
446
447   if (column == NULL)
448     return;
449
450   pspp_sheet_view_scroll_to_cell (sheet_view, NULL, column, FALSE, 0, 0);
451
452   if (prev)
453     {
454       GtkTreePath *path;
455
456       pspp_sheet_view_get_cursor (sheet_view, &path, NULL);
457       if (path)
458         {
459           pspp_sheet_view_set_cursor (sheet_view, path, prev, TRUE);
460           gtk_tree_path_free (path);
461         }
462     }
463
464   if (data_sheet->scroll_to_right_signal)
465     {
466       g_signal_handler_disconnect (widget, data_sheet->scroll_to_right_signal);
467       data_sheet->scroll_to_right_signal = 0;
468     }
469 }
470
471 static void
472 on_new_variable_column_edited (GtkCellRendererText *cell,
473                                gchar               *path_string,
474                                gchar               *new_text,
475                                gpointer             user_data)
476 {
477   PsppireDataSheet *data_sheet = g_object_get_data (G_OBJECT (cell), "data-sheet");
478   PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
479   PsppireDict *dict = data_store->dict;
480   struct variable *var;
481   GtkTreePath *path;
482   char name[64];
483   gint row;
484
485   if (new_text[0] == '\0')
486     {
487       /* User didn't enter anything so don't create a variable. */
488       return;
489     }
490
491   path = gtk_tree_path_new_from_string (path_string);
492   row = gtk_tree_path_get_indices (path)[0];
493   gtk_tree_path_free (path);
494
495   if (!psppire_dict_generate_name (dict, name, sizeof name))
496     return;
497
498   var = psppire_dict_insert_variable (dict, psppire_dict_get_var_cnt (dict),
499                                       name);
500   g_return_if_fail (var != NULL);
501
502   psppire_data_store_set_string (data_store, new_text, row, var, FALSE);
503
504   if (!data_sheet->scroll_to_right_signal)
505     {
506       gtk_widget_queue_resize (GTK_WIDGET (data_sheet));
507       data_sheet->scroll_to_right_signal =
508         g_signal_connect_after (gtk_widget_get_toplevel (GTK_WIDGET (data_sheet)), "check-resize",
509                                 G_CALLBACK (scroll_to_right), data_sheet);
510     }
511   else
512     {
513       /* We could be more specific about what to redraw, if it seems
514          important for performance. */
515       gtk_widget_queue_draw (GTK_WIDGET (data_sheet));
516     }
517 }
518
519 static void
520 calc_width_conversion (PsppireDataSheet *data_sheet,
521                        gint *base_width, gint *incr_width)
522 {
523   GtkCellRenderer *cell;
524   gint w1, w10;
525
526   cell = gtk_cell_renderer_text_new ();
527   w1 = get_monospace_width (PSPP_SHEET_VIEW (data_sheet), cell, 1);
528   w10 = get_monospace_width (PSPP_SHEET_VIEW (data_sheet), cell, 10);
529   *incr_width = MAX (1, (w10 - w1) / 9);
530   *base_width = MAX (0, w10 - *incr_width * 10);
531   g_object_ref_sink (cell);
532   g_object_unref (cell);
533 }
534
535 static gint
536 display_width_from_pixel_width (PsppireDataSheet *data_sheet,
537                                 gint pixel_width)
538 {
539   gint base_width, incr_width;
540
541   calc_width_conversion (data_sheet, &base_width, &incr_width);
542   return MAX ((pixel_width - base_width + incr_width / 2) / incr_width, 1);
543 }
544
545 static gint
546 display_width_to_pixel_width (PsppireDataSheet *data_sheet,
547                               gint display_width,
548                               gint base_width,
549                               gint incr_width)
550 {
551   return base_width + incr_width * display_width;
552 }
553
554 static void
555 on_data_column_resized (GObject    *gobject,
556                         GParamSpec *pspec,
557                         gpointer    user_data)
558 {
559   PsppireDataSheet *data_sheet = user_data;
560   PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
561   PsppSheetViewColumn *column = PSPP_SHEET_VIEW_COLUMN (gobject);
562   struct variable *var;
563   gint pixel_width;
564   int display_width;
565
566   if (data_store == NULL)
567     return;
568
569   pixel_width = pspp_sheet_view_column_get_width (column);
570   if (pixel_width == pspp_sheet_view_column_get_fixed_width (column))
571     {
572       /* Short-circuit the expensive display_width_from_pixel_width()
573          calculation, to make loading .sav files with 2000 columns visibly
574          faster. */
575       return;
576     }
577
578   var = g_object_get_data (G_OBJECT (column), "variable");
579   display_width = display_width_from_pixel_width (data_sheet, pixel_width);
580   var_set_display_width (var, display_width);
581 }
582
583 static void
584 do_data_column_popup_menu (PsppSheetViewColumn *column,
585                            guint button, guint32 time)
586 {
587   GtkWidget *sheet_view = pspp_sheet_view_column_get_tree_view (column);
588   PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (sheet_view);
589   GtkWidget *menu;
590
591   menu = get_widget_assert (data_sheet->builder, "datasheet-variable-popup");
592   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time);
593 }
594
595 static void
596 on_data_column_popup_menu (PsppSheetViewColumn *column,
597                            gpointer user_data UNUSED)
598 {
599   do_data_column_popup_menu (column, 0, gtk_get_current_event_time ());
600 }
601
602 static gboolean
603 on_column_button_press_event (PsppSheetViewColumn *column,
604                               GdkEventButton *event,
605                               gpointer user_data UNUSED)
606 {
607   PsppSheetSelection *selection;
608   PsppSheetView *sheet_view;
609
610   sheet_view = PSPP_SHEET_VIEW (pspp_sheet_view_column_get_tree_view (
611                                   column));
612   g_return_val_if_fail (sheet_view != NULL, FALSE);
613
614   selection = pspp_sheet_view_get_selection (sheet_view);
615   g_return_val_if_fail (selection != NULL, FALSE);
616
617   if (event->type == GDK_BUTTON_PRESS && event->button == 3)
618     {
619       do_data_column_popup_menu (column, event->button, event->time);
620       return TRUE;
621     }
622   else if (event->type == GDK_2BUTTON_PRESS && event->button == 1)
623     {
624       PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (sheet_view);
625       struct variable *var;
626
627       var = g_object_get_data (G_OBJECT (column), "variable");
628       if (var != NULL)
629         {
630           gboolean handled;
631
632           g_signal_emit_by_name (data_sheet, "var-double-clicked",
633                                  var_get_dict_index (var), &handled);
634           return handled;
635         }
636     }
637
638   return FALSE;
639 }
640
641 static gboolean
642 on_data_column_query_tooltip (PsppSheetViewColumn *column,
643                               GtkTooltip *tooltip,
644                               gpointer user_data UNUSED)
645 {
646   struct variable *var;
647   const char *text;
648
649   var = g_object_get_data (G_OBJECT (column), "variable");
650   g_return_val_if_fail (var != NULL, FALSE);
651
652   text = var_has_label (var) ? var_get_label (var) : var_get_name (var);
653   gtk_tooltip_set_text (tooltip, text);
654
655   return TRUE;
656 }
657
658 static void
659 add_data_column_cell_renderer (PsppireDataSheet *data_sheet,
660                                PsppSheetViewColumn *column)
661 {
662   GtkCellRenderer *cell;
663   struct variable *var;
664
665   var = g_object_get_data (G_OBJECT (column), "variable");
666   g_return_if_fail (var != NULL);
667
668   if (var_has_value_labels (var))
669     {
670       cell = gtk_cell_renderer_combo_new ();
671       g_object_set (G_OBJECT (cell),
672                     "has-entry", TRUE,
673                     "text-column", 0,
674                     NULL);
675     }
676   else
677     cell = gtk_cell_renderer_text_new ();
678
679   g_signal_connect (cell, "editing-started",
680                     G_CALLBACK (on_data_column_editing_started), NULL);
681   g_signal_connect (cell, "edited", G_CALLBACK (on_data_column_edited), NULL);
682
683   g_object_set_data (G_OBJECT (cell), "column", column);
684   g_object_set_data (G_OBJECT (cell), "data-sheet", data_sheet);
685
686   pspp_sheet_view_column_clear (column);
687   pspp_sheet_view_column_pack_start (column, cell, TRUE);
688
689   pspp_sheet_view_column_set_cell_data_func (
690     column, cell, render_data_cell, data_sheet, NULL);
691 }
692
693 static PsppSheetViewColumn *
694 make_data_column (PsppireDataSheet *data_sheet, gint dict_idx,
695                   gint base_width, gint incr_width)
696 {
697   PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
698   struct variable *var;
699   PsppSheetViewColumn *column;
700   char *name;
701   int width;
702
703   var = psppire_dict_get_variable (data_store->dict, dict_idx);
704
705   column = pspp_sheet_view_column_new ();
706
707   name = escape_underscores (var_get_name (var));
708   pspp_sheet_view_column_set_title (column, name);
709   free (name);
710
711   g_object_set_data (G_OBJECT (column), "variable", var);
712
713   width = display_width_to_pixel_width (data_sheet,
714                                         var_get_display_width (var),
715                                         base_width, incr_width);
716   pspp_sheet_view_column_set_min_width (column, 10);
717   pspp_sheet_view_column_set_fixed_width (column, width);
718   pspp_sheet_view_column_set_resizable (column, TRUE);
719
720   pspp_sheet_view_column_set_clickable (column, TRUE);
721   g_signal_connect (column, "notify::width",
722                     G_CALLBACK (on_data_column_resized), data_sheet);
723
724   g_signal_connect (column, "button-press-event",
725                     G_CALLBACK (on_column_button_press_event),
726                     data_sheet);
727   g_signal_connect (column, "query-tooltip",
728                     G_CALLBACK (on_data_column_query_tooltip), NULL);
729   g_signal_connect (column, "popup-menu",
730                     G_CALLBACK (on_data_column_popup_menu), data_sheet);
731
732   add_data_column_cell_renderer (data_sheet, column);
733
734   return column;
735 }
736
737 static void
738 make_new_variable_column (PsppireDataSheet *data_sheet,
739                           gint base_width, gint incr_width)
740 {
741   PsppSheetViewColumn *column;
742   GtkCellRenderer *cell;
743   int width;
744
745   cell = gtk_cell_renderer_text_new ();
746   g_object_set (cell, "editable", TRUE, NULL);
747
748   g_signal_connect (cell, "edited", G_CALLBACK (on_new_variable_column_edited),
749                     NULL);
750
751   column = pspp_sheet_view_column_new_with_attributes ("", cell, NULL);
752   g_object_set_data (G_OBJECT (column), "new-var-column", column);
753
754   width = display_width_to_pixel_width (data_sheet, 8, base_width, incr_width);
755   pspp_sheet_view_column_set_min_width (column, 10);
756   pspp_sheet_view_column_set_fixed_width (column, width);
757
758   g_object_set_data (G_OBJECT (cell), "data-sheet", data_sheet);
759   g_signal_connect (column, "button-press-event",
760                     G_CALLBACK (on_column_button_press_event),
761                     data_sheet);
762   g_signal_connect (column, "popup-menu",
763                     G_CALLBACK (on_data_column_popup_menu), data_sheet);
764
765   pspp_sheet_view_column_set_visible (column, data_sheet->may_create_vars);
766
767   pspp_sheet_view_append_column (PSPP_SHEET_VIEW (data_sheet), column);
768   data_sheet->new_variable_column = column;
769 }
770
771 static void
772 psppire_data_sheet_model_changed (GObject    *gobject,
773                                   GParamSpec *pspec,
774                                   gpointer    user_data)
775 {
776   PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (gobject);
777   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
778   PsppireDataStore *data_store;
779
780   /* Remove old columns. */
781   for (;;)
782     {
783       PsppSheetViewColumn *column = pspp_sheet_view_get_column (sheet_view, 0);
784       if (column == NULL)
785         break;
786
787       pspp_sheet_view_remove_column (sheet_view, column);
788     }
789   data_sheet->new_variable_column = NULL;
790
791   if (pspp_sheet_view_get_model (sheet_view) == NULL)
792     {
793       /* Don't create any columns at all if there's no model.  Otherwise we'll
794          create some columns as part of the "dispose" callback for the sheet
795          view, which sets the model to NULL.  That causes warnings to be
796          logged and is obviously undesirable in any case. */
797       return;
798     }
799
800   /* Add new columns. */
801   data_store = psppire_data_sheet_get_data_store (data_sheet);
802   if (data_store != NULL)
803     {
804       gint base_width, incr_width;
805       int i;
806
807       calc_width_conversion (data_sheet, &base_width, &incr_width);
808
809       make_row_number_column (data_sheet, data_store);
810       for (i = 0; i < psppire_dict_get_var_cnt (data_store->dict); i++)
811         {
812           PsppSheetViewColumn *column;
813
814           column = make_data_column (data_sheet, i, base_width, incr_width);
815           pspp_sheet_view_append_column (sheet_view, column);
816         }
817       make_new_variable_column (data_sheet, base_width, incr_width);
818     }
819 }
820
821 enum
822   {
823     PROP_0,
824     PROP_DATA_STORE,
825     PROP_VALUE_LABELS,
826     PROP_CASE_NUMBERS,
827     PROP_CURRENT_CASE,
828     PROP_MAY_CREATE_VARS,
829     PROP_MAY_DELETE_VARS,
830     PROP_UI_MANAGER
831   };
832
833 static void
834 psppire_data_sheet_set_property (GObject      *object,
835                                  guint         prop_id,
836                                  const GValue *value,
837                                  GParamSpec   *pspec)
838 {
839   PsppireDataSheet *obj = PSPPIRE_DATA_SHEET (object);
840
841   switch (prop_id)
842     {
843     case PROP_DATA_STORE:
844       psppire_data_sheet_set_data_store (
845         obj, PSPPIRE_DATA_STORE (g_value_get_object (value)));
846       break;
847
848     case PROP_VALUE_LABELS:
849       psppire_data_sheet_set_value_labels (obj, g_value_get_boolean (value));
850       break;
851
852     case PROP_CASE_NUMBERS:
853       psppire_data_sheet_set_case_numbers (obj, g_value_get_boolean (value));
854       break;
855
856     case PROP_CURRENT_CASE:
857       psppire_data_sheet_goto_case (obj, g_value_get_long (value));
858       break;
859
860     case PROP_MAY_CREATE_VARS:
861       psppire_data_sheet_set_may_create_vars (obj,
862                                               g_value_get_boolean (value));
863       break;
864
865     case PROP_MAY_DELETE_VARS:
866       psppire_data_sheet_set_may_delete_vars (obj,
867                                               g_value_get_boolean (value));
868       break;
869
870     case PROP_UI_MANAGER:
871     default:
872       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
873       break;
874     }
875 }
876
877 static void
878 psppire_data_sheet_get_property (GObject      *object,
879                                  guint         prop_id,
880                                  GValue       *value,
881                                  GParamSpec   *pspec)
882 {
883   PsppireDataSheet *obj = PSPPIRE_DATA_SHEET (object);
884
885   switch (prop_id)
886     {
887     case PROP_DATA_STORE:
888       g_value_set_object (value, psppire_data_sheet_get_data_store (obj));
889       break;
890
891     case PROP_VALUE_LABELS:
892       g_value_set_boolean (value, psppire_data_sheet_get_value_labels (obj));
893       break;
894
895     case PROP_CASE_NUMBERS:
896       g_value_set_boolean (value, psppire_data_sheet_get_case_numbers (obj));
897       break;
898
899     case PROP_CURRENT_CASE:
900       g_value_set_long (value, psppire_data_sheet_get_selected_case (obj));
901       break;
902
903     case PROP_MAY_CREATE_VARS:
904       g_value_set_boolean (value, obj->may_create_vars);
905       break;
906
907     case PROP_MAY_DELETE_VARS:
908       g_value_set_boolean (value, obj->may_delete_vars);
909       break;
910
911     case PROP_UI_MANAGER:
912       g_value_set_object (value, psppire_data_sheet_get_ui_manager (obj));
913       break;
914
915     default:
916       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
917       break;
918     }
919 }
920
921 gboolean
922 psppire_data_sheet_get_value_labels (const PsppireDataSheet *ds)
923 {
924   return ds->show_value_labels;
925 }
926
927 void
928 psppire_data_sheet_set_value_labels (PsppireDataSheet *ds,
929                                   gboolean show_value_labels)
930 {
931   show_value_labels = !!show_value_labels;
932   if (show_value_labels != ds->show_value_labels)
933     {
934       ds->show_value_labels = show_value_labels;
935       g_object_notify (G_OBJECT (ds), "value-labels");
936       gtk_widget_queue_draw (GTK_WIDGET (ds));
937
938       /* Make the cell being edited refresh too. */
939       pspp_sheet_view_stop_editing (PSPP_SHEET_VIEW (ds), TRUE);
940     }
941 }
942
943 gboolean
944 psppire_data_sheet_get_case_numbers (const PsppireDataSheet *ds)
945 {
946   return ds->show_case_numbers;
947 }
948
949 void
950 psppire_data_sheet_set_case_numbers (PsppireDataSheet *ds,
951                                      gboolean show_case_numbers)
952 {
953   show_case_numbers = !!show_case_numbers;
954   if (show_case_numbers != ds->show_case_numbers)
955     {
956       PsppSheetViewColumn *column;
957
958       ds->show_case_numbers = show_case_numbers;
959       column = pspp_sheet_view_get_column (PSPP_SHEET_VIEW (ds), 0);
960       if (column)
961         pspp_sheet_view_column_set_visible (column, show_case_numbers);
962
963       g_object_notify (G_OBJECT (ds), "case-numbers");
964       gtk_widget_queue_draw (GTK_WIDGET (ds));
965     }
966 }
967
968 gboolean
969 psppire_data_sheet_get_may_create_vars (PsppireDataSheet *data_sheet)
970 {
971   return data_sheet->may_create_vars;
972 }
973
974 void
975 psppire_data_sheet_set_may_create_vars (PsppireDataSheet *data_sheet,
976                                        gboolean may_create_vars)
977 {
978   if (data_sheet->may_create_vars != may_create_vars)
979     {
980       data_sheet->may_create_vars = may_create_vars;
981       if (data_sheet->new_variable_column)
982         pspp_sheet_view_column_set_visible (data_sheet->new_variable_column,
983                                             may_create_vars);
984
985       on_selection_changed (pspp_sheet_view_get_selection (
986                               PSPP_SHEET_VIEW (data_sheet)), NULL);
987     }
988 }
989
990 gboolean
991 psppire_data_sheet_get_may_delete_vars (PsppireDataSheet *data_sheet)
992 {
993   return data_sheet->may_delete_vars;
994 }
995
996 void
997 psppire_data_sheet_set_may_delete_vars (PsppireDataSheet *data_sheet,
998                                        gboolean may_delete_vars)
999 {
1000   if (data_sheet->may_delete_vars != may_delete_vars)
1001     {
1002       data_sheet->may_delete_vars = may_delete_vars;
1003       on_selection_changed (pspp_sheet_view_get_selection (
1004                               PSPP_SHEET_VIEW (data_sheet)), NULL);
1005     }
1006 }
1007
1008 static PsppSheetViewColumn *
1009 psppire_data_sheet_find_column_for_variable (PsppireDataSheet *data_sheet,
1010                                              gint dict_index)
1011 {
1012   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1013   PsppireDataStore *data_store;
1014   PsppSheetViewColumn *column;
1015   struct variable *var;
1016   GList *list, *iter;
1017
1018   data_store = psppire_data_sheet_get_data_store (data_sheet);
1019   g_return_val_if_fail (data_store != NULL, NULL);
1020   g_return_val_if_fail (data_store->dict != NULL, NULL);
1021
1022   var = psppire_dict_get_variable (data_store->dict, dict_index);
1023   g_return_val_if_fail (var != NULL, NULL);
1024
1025   column = NULL;
1026   list = pspp_sheet_view_get_columns (sheet_view);
1027   for (iter = list; iter != NULL; iter = iter->next)
1028     {
1029       PsppSheetViewColumn *c = iter->data;
1030       struct variable *v;
1031
1032       v = g_object_get_data (G_OBJECT (c), "variable");
1033       if (v == var)
1034         {
1035           column = c;
1036           break;
1037         }
1038     }
1039   g_list_free (list);
1040
1041   return column;
1042 }
1043
1044 void
1045 psppire_data_sheet_show_variable (PsppireDataSheet *data_sheet,
1046                                   gint dict_index)
1047 {
1048   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1049   PsppSheetViewColumn *column;
1050
1051   column = psppire_data_sheet_find_column_for_variable (data_sheet,
1052                                                         dict_index);
1053   if (column != NULL)
1054     pspp_sheet_view_scroll_to_cell (sheet_view, NULL, column,
1055                                     FALSE, 0.0, 0.0);
1056 }
1057
1058 struct variable *
1059 psppire_data_sheet_get_current_variable (const PsppireDataSheet *data_sheet)
1060 {
1061   PsppSheetSelection *selection;
1062   struct variable *var;
1063   GList *selected_columns;
1064   GList *iter;
1065
1066   selection = pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (data_sheet));
1067   selected_columns = pspp_sheet_selection_get_selected_columns (selection);
1068
1069   var = NULL;
1070   for (iter = selected_columns; iter != NULL; iter = iter->next)
1071     {
1072       PsppSheetViewColumn *column = iter->data;
1073       struct variable *v = g_object_get_data (G_OBJECT (column), "variable");
1074       if (v != NULL)
1075         {
1076           if (var)
1077             {
1078               var = NULL;
1079               break;
1080             }
1081           else
1082             var = v;
1083         }
1084     }
1085
1086   g_list_free (selected_columns);
1087
1088   return var;
1089
1090 }
1091 void
1092 psppire_data_sheet_goto_case (PsppireDataSheet *data_sheet, gint case_index)
1093 {
1094   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1095   PsppireDataStore *store = data_sheet->data_store;
1096   PsppSheetSelection *selection;
1097   GtkTreePath *path;
1098
1099   g_return_if_fail (case_index >= 0);
1100   g_return_if_fail (case_index < psppire_data_store_get_case_count (store));
1101
1102   path = gtk_tree_path_new_from_indices (case_index, -1);
1103
1104   /* Select the case. */
1105   selection = pspp_sheet_view_get_selection (sheet_view);
1106   pspp_sheet_selection_unselect_all (selection);
1107   pspp_sheet_selection_select_path (selection, path);
1108   pspp_sheet_selection_select_all_columns (selection);
1109
1110   /* Scroll so that the case is visible. */
1111   pspp_sheet_view_scroll_to_cell (sheet_view, path, NULL, FALSE, 0.0, 0.0);
1112
1113   gtk_tree_path_free (path);
1114 }
1115
1116 /* Returns the 0-based index of a selected case, if there is at least one, and
1117    -1 otherwise.
1118
1119    If more than one case is selected, returns the one with the smallest index,
1120    that is, the index of the case closest to the beginning of the file.  The
1121    row that can be used to insert a new case is not considered a case. */
1122 gint
1123 psppire_data_sheet_get_selected_case (const PsppireDataSheet *data_sheet)
1124 {
1125   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1126   PsppireDataStore *store = data_sheet->data_store;
1127   const struct range_set_node *node;
1128   PsppSheetSelection *selection;
1129   struct range_set *rows;
1130   gint row;
1131
1132   selection = pspp_sheet_view_get_selection (sheet_view);
1133   rows = pspp_sheet_selection_get_range_set (selection);
1134   node = range_set_first (rows);
1135   row = (node && node->start < psppire_data_store_get_case_count (store)
1136          ? node->start
1137          : -1);
1138   range_set_destroy (rows);
1139
1140   return row;
1141 }
1142
1143 /* Returns the 0-based index of a selected case, if exactly one case is
1144    selected, and -1 otherwise.  Returns -1 if the row that can be used to
1145    insert a new case is selected. */
1146 gint
1147 psppire_data_sheet_get_current_case (const PsppireDataSheet *data_sheet)
1148 {
1149   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1150   PsppireDataStore *store = data_sheet->data_store;
1151   const struct range_set_node *node;
1152   PsppSheetSelection *selection;
1153   struct range_set *rows;
1154   gint row;
1155
1156   selection = pspp_sheet_view_get_selection (sheet_view);
1157   if (pspp_sheet_selection_count_selected_rows (selection) != 1)
1158     return -1;
1159
1160   rows = pspp_sheet_selection_get_range_set (selection);
1161   node = range_set_first (rows);
1162   row = (node && node->start < psppire_data_store_get_case_count (store)
1163          ? node->start
1164          : -1);
1165   range_set_destroy (rows);
1166
1167   return row;
1168 }
1169
1170 GtkUIManager *
1171 psppire_data_sheet_get_ui_manager (PsppireDataSheet *data_sheet)
1172 {
1173   if (data_sheet->uim == NULL)
1174     {
1175       data_sheet->uim = 
1176         GTK_UI_MANAGER (get_object_assert (data_sheet->builder,
1177                                            "data_sheet_uim",
1178                                            GTK_TYPE_UI_MANAGER));
1179       g_object_ref (data_sheet->uim);
1180     }
1181
1182   return data_sheet->uim;
1183 }
1184
1185 static void
1186 psppire_data_sheet_dispose (GObject *object)
1187 {
1188   PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (object);
1189
1190   if (data_sheet->dispose_has_run)
1191     return;
1192
1193   data_sheet->dispose_has_run = TRUE;
1194
1195   psppire_data_sheet_unset_data_store (data_sheet);
1196
1197   g_object_unref (data_sheet->builder);
1198
1199   if (data_sheet->uim)
1200     g_object_unref (data_sheet->uim);
1201
1202   G_OBJECT_CLASS (psppire_data_sheet_parent_class)->dispose (object);
1203 }
1204
1205 static void
1206 psppire_data_sheet_class_init (PsppireDataSheetClass *class)
1207 {
1208   GObjectClass *gobject_class;
1209
1210   gobject_class = G_OBJECT_CLASS (class);
1211   gobject_class->set_property = psppire_data_sheet_set_property;
1212   gobject_class->get_property = psppire_data_sheet_get_property;
1213
1214   gobject_class->dispose = psppire_data_sheet_dispose;
1215
1216   g_signal_new ("var-double-clicked",
1217                 G_OBJECT_CLASS_TYPE (gobject_class),
1218                 G_SIGNAL_RUN_LAST,
1219                 0,
1220                 g_signal_accumulator_true_handled, NULL,
1221                 psppire_marshal_BOOLEAN__INT,
1222                 G_TYPE_BOOLEAN, 1, G_TYPE_INT);
1223
1224   g_object_class_install_property (
1225     gobject_class, PROP_DATA_STORE,
1226     g_param_spec_object ("data-store",
1227                          "Data Store",
1228                          "The data store for the data sheet to display.",
1229                          PSPPIRE_TYPE_DATA_STORE,
1230                          G_PARAM_WRITABLE | G_PARAM_READABLE));
1231
1232   g_object_class_install_property (
1233     gobject_class, PROP_VALUE_LABELS,
1234     g_param_spec_boolean ("value-labels",
1235                           "Value Labels",
1236                           "Whether or not the data sheet should display labels instead of values",
1237                           FALSE,
1238                           G_PARAM_WRITABLE | G_PARAM_READABLE));
1239
1240   g_object_class_install_property (
1241     gobject_class, PROP_CASE_NUMBERS,
1242     g_param_spec_boolean ("case-numbers",
1243                           "Case Numbers",
1244                           "Whether or not the data sheet should display case numbers",
1245                           FALSE,
1246                           G_PARAM_WRITABLE | G_PARAM_READABLE));
1247
1248   g_object_class_install_property (
1249     gobject_class,
1250     PROP_CURRENT_CASE,
1251     g_param_spec_long ("current-case",
1252                        "Current Case",
1253                        "Zero based number of the selected case",
1254                        0, CASENUMBER_MAX,
1255                        0,
1256                        G_PARAM_WRITABLE | G_PARAM_READABLE));
1257
1258   g_object_class_install_property (
1259     gobject_class,
1260     PROP_MAY_CREATE_VARS,
1261     g_param_spec_boolean ("may-create-vars",
1262                           "May create variables",
1263                           "Whether the user may create more variables",
1264                           TRUE,
1265                           G_PARAM_READWRITE));
1266
1267   g_object_class_install_property (
1268     gobject_class,
1269     PROP_MAY_DELETE_VARS,
1270     g_param_spec_boolean ("may-delete-vars",
1271                           "May delete variables",
1272                           "Whether the user may delete variables",
1273                           TRUE,
1274                           G_PARAM_READWRITE));
1275
1276   g_object_class_install_property (
1277     gobject_class,
1278     PROP_UI_MANAGER,
1279     g_param_spec_object ("ui-manager",
1280                          "UI Manager",
1281                          "UI manager for the data sheet.  The client should merge this UI manager with the active UI manager to obtain data sheet specific menu items and tool bar items.",
1282                          GTK_TYPE_UI_MANAGER,
1283                          G_PARAM_READABLE));
1284 }
1285
1286 static void
1287 do_popup_menu (GtkWidget *widget, guint button, guint32 time)
1288 {
1289   PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (widget);
1290   GtkWidget *menu;
1291
1292   menu = get_widget_assert (data_sheet->builder, "datasheet-cases-popup");
1293   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time);
1294 }
1295
1296 static void
1297 on_popup_menu (GtkWidget *widget, gpointer user_data UNUSED)
1298 {
1299   do_popup_menu (widget, 0, gtk_get_current_event_time ());
1300 }
1301
1302 static gboolean
1303 on_button_pressed (GtkWidget *widget, GdkEventButton *event,
1304                    gpointer user_data UNUSED)
1305 {
1306   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (widget);
1307
1308   if (event->type == GDK_BUTTON_PRESS && event->button == 3)
1309     {
1310       PsppSheetSelection *selection;
1311
1312       selection = pspp_sheet_view_get_selection (sheet_view);
1313       if (pspp_sheet_selection_count_selected_rows (selection) <= 1)
1314         {
1315           GtkTreePath *path;
1316
1317           if (pspp_sheet_view_get_path_at_pos (sheet_view, event->x, event->y,
1318                                                &path, NULL, NULL, NULL))
1319             {
1320               pspp_sheet_selection_unselect_all (selection);
1321               pspp_sheet_selection_select_path (selection, path);
1322               pspp_sheet_selection_select_all_columns (selection);
1323               gtk_tree_path_free (path);
1324             }
1325         }
1326
1327       do_popup_menu (widget, event->button, event->time);
1328
1329       return TRUE;
1330     }
1331
1332   return FALSE;
1333 }
1334
1335 static void
1336 on_edit_clear_cases (GtkAction *action, PsppireDataSheet *data_sheet)
1337 {
1338   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1339   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1340   const struct range_set_node *node;
1341   struct range_set *selected;
1342
1343   selected = pspp_sheet_selection_get_range_set (selection);
1344   for (node = range_set_last (selected); node != NULL;
1345        node = range_set_prev (selected, node))
1346     {
1347       unsigned long int start = range_set_node_get_start (node);
1348       unsigned long int count = range_set_node_get_width (node);
1349
1350       psppire_data_store_delete_cases (data_sheet->data_store, start, count);
1351     }
1352   range_set_destroy (selected);
1353 }
1354
1355 static void
1356 on_selection_changed (PsppSheetSelection *selection,
1357                       gpointer user_data UNUSED)
1358 {
1359   PsppSheetView *sheet_view = pspp_sheet_selection_get_tree_view (selection);
1360   PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (sheet_view);
1361   gint n_selected_rows;
1362   gboolean may_delete_cases, may_delete_vars, may_insert_vars;
1363   GList *list, *iter;
1364   GtkTreePath *path;
1365   GtkAction *action;
1366
1367   n_selected_rows = pspp_sheet_selection_count_selected_rows (selection);
1368
1369   action = get_action_assert (data_sheet->builder, "edit_insert-case");
1370   gtk_action_set_sensitive (action, n_selected_rows > 0);
1371
1372   switch (n_selected_rows)
1373     {
1374     case 0:
1375       may_delete_cases = FALSE;
1376       break;
1377
1378     case 1:
1379       /* The row used for inserting new cases cannot be deleted. */
1380       path = gtk_tree_path_new_from_indices (
1381         psppire_data_store_get_case_count (data_sheet->data_store), -1);
1382       may_delete_cases = !pspp_sheet_selection_path_is_selected (selection,
1383                                                                  path);
1384       gtk_tree_path_free (path);
1385       break;
1386
1387     default:
1388       may_delete_cases = TRUE;
1389       break;
1390     }
1391   action = get_action_assert (data_sheet->builder, "edit_clear-cases");
1392   gtk_action_set_sensitive (action, may_delete_cases);
1393
1394   may_delete_vars = may_insert_vars = FALSE;
1395   list = pspp_sheet_selection_get_selected_columns (selection);
1396   for (iter = list; iter != NULL; iter = iter->next)
1397     {
1398       PsppSheetViewColumn *column = iter->data;
1399       struct variable *var = g_object_get_data (G_OBJECT (column), "variable");
1400
1401       if (var != NULL)
1402         {
1403           may_delete_vars = may_insert_vars = TRUE;
1404           break;
1405         }
1406       if (g_object_get_data (G_OBJECT (column), "new-var-column") != NULL)
1407         may_insert_vars = TRUE;
1408     }
1409   g_list_free (list);
1410
1411   may_insert_vars = may_insert_vars && data_sheet->may_create_vars;
1412   may_delete_vars = may_delete_vars && data_sheet->may_delete_vars;
1413
1414   action = get_action_assert (data_sheet->builder, "edit_insert-variable");
1415   gtk_action_set_sensitive (action, may_insert_vars);
1416
1417   action = get_action_assert (data_sheet->builder, "edit_clear-variables");
1418   gtk_action_set_sensitive (action, may_delete_vars);
1419
1420   action = get_action_assert (data_sheet->builder, "sort-up");
1421   gtk_action_set_sensitive (action, may_delete_vars);
1422
1423   action = get_action_assert (data_sheet->builder, "sort-down");
1424   gtk_action_set_sensitive (action, may_delete_vars);
1425
1426   psppire_data_sheet_update_clip_actions (data_sheet);
1427 }
1428
1429 static gboolean
1430 psppire_data_sheet_get_selected_range (PsppireDataSheet *data_sheet,
1431                                     struct range_set **rowsp,
1432                                     struct range_set **colsp)
1433 {
1434   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1435   PsppireDataStore *data_store = data_sheet->data_store;
1436   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1437   unsigned long n_cases;
1438   struct range_set *rows, *cols;
1439   GList *list, *iter;
1440
1441   if (data_store == NULL)
1442     return FALSE;
1443   n_cases = psppire_data_store_get_case_count (data_store);
1444
1445   rows = pspp_sheet_selection_get_range_set (selection);
1446   range_set_set0 (rows, n_cases, ULONG_MAX - n_cases);
1447   if (range_set_is_empty (rows))
1448     {
1449       range_set_destroy (rows);
1450       return FALSE;
1451     }
1452
1453   cols = range_set_create ();
1454   list = pspp_sheet_selection_get_selected_columns (selection);
1455   for (iter = list; iter != NULL; iter = iter->next)
1456     {
1457       PsppSheetViewColumn *column = iter->data;
1458       struct variable *var = g_object_get_data (G_OBJECT (column), "variable");
1459
1460       if (var != NULL)
1461         range_set_set1 (cols, var_get_dict_index (var), 1);
1462     }
1463   g_list_free (list);
1464   if (range_set_is_empty (cols))
1465     {
1466       range_set_destroy (rows);
1467       range_set_destroy (cols);
1468       return FALSE;
1469     }
1470
1471   *rowsp = rows;
1472   *colsp = cols;
1473   return TRUE;
1474 }
1475
1476 static void
1477 on_edit_insert_case (GtkAction *action, PsppireDataSheet *data_sheet)
1478 {
1479   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1480   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1481   PsppireDataStore *data_store = data_sheet->data_store;
1482   struct range_set *selected;
1483   unsigned long row;
1484
1485   selected = pspp_sheet_selection_get_range_set (selection);
1486   row = range_set_scan (selected, 0);
1487   range_set_destroy (selected);
1488
1489   if (row <= psppire_data_store_get_case_count (data_store))
1490     psppire_data_store_insert_new_case (data_store, row);
1491 }
1492
1493 static void
1494 on_edit_insert_variable (GtkAction *action, PsppireDataSheet *data_sheet)
1495 {
1496   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1497   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1498   PsppireDict *dict = data_sheet->data_store->dict;
1499   PsppSheetViewColumn *column;
1500   struct variable *var;
1501   gchar name[64];
1502   GList *list;
1503   gint index;
1504
1505   list = pspp_sheet_selection_get_selected_columns (selection);
1506   if (list == NULL)
1507     return;
1508   column = list->data;
1509   g_list_free (list);
1510
1511   var = g_object_get_data (G_OBJECT (column), "variable");
1512   index = var ? var_get_dict_index (var) : psppire_dict_get_var_cnt (dict);
1513   if (psppire_dict_generate_name (dict, name, sizeof name))
1514     psppire_dict_insert_variable (dict, index, name);
1515 }
1516
1517 static void
1518 on_edit_clear_variables (GtkAction *action, PsppireDataSheet *data_sheet)
1519 {
1520   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1521   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1522   PsppireDict *dict = data_sheet->data_store->dict;
1523   GList *list, *iter;
1524
1525   list = pspp_sheet_selection_get_selected_columns (selection);
1526   if (list == NULL)
1527     return;
1528   list = g_list_reverse (list);
1529   for (iter = list; iter; iter = iter->next)
1530     {
1531       PsppSheetViewColumn *column = iter->data;
1532       struct variable *var;
1533
1534       var = g_object_get_data (G_OBJECT (column), "variable");
1535       if (var != NULL)
1536         psppire_dict_delete_variables (dict, var_get_dict_index (var), 1);
1537     }
1538   g_list_free (list);
1539 }
1540
1541 enum sort_order
1542   {
1543     SORT_ASCEND,
1544     SORT_DESCEND
1545   };
1546
1547 static void
1548 do_sort (PsppireDataSheet *data_sheet, enum sort_order order)
1549 {
1550   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1551   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1552   PsppireDataWindow *pdw;
1553   GList *list, *iter;
1554   GString *syntax;
1555   int n_vars;
1556
1557   pdw = psppire_data_window_for_data_store (data_sheet->data_store);
1558   g_return_if_fail (pdw != NULL);
1559
1560   list = pspp_sheet_selection_get_selected_columns (selection);
1561
1562   syntax = g_string_new ("SORT CASES BY");
1563   n_vars = 0;
1564   for (iter = list; iter; iter = iter->next)
1565     {
1566       PsppSheetViewColumn *column = iter->data;
1567       struct variable *var;
1568
1569       var = g_object_get_data (G_OBJECT (column), "variable");
1570       if (var != NULL)
1571         {
1572           g_string_append_printf (syntax, " %s", var_get_name (var));
1573           n_vars++;
1574         }
1575     }
1576   if (n_vars > 0)
1577     {
1578       if (order == SORT_DESCEND)
1579         g_string_append (syntax, " (DOWN)");
1580       g_string_append_c (syntax, '.');
1581       execute_const_syntax_string (pdw, syntax->str);
1582     }
1583   g_string_free (syntax, TRUE);
1584 }
1585
1586 void
1587 on_sort_up (GtkAction *action, PsppireDataSheet *data_sheet)
1588 {
1589   do_sort (data_sheet, SORT_ASCEND);
1590 }
1591
1592 void
1593 on_sort_down (GtkAction *action, PsppireDataSheet *data_sheet)
1594 {
1595   do_sort (data_sheet, SORT_DESCEND);
1596 }
1597
1598 void
1599 on_edit_goto_case (GtkAction *action, PsppireDataSheet *data_sheet)
1600 {
1601   goto_case_dialog (data_sheet);
1602 }
1603
1604 void
1605 on_edit_find (GtkAction *action, PsppireDataSheet *data_sheet)
1606 {
1607   PsppireDataWindow *pdw;
1608
1609   pdw = psppire_data_window_for_data_store (data_sheet->data_store);
1610   g_return_if_fail (pdw != NULL);
1611
1612   find_dialog (pdw);
1613 }
1614
1615 void
1616 on_edit_copy (GtkAction *action, PsppireDataSheet *data_sheet)
1617 {
1618   psppire_data_sheet_set_clip (data_sheet);
1619 }
1620
1621 static void
1622 psppire_data_sheet_init (PsppireDataSheet *obj)
1623 {
1624   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (obj);
1625   GtkAction *action;
1626
1627   obj->show_value_labels = FALSE;
1628   obj->show_case_numbers = TRUE;
1629   obj->may_create_vars = TRUE;
1630   obj->may_delete_vars = TRUE;
1631
1632   obj->scroll_to_bottom_signal = 0;
1633   obj->scroll_to_right_signal = 0;
1634   obj->new_variable_column = NULL;
1635   obj->container = NULL;
1636
1637   obj->uim = NULL;
1638   obj->dispose_has_run = FALSE;
1639
1640   pspp_sheet_view_set_special_cells (sheet_view, PSPP_SHEET_VIEW_SPECIAL_CELLS_YES);
1641
1642   g_signal_connect (obj, "notify::model",
1643                     G_CALLBACK (psppire_data_sheet_model_changed), NULL);
1644
1645   pspp_sheet_view_set_rubber_banding (sheet_view, TRUE);
1646   pspp_sheet_selection_set_mode (pspp_sheet_view_get_selection (sheet_view),
1647                                  PSPP_SHEET_SELECTION_RECTANGLE);
1648
1649   g_object_set (G_OBJECT (obj), "has-tooltip", TRUE, (void *) NULL);
1650   g_signal_connect (obj, "query-tooltip",
1651                     G_CALLBACK (on_query_tooltip), NULL);
1652   g_signal_connect (obj, "button-press-event",
1653                     G_CALLBACK (on_button_pressed), NULL);
1654   g_signal_connect (obj, "popup-menu", G_CALLBACK (on_popup_menu), NULL);
1655
1656   obj->builder = builder_new ("data-sheet.ui");
1657
1658   action = get_action_assert (obj->builder, "edit_clear-cases");
1659   g_signal_connect (action, "activate", G_CALLBACK (on_edit_clear_cases),
1660                     obj);
1661   gtk_action_set_sensitive (action, FALSE);
1662   g_signal_connect (pspp_sheet_view_get_selection (sheet_view),
1663                     "changed", G_CALLBACK (on_selection_changed), NULL);
1664
1665   action = get_action_assert (obj->builder, "edit_insert-case");
1666   g_signal_connect (action, "activate", G_CALLBACK (on_edit_insert_case),
1667                     obj);
1668
1669   action = get_action_assert (obj->builder, "edit_insert-variable");
1670   g_signal_connect (action, "activate", G_CALLBACK (on_edit_insert_variable),
1671                     obj);
1672
1673   action = get_action_assert (obj->builder, "edit_goto-case");
1674   g_signal_connect (action, "activate", G_CALLBACK (on_edit_goto_case),
1675                     obj);
1676
1677   action = get_action_assert (obj->builder, "edit_copy");
1678   g_signal_connect (action, "activate", G_CALLBACK (on_edit_copy), obj);
1679
1680   action = get_action_assert (obj->builder, "edit_clear-variables");
1681   g_signal_connect (action, "activate", G_CALLBACK (on_edit_clear_variables),
1682                     obj);
1683
1684   action = get_action_assert (obj->builder, "edit_find");
1685   g_signal_connect (action, "activate", G_CALLBACK (on_edit_find), obj);
1686
1687   action = get_action_assert (obj->builder, "sort-up");
1688   g_signal_connect (action, "activate", G_CALLBACK (on_sort_up), obj);
1689
1690   action = get_action_assert (obj->builder, "sort-down");
1691   g_signal_connect (action, "activate", G_CALLBACK (on_sort_down), obj);
1692
1693 }
1694
1695 GtkWidget *
1696 psppire_data_sheet_new (void)
1697 {
1698   return g_object_new (PSPP_TYPE_DATA_SHEET, NULL);
1699 }
1700
1701 PsppireDataStore *
1702 psppire_data_sheet_get_data_store (PsppireDataSheet *data_sheet)
1703 {
1704   return data_sheet->data_store;
1705 }
1706
1707 static void
1708 refresh_model (PsppireDataSheet *data_sheet)
1709 {
1710   pspp_sheet_view_set_model (PSPP_SHEET_VIEW (data_sheet), NULL);
1711
1712   if (data_sheet->data_store != NULL)
1713     {
1714       PsppireEmptyListStore *model;
1715       GtkAction *action;
1716       int n_rows;
1717
1718       n_rows = psppire_data_store_get_case_count (data_sheet->data_store) + 1;
1719       model = psppire_empty_list_store_new (n_rows);
1720       pspp_sheet_view_set_model (PSPP_SHEET_VIEW (data_sheet),
1721                                  GTK_TREE_MODEL (model));
1722       g_object_unref (model);
1723
1724       action = get_action_assert (data_sheet->builder, "edit_copy");
1725       g_signal_connect (action, "activate", G_CALLBACK (on_edit_copy),
1726                         data_sheet);
1727     }
1728 }
1729
1730 static void
1731 on_case_inserted (PsppireDataStore *data_store, gint row,
1732                   PsppireDataSheet *data_sheet)
1733 {
1734   PsppireEmptyListStore *empty_list_store;
1735   GtkTreeModel *tree_model;
1736   gint n_rows;
1737
1738   g_return_if_fail (data_store == data_sheet->data_store);
1739
1740   n_rows = psppire_data_store_get_case_count (data_store) + 1;
1741   if (row == n_rows - 1)
1742     row++;
1743
1744   tree_model = pspp_sheet_view_get_model (PSPP_SHEET_VIEW (data_sheet));
1745   empty_list_store = PSPPIRE_EMPTY_LIST_STORE (tree_model);
1746   psppire_empty_list_store_set_n_rows (empty_list_store, n_rows);
1747   psppire_empty_list_store_row_inserted (empty_list_store, row);
1748 }
1749
1750 static void
1751 on_cases_deleted (PsppireDataStore *data_store, gint first, gint n_cases,
1752                   PsppireDataSheet *data_sheet)
1753 {
1754
1755   g_return_if_fail (data_store == data_sheet->data_store);
1756
1757   if (n_cases > 1)
1758     {
1759       /* This is a bit of a cop-out.  We could do better, if it ever turns out
1760          that this performs too poorly. */
1761       refresh_model (data_sheet);
1762     }
1763   else
1764     {
1765       PsppireEmptyListStore *empty_list_store;
1766       GtkTreeModel *tree_model;
1767       gint n_rows = psppire_data_store_get_case_count (data_store) + 1;
1768
1769       tree_model = pspp_sheet_view_get_model (PSPP_SHEET_VIEW (data_sheet));
1770       empty_list_store = PSPPIRE_EMPTY_LIST_STORE (tree_model);
1771       psppire_empty_list_store_set_n_rows (empty_list_store, n_rows);
1772       psppire_empty_list_store_row_deleted (empty_list_store, first);
1773     }
1774 }
1775
1776 static void
1777 on_case_change (PsppireDataStore *data_store, gint row,
1778                 PsppireDataSheet *data_sheet)
1779 {
1780   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1781
1782   pspp_sheet_view_stop_editing (sheet_view, TRUE);
1783   gtk_widget_queue_draw (GTK_WIDGET (data_sheet));
1784 }
1785
1786 static void
1787 on_backend_changed (PsppireDataStore *data_store,
1788                     PsppireDataSheet *data_sheet)
1789 {
1790   g_return_if_fail (data_store == data_sheet->data_store);
1791   refresh_model (data_sheet);
1792 }
1793
1794 static void
1795 on_variable_display_width_changed (PsppireDict *dict, int dict_index,
1796                                    PsppireDataSheet *data_sheet)
1797 {
1798   PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
1799   PsppSheetViewColumn *column;
1800   struct variable *var;
1801   int display_width;
1802   gint pixel_width;
1803
1804   g_return_if_fail (data_sheet->data_store != NULL);
1805   g_return_if_fail (dict == data_sheet->data_store->dict);
1806
1807   column = psppire_data_sheet_find_column_for_variable (data_sheet,
1808                                                         dict_index);
1809   if (column == NULL)
1810     return;
1811
1812   var = psppire_dict_get_variable (data_store->dict, dict_index);
1813   g_return_if_fail (var != NULL);
1814
1815   pixel_width = pspp_sheet_view_column_get_fixed_width (column);
1816   display_width = display_width_from_pixel_width (data_sheet, pixel_width);
1817   if (display_width != var_get_display_width (var))
1818     {
1819       gint base_width, incr_width;
1820
1821       display_width = var_get_display_width (var);
1822       calc_width_conversion (data_sheet, &base_width, &incr_width);
1823       pixel_width = display_width_to_pixel_width (data_sheet, display_width,
1824                                                   base_width, incr_width);
1825       pspp_sheet_view_column_set_fixed_width (column, pixel_width);
1826     }
1827 }
1828
1829 static void
1830 on_variable_changed (PsppireDict *dict, int dict_index,
1831                      PsppireDataSheet *data_sheet)
1832 {
1833   PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
1834   PsppSheetViewColumn *column;
1835   GtkCellRenderer *cell;
1836   struct variable *var;
1837   GList *cells;
1838   char *name;
1839
1840   g_return_if_fail (data_sheet->data_store != NULL);
1841   g_return_if_fail (dict == data_sheet->data_store->dict);
1842
1843   column = psppire_data_sheet_find_column_for_variable (data_sheet,
1844                                                         dict_index);
1845   if (column == NULL)
1846     return;
1847
1848   var = psppire_dict_get_variable (data_store->dict, dict_index);
1849   g_return_if_fail (var != NULL);
1850
1851   name = escape_underscores (var_get_name (var));
1852   if (strcmp (name, pspp_sheet_view_column_get_title (column)))
1853     pspp_sheet_view_column_set_title (column, name);
1854   free (name);
1855
1856   cells = pspp_sheet_view_column_get_cell_renderers (column);
1857   g_return_if_fail (cells);
1858   cell = cells->data;
1859   g_list_free (cells);
1860
1861   if (var_has_value_labels (var) != GTK_IS_CELL_RENDERER_COMBO (cell))
1862     {
1863       /* Stop editing before we delete and replace the cell renderers.
1864          Otherwise if this column is currently being edited, an eventual call
1865          to pspp_sheet_view_stop_editing() will obtain a NULL cell and pass
1866          that to gtk_cell_renderer_stop_editing(), which causes a critical.
1867
1868          It's possible that this is a bug in PsppSheetView, and it's possible
1869          that PsppSheetView inherits that from GtkTreeView, but I haven't
1870          investigated yet. */
1871       pspp_sheet_view_stop_editing (PSPP_SHEET_VIEW (data_sheet), TRUE);
1872
1873       add_data_column_cell_renderer (data_sheet, column);
1874     }
1875 }
1876
1877 static void
1878 on_variable_inserted (PsppireDict *dict, int var_index,
1879                       PsppireDataSheet *data_sheet)
1880 {
1881   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1882   gint base_width, incr_width;
1883   PsppSheetViewColumn *column;
1884
1885   calc_width_conversion (data_sheet, &base_width, &incr_width);
1886   column = make_data_column (data_sheet, var_index, base_width, incr_width);
1887   pspp_sheet_view_insert_column (sheet_view, column, var_index + 1);
1888 }
1889
1890 static void
1891 on_variable_deleted (PsppireDict *dict,
1892                      const struct variable *var, int case_idx, int width,
1893                      PsppireDataSheet *data_sheet)
1894 {
1895   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1896   GList *columns, *iter;
1897
1898   columns = pspp_sheet_view_get_columns (sheet_view);
1899   for (iter = columns; iter != NULL; iter = iter->next)
1900     {
1901       PsppSheetViewColumn *column = iter->data;
1902       const struct variable *column_var;
1903
1904       column_var = g_object_get_data (G_OBJECT (column), "variable");
1905       if (column_var == var)
1906         pspp_sheet_view_remove_column (sheet_view, column);
1907     }
1908   g_list_free (columns);
1909 }
1910
1911 static void
1912 psppire_data_sheet_unset_data_store (PsppireDataSheet *data_sheet)
1913 {
1914   PsppireDataStore *store = data_sheet->data_store;
1915
1916   if (store == NULL)
1917     return;
1918
1919   data_sheet->data_store = NULL;
1920
1921   g_signal_handlers_disconnect_by_func (
1922     store, G_CALLBACK (on_backend_changed), data_sheet);
1923   g_signal_handlers_disconnect_by_func (
1924     store, G_CALLBACK (on_case_inserted), data_sheet);
1925   g_signal_handlers_disconnect_by_func (
1926     store, G_CALLBACK (on_cases_deleted), data_sheet);
1927   g_signal_handlers_disconnect_by_func (
1928     store, G_CALLBACK (on_case_change), data_sheet);
1929
1930   g_signal_handlers_disconnect_by_func (
1931     store->dict, G_CALLBACK (on_variable_changed), data_sheet);
1932   g_signal_handlers_disconnect_by_func (
1933     store->dict, G_CALLBACK (on_variable_display_width_changed), data_sheet);
1934   g_signal_handlers_disconnect_by_func (
1935     store->dict, G_CALLBACK (on_variable_inserted), data_sheet);
1936   g_signal_handlers_disconnect_by_func (
1937     store->dict, G_CALLBACK (on_variable_deleted), data_sheet);
1938
1939   g_object_unref (store);
1940 }
1941
1942 void
1943 psppire_data_sheet_set_data_store (PsppireDataSheet *data_sheet,
1944                                 PsppireDataStore *data_store)
1945 {
1946   psppire_data_sheet_unset_data_store (data_sheet);
1947
1948   data_sheet->data_store = data_store;
1949   if (data_store != NULL)
1950     {
1951       g_object_ref (data_store);
1952       g_signal_connect (data_store, "backend-changed",
1953                         G_CALLBACK (on_backend_changed), data_sheet);
1954       g_signal_connect (data_store, "case-inserted",
1955                         G_CALLBACK (on_case_inserted), data_sheet);
1956       g_signal_connect (data_store, "cases-deleted",
1957                         G_CALLBACK (on_cases_deleted), data_sheet);
1958       g_signal_connect (data_store, "case-changed",
1959                         G_CALLBACK (on_case_change), data_sheet);
1960
1961       /* XXX it's unclean to hook into the dict this way--what if the dict
1962          changes?  As of this writing, though, nothing ever changes the
1963          data_store's dict. */
1964       g_signal_connect (data_store->dict, "variable-changed",
1965                         G_CALLBACK (on_variable_changed),
1966                         data_sheet);
1967       g_signal_connect (data_store->dict, "variable-display-width-changed",
1968                         G_CALLBACK (on_variable_display_width_changed),
1969                         data_sheet);
1970       g_signal_connect (data_store->dict, "variable-inserted",
1971                         G_CALLBACK (on_variable_inserted), data_sheet);
1972       g_signal_connect (data_store->dict, "variable-deleted",
1973                         G_CALLBACK (on_variable_deleted), data_sheet);
1974     }
1975   refresh_model (data_sheet);
1976 }
1977 \f
1978 /* Clipboard stuff */
1979
1980 /* A casereader and dictionary holding the data currently in the clip */
1981 static struct casereader *clip_datasheet = NULL;
1982 static struct dictionary *clip_dict = NULL;
1983
1984
1985 static void psppire_data_sheet_update_clipboard (PsppireDataSheet *);
1986
1987 /* Set the clip according to the currently
1988    selected range in the data sheet */
1989 static void
1990 psppire_data_sheet_set_clip (PsppireDataSheet *data_sheet)
1991 {
1992   struct casewriter *writer ;
1993   PsppireDataStore *ds = psppire_data_sheet_get_data_store (data_sheet);
1994   struct case_map *map = NULL;
1995   struct range_set *rows, *cols;
1996   const struct range_set_node *node;
1997
1998   if (!psppire_data_sheet_get_selected_range (data_sheet, &rows, &cols))
1999     return;
2000
2001
2002   /* Destroy any existing clip */
2003   if ( clip_datasheet )
2004     {
2005       casereader_destroy (clip_datasheet);
2006       clip_datasheet = NULL;
2007     }
2008
2009   if ( clip_dict )
2010     {
2011       dict_destroy (clip_dict);
2012       clip_dict = NULL;
2013     }
2014
2015   /* Construct clip dictionary. */
2016   clip_dict = dict_create (dict_get_encoding (ds->dict->dict));
2017   RANGE_SET_FOR_EACH (node, cols)
2018     {
2019       int dict_index;
2020
2021       for (dict_index = node->start; dict_index < node->end; dict_index++)
2022         {
2023           struct variable *var = dict_get_var (ds->dict->dict, dict_index);
2024           dict_clone_var_assert (clip_dict, var);
2025         }
2026     }
2027
2028   /* Construct clip data. */
2029   map = case_map_by_name (ds->dict->dict, clip_dict);
2030   writer = autopaging_writer_create (dict_get_proto (clip_dict));
2031   RANGE_SET_FOR_EACH (node, rows)
2032     {
2033       unsigned long int row;
2034
2035       for (row = node->start; row < node->end; row++)
2036         {
2037           struct ccase *old = psppire_data_store_get_case (ds, row);
2038           if (old != NULL)
2039             casewriter_write (writer, case_map_execute (map, old));
2040           else
2041             casewriter_force_error (writer);
2042         }
2043     }
2044   case_map_destroy (map);
2045
2046   range_set_destroy (rows);
2047   range_set_destroy (cols);
2048
2049   clip_datasheet = casewriter_make_reader (writer);
2050
2051   psppire_data_sheet_update_clipboard (data_sheet);
2052 }
2053
2054 enum {
2055   SELECT_FMT_NULL,
2056   SELECT_FMT_TEXT,
2057   SELECT_FMT_HTML
2058 };
2059
2060
2061 /* Perform data_out for case CC, variable V, appending to STRING */
2062 static void
2063 data_out_g_string (GString *string, const struct variable *v,
2064                    const struct ccase *cc)
2065 {
2066   const struct fmt_spec *fs = var_get_print_format (v);
2067   const union value *val = case_data (cc, v);
2068
2069   char *s = data_out (val, var_get_encoding (v), fs);
2070
2071   g_string_append (string, s);
2072
2073   g_free (s);
2074 }
2075
2076 static GString *
2077 clip_to_text (void)
2078 {
2079   casenumber r;
2080   GString *string;
2081
2082   const size_t val_cnt = caseproto_get_n_widths (casereader_get_proto (clip_datasheet));
2083   const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
2084   const size_t var_cnt = dict_get_var_cnt (clip_dict);
2085
2086   string = g_string_sized_new (10 * val_cnt * case_cnt);
2087
2088   for (r = 0 ; r < case_cnt ; ++r )
2089     {
2090       int c;
2091       struct ccase *cc;
2092
2093       cc = casereader_peek (clip_datasheet, r);
2094       if (cc == NULL)
2095         {
2096           g_warning ("Clipboard seems to have inexplicably shrunk");
2097           break;
2098         }
2099
2100       for (c = 0 ; c < var_cnt ; ++c)
2101         {
2102           const struct variable *v = dict_get_var (clip_dict, c);
2103           data_out_g_string (string, v, cc);
2104           if ( c < val_cnt - 1 )
2105             g_string_append (string, "\t");
2106         }
2107
2108       if ( r < case_cnt)
2109         g_string_append (string, "\n");
2110
2111       case_unref (cc);
2112     }
2113
2114   return string;
2115 }
2116
2117
2118 static GString *
2119 clip_to_html (void)
2120 {
2121   casenumber r;
2122   GString *string;
2123
2124   const size_t val_cnt = caseproto_get_n_widths (casereader_get_proto (clip_datasheet));
2125   const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
2126   const size_t var_cnt = dict_get_var_cnt (clip_dict);
2127
2128   /* Guestimate the size needed */
2129   string = g_string_sized_new (80 + 20 * val_cnt * case_cnt);
2130
2131   g_string_append (string,
2132                    "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n");
2133
2134   g_string_append (string, "<table>\n");
2135   for (r = 0 ; r < case_cnt ; ++r )
2136     {
2137       int c;
2138       struct ccase *cc = casereader_peek (clip_datasheet, r);
2139       if (cc == NULL)
2140         {
2141           g_warning ("Clipboard seems to have inexplicably shrunk");
2142           break;
2143         }
2144       g_string_append (string, "<tr>\n");
2145
2146       for (c = 0 ; c < var_cnt ; ++c)
2147         {
2148           const struct variable *v = dict_get_var (clip_dict, c);
2149           g_string_append (string, "<td>");
2150           data_out_g_string (string, v, cc);
2151           g_string_append (string, "</td>\n");
2152         }
2153
2154       g_string_append (string, "</tr>\n");
2155
2156       case_unref (cc);
2157     }
2158   g_string_append (string, "</table>\n");
2159
2160   return string;
2161 }
2162
2163
2164
2165 static void
2166 psppire_data_sheet_clipboard_get_cb (GtkClipboard     *clipboard,
2167                                      GtkSelectionData *selection_data,
2168                                      guint             info,
2169                                      gpointer          data)
2170 {
2171   GString *string = NULL;
2172
2173   switch (info)
2174     {
2175     case SELECT_FMT_TEXT:
2176       string = clip_to_text ();
2177       break;
2178     case SELECT_FMT_HTML:
2179       string = clip_to_html ();
2180       break;
2181     default:
2182       g_assert_not_reached ();
2183     }
2184
2185   gtk_selection_data_set (selection_data, selection_data->target,
2186                           8,
2187                           (const guchar *) string->str, string->len);
2188
2189   g_string_free (string, TRUE);
2190 }
2191
2192 static void
2193 psppire_data_sheet_clipboard_clear_cb (GtkClipboard *clipboard,
2194                                        gpointer data)
2195 {
2196   dict_destroy (clip_dict);
2197   clip_dict = NULL;
2198
2199   casereader_destroy (clip_datasheet);
2200   clip_datasheet = NULL;
2201 }
2202
2203
2204 static const GtkTargetEntry targets[] = {
2205   { "UTF8_STRING",   0, SELECT_FMT_TEXT },
2206   { "STRING",        0, SELECT_FMT_TEXT },
2207   { "TEXT",          0, SELECT_FMT_TEXT },
2208   { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
2209   { "text/plain;charset=utf-8", 0, SELECT_FMT_TEXT },
2210   { "text/plain",    0, SELECT_FMT_TEXT },
2211   { "text/html",     0, SELECT_FMT_HTML }
2212 };
2213
2214
2215
2216 static void
2217 psppire_data_sheet_update_clipboard (PsppireDataSheet *sheet)
2218 {
2219   GtkClipboard *clipboard =
2220     gtk_widget_get_clipboard (GTK_WIDGET (sheet),
2221                               GDK_SELECTION_CLIPBOARD);
2222
2223   if (!gtk_clipboard_set_with_owner (clipboard, targets,
2224                                      G_N_ELEMENTS (targets),
2225                                      psppire_data_sheet_clipboard_get_cb,
2226                                      psppire_data_sheet_clipboard_clear_cb,
2227                                      G_OBJECT (sheet)))
2228     psppire_data_sheet_clipboard_clear_cb (clipboard, sheet);
2229 }
2230
2231 static void
2232 psppire_data_sheet_update_clip_actions (PsppireDataSheet *data_sheet)
2233 {
2234   struct range_set *rows, *cols;
2235   GtkAction *action;
2236   gboolean enable;
2237
2238   enable = psppire_data_sheet_get_selected_range (data_sheet, &rows, &cols);
2239   if (enable)
2240     {
2241       range_set_destroy (rows);
2242       range_set_destroy (cols);
2243     }
2244
2245   action = get_action_assert (data_sheet->builder, "edit_copy");
2246   gtk_action_set_sensitive (action, enable);
2247
2248   action = get_action_assert (data_sheet->builder, "edit_cut");
2249   gtk_action_set_sensitive (action, enable);
2250 }
2251