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