psppire-data-sheet: Implement Edit|Cut.
[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 *, gboolean cut);
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, FALSE);
1619 }
1620
1621 void
1622 on_edit_cut (GtkAction *action, PsppireDataSheet *data_sheet)
1623 {
1624   psppire_data_sheet_set_clip (data_sheet, TRUE);
1625 }
1626
1627 static void
1628 psppire_data_sheet_init (PsppireDataSheet *obj)
1629 {
1630   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (obj);
1631   GtkAction *action;
1632
1633   obj->show_value_labels = FALSE;
1634   obj->show_case_numbers = TRUE;
1635   obj->may_create_vars = TRUE;
1636   obj->may_delete_vars = TRUE;
1637
1638   obj->scroll_to_bottom_signal = 0;
1639   obj->scroll_to_right_signal = 0;
1640   obj->new_variable_column = NULL;
1641   obj->container = NULL;
1642
1643   obj->uim = NULL;
1644   obj->dispose_has_run = FALSE;
1645
1646   pspp_sheet_view_set_special_cells (sheet_view, PSPP_SHEET_VIEW_SPECIAL_CELLS_YES);
1647
1648   g_signal_connect (obj, "notify::model",
1649                     G_CALLBACK (psppire_data_sheet_model_changed), NULL);
1650
1651   pspp_sheet_view_set_rubber_banding (sheet_view, TRUE);
1652   pspp_sheet_selection_set_mode (pspp_sheet_view_get_selection (sheet_view),
1653                                  PSPP_SHEET_SELECTION_RECTANGLE);
1654
1655   g_object_set (G_OBJECT (obj), "has-tooltip", TRUE, (void *) NULL);
1656   g_signal_connect (obj, "query-tooltip",
1657                     G_CALLBACK (on_query_tooltip), NULL);
1658   g_signal_connect (obj, "button-press-event",
1659                     G_CALLBACK (on_button_pressed), NULL);
1660   g_signal_connect (obj, "popup-menu", G_CALLBACK (on_popup_menu), NULL);
1661
1662   obj->builder = builder_new ("data-sheet.ui");
1663
1664   action = get_action_assert (obj->builder, "edit_clear-cases");
1665   g_signal_connect (action, "activate", G_CALLBACK (on_edit_clear_cases),
1666                     obj);
1667   gtk_action_set_sensitive (action, FALSE);
1668   g_signal_connect (pspp_sheet_view_get_selection (sheet_view),
1669                     "changed", G_CALLBACK (on_selection_changed), NULL);
1670
1671   action = get_action_assert (obj->builder, "edit_insert-case");
1672   g_signal_connect (action, "activate", G_CALLBACK (on_edit_insert_case),
1673                     obj);
1674
1675   action = get_action_assert (obj->builder, "edit_insert-variable");
1676   g_signal_connect (action, "activate", G_CALLBACK (on_edit_insert_variable),
1677                     obj);
1678
1679   action = get_action_assert (obj->builder, "edit_goto-case");
1680   g_signal_connect (action, "activate", G_CALLBACK (on_edit_goto_case),
1681                     obj);
1682
1683   action = get_action_assert (obj->builder, "edit_copy");
1684   g_signal_connect (action, "activate", G_CALLBACK (on_edit_copy), obj);
1685
1686   action = get_action_assert (obj->builder, "edit_cut");
1687   g_signal_connect (action, "activate", G_CALLBACK (on_edit_cut), obj);
1688
1689   action = get_action_assert (obj->builder, "edit_clear-variables");
1690   g_signal_connect (action, "activate", G_CALLBACK (on_edit_clear_variables),
1691                     obj);
1692
1693   action = get_action_assert (obj->builder, "edit_find");
1694   g_signal_connect (action, "activate", G_CALLBACK (on_edit_find), obj);
1695
1696   action = get_action_assert (obj->builder, "sort-up");
1697   g_signal_connect (action, "activate", G_CALLBACK (on_sort_up), obj);
1698
1699   action = get_action_assert (obj->builder, "sort-down");
1700   g_signal_connect (action, "activate", G_CALLBACK (on_sort_down), obj);
1701
1702 }
1703
1704 GtkWidget *
1705 psppire_data_sheet_new (void)
1706 {
1707   return g_object_new (PSPP_TYPE_DATA_SHEET, NULL);
1708 }
1709
1710 PsppireDataStore *
1711 psppire_data_sheet_get_data_store (PsppireDataSheet *data_sheet)
1712 {
1713   return data_sheet->data_store;
1714 }
1715
1716 static void
1717 refresh_model (PsppireDataSheet *data_sheet)
1718 {
1719   pspp_sheet_view_set_model (PSPP_SHEET_VIEW (data_sheet), NULL);
1720
1721   if (data_sheet->data_store != NULL)
1722     {
1723       PsppireEmptyListStore *model;
1724       GtkAction *action;
1725       int n_rows;
1726
1727       n_rows = psppire_data_store_get_case_count (data_sheet->data_store) + 1;
1728       model = psppire_empty_list_store_new (n_rows);
1729       pspp_sheet_view_set_model (PSPP_SHEET_VIEW (data_sheet),
1730                                  GTK_TREE_MODEL (model));
1731       g_object_unref (model);
1732
1733       action = get_action_assert (data_sheet->builder, "edit_copy");
1734       g_signal_connect (action, "activate", G_CALLBACK (on_edit_copy),
1735                         data_sheet);
1736     }
1737 }
1738
1739 static void
1740 on_case_inserted (PsppireDataStore *data_store, gint row,
1741                   PsppireDataSheet *data_sheet)
1742 {
1743   PsppireEmptyListStore *empty_list_store;
1744   GtkTreeModel *tree_model;
1745   gint n_rows;
1746
1747   g_return_if_fail (data_store == data_sheet->data_store);
1748
1749   n_rows = psppire_data_store_get_case_count (data_store) + 1;
1750   if (row == n_rows - 1)
1751     row++;
1752
1753   tree_model = pspp_sheet_view_get_model (PSPP_SHEET_VIEW (data_sheet));
1754   empty_list_store = PSPPIRE_EMPTY_LIST_STORE (tree_model);
1755   psppire_empty_list_store_set_n_rows (empty_list_store, n_rows);
1756   psppire_empty_list_store_row_inserted (empty_list_store, row);
1757 }
1758
1759 static void
1760 on_cases_deleted (PsppireDataStore *data_store, gint first, gint n_cases,
1761                   PsppireDataSheet *data_sheet)
1762 {
1763
1764   g_return_if_fail (data_store == data_sheet->data_store);
1765
1766   if (n_cases > 1)
1767     {
1768       /* This is a bit of a cop-out.  We could do better, if it ever turns out
1769          that this performs too poorly. */
1770       refresh_model (data_sheet);
1771     }
1772   else
1773     {
1774       PsppireEmptyListStore *empty_list_store;
1775       GtkTreeModel *tree_model;
1776       gint n_rows = psppire_data_store_get_case_count (data_store) + 1;
1777
1778       tree_model = pspp_sheet_view_get_model (PSPP_SHEET_VIEW (data_sheet));
1779       empty_list_store = PSPPIRE_EMPTY_LIST_STORE (tree_model);
1780       psppire_empty_list_store_set_n_rows (empty_list_store, n_rows);
1781       psppire_empty_list_store_row_deleted (empty_list_store, first);
1782     }
1783 }
1784
1785 static void
1786 on_case_change (PsppireDataStore *data_store, gint row,
1787                 PsppireDataSheet *data_sheet)
1788 {
1789   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1790
1791   pspp_sheet_view_stop_editing (sheet_view, TRUE);
1792   gtk_widget_queue_draw (GTK_WIDGET (data_sheet));
1793 }
1794
1795 static void
1796 on_backend_changed (PsppireDataStore *data_store,
1797                     PsppireDataSheet *data_sheet)
1798 {
1799   g_return_if_fail (data_store == data_sheet->data_store);
1800   refresh_model (data_sheet);
1801 }
1802
1803 static void
1804 on_variable_display_width_changed (PsppireDict *dict, int dict_index,
1805                                    PsppireDataSheet *data_sheet)
1806 {
1807   PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
1808   PsppSheetViewColumn *column;
1809   struct variable *var;
1810   int display_width;
1811   gint pixel_width;
1812
1813   g_return_if_fail (data_sheet->data_store != NULL);
1814   g_return_if_fail (dict == data_sheet->data_store->dict);
1815
1816   column = psppire_data_sheet_find_column_for_variable (data_sheet,
1817                                                         dict_index);
1818   if (column == NULL)
1819     return;
1820
1821   var = psppire_dict_get_variable (data_store->dict, dict_index);
1822   g_return_if_fail (var != NULL);
1823
1824   pixel_width = pspp_sheet_view_column_get_fixed_width (column);
1825   display_width = display_width_from_pixel_width (data_sheet, pixel_width);
1826   if (display_width != var_get_display_width (var))
1827     {
1828       gint base_width, incr_width;
1829
1830       display_width = var_get_display_width (var);
1831       calc_width_conversion (data_sheet, &base_width, &incr_width);
1832       pixel_width = display_width_to_pixel_width (data_sheet, display_width,
1833                                                   base_width, incr_width);
1834       pspp_sheet_view_column_set_fixed_width (column, pixel_width);
1835     }
1836 }
1837
1838 static void
1839 on_variable_changed (PsppireDict *dict, int dict_index,
1840                      PsppireDataSheet *data_sheet)
1841 {
1842   PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
1843   PsppSheetViewColumn *column;
1844   GtkCellRenderer *cell;
1845   struct variable *var;
1846   GList *cells;
1847   char *name;
1848
1849   g_return_if_fail (data_sheet->data_store != NULL);
1850   g_return_if_fail (dict == data_sheet->data_store->dict);
1851
1852   column = psppire_data_sheet_find_column_for_variable (data_sheet,
1853                                                         dict_index);
1854   if (column == NULL)
1855     return;
1856
1857   var = psppire_dict_get_variable (data_store->dict, dict_index);
1858   g_return_if_fail (var != NULL);
1859
1860   name = escape_underscores (var_get_name (var));
1861   if (strcmp (name, pspp_sheet_view_column_get_title (column)))
1862     pspp_sheet_view_column_set_title (column, name);
1863   free (name);
1864
1865   cells = pspp_sheet_view_column_get_cell_renderers (column);
1866   g_return_if_fail (cells);
1867   cell = cells->data;
1868   g_list_free (cells);
1869
1870   if (var_has_value_labels (var) != GTK_IS_CELL_RENDERER_COMBO (cell))
1871     {
1872       /* Stop editing before we delete and replace the cell renderers.
1873          Otherwise if this column is currently being edited, an eventual call
1874          to pspp_sheet_view_stop_editing() will obtain a NULL cell and pass
1875          that to gtk_cell_renderer_stop_editing(), which causes a critical.
1876
1877          It's possible that this is a bug in PsppSheetView, and it's possible
1878          that PsppSheetView inherits that from GtkTreeView, but I haven't
1879          investigated yet. */
1880       pspp_sheet_view_stop_editing (PSPP_SHEET_VIEW (data_sheet), TRUE);
1881
1882       add_data_column_cell_renderer (data_sheet, column);
1883     }
1884 }
1885
1886 static void
1887 on_variable_inserted (PsppireDict *dict, int var_index,
1888                       PsppireDataSheet *data_sheet)
1889 {
1890   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1891   gint base_width, incr_width;
1892   PsppSheetViewColumn *column;
1893
1894   calc_width_conversion (data_sheet, &base_width, &incr_width);
1895   column = make_data_column (data_sheet, var_index, base_width, incr_width);
1896   pspp_sheet_view_insert_column (sheet_view, column, var_index + 1);
1897 }
1898
1899 static void
1900 on_variable_deleted (PsppireDict *dict,
1901                      const struct variable *var, int case_idx, int width,
1902                      PsppireDataSheet *data_sheet)
1903 {
1904   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1905   GList *columns, *iter;
1906
1907   columns = pspp_sheet_view_get_columns (sheet_view);
1908   for (iter = columns; iter != NULL; iter = iter->next)
1909     {
1910       PsppSheetViewColumn *column = iter->data;
1911       const struct variable *column_var;
1912
1913       column_var = g_object_get_data (G_OBJECT (column), "variable");
1914       if (column_var == var)
1915         pspp_sheet_view_remove_column (sheet_view, column);
1916     }
1917   g_list_free (columns);
1918 }
1919
1920 static void
1921 psppire_data_sheet_unset_data_store (PsppireDataSheet *data_sheet)
1922 {
1923   PsppireDataStore *store = data_sheet->data_store;
1924
1925   if (store == NULL)
1926     return;
1927
1928   data_sheet->data_store = NULL;
1929
1930   g_signal_handlers_disconnect_by_func (
1931     store, G_CALLBACK (on_backend_changed), data_sheet);
1932   g_signal_handlers_disconnect_by_func (
1933     store, G_CALLBACK (on_case_inserted), data_sheet);
1934   g_signal_handlers_disconnect_by_func (
1935     store, G_CALLBACK (on_cases_deleted), data_sheet);
1936   g_signal_handlers_disconnect_by_func (
1937     store, G_CALLBACK (on_case_change), data_sheet);
1938
1939   g_signal_handlers_disconnect_by_func (
1940     store->dict, G_CALLBACK (on_variable_changed), data_sheet);
1941   g_signal_handlers_disconnect_by_func (
1942     store->dict, G_CALLBACK (on_variable_display_width_changed), data_sheet);
1943   g_signal_handlers_disconnect_by_func (
1944     store->dict, G_CALLBACK (on_variable_inserted), data_sheet);
1945   g_signal_handlers_disconnect_by_func (
1946     store->dict, G_CALLBACK (on_variable_deleted), data_sheet);
1947
1948   g_object_unref (store);
1949 }
1950
1951 void
1952 psppire_data_sheet_set_data_store (PsppireDataSheet *data_sheet,
1953                                 PsppireDataStore *data_store)
1954 {
1955   psppire_data_sheet_unset_data_store (data_sheet);
1956
1957   data_sheet->data_store = data_store;
1958   if (data_store != NULL)
1959     {
1960       g_object_ref (data_store);
1961       g_signal_connect (data_store, "backend-changed",
1962                         G_CALLBACK (on_backend_changed), data_sheet);
1963       g_signal_connect (data_store, "case-inserted",
1964                         G_CALLBACK (on_case_inserted), data_sheet);
1965       g_signal_connect (data_store, "cases-deleted",
1966                         G_CALLBACK (on_cases_deleted), data_sheet);
1967       g_signal_connect (data_store, "case-changed",
1968                         G_CALLBACK (on_case_change), data_sheet);
1969
1970       /* XXX it's unclean to hook into the dict this way--what if the dict
1971          changes?  As of this writing, though, nothing ever changes the
1972          data_store's dict. */
1973       g_signal_connect (data_store->dict, "variable-changed",
1974                         G_CALLBACK (on_variable_changed),
1975                         data_sheet);
1976       g_signal_connect (data_store->dict, "variable-display-width-changed",
1977                         G_CALLBACK (on_variable_display_width_changed),
1978                         data_sheet);
1979       g_signal_connect (data_store->dict, "variable-inserted",
1980                         G_CALLBACK (on_variable_inserted), data_sheet);
1981       g_signal_connect (data_store->dict, "variable-deleted",
1982                         G_CALLBACK (on_variable_deleted), data_sheet);
1983     }
1984   refresh_model (data_sheet);
1985 }
1986 \f
1987 /* Clipboard stuff */
1988
1989 /* A casereader and dictionary holding the data currently in the clip */
1990 static struct casereader *clip_datasheet = NULL;
1991 static struct dictionary *clip_dict = NULL;
1992
1993
1994 static void psppire_data_sheet_update_clipboard (PsppireDataSheet *);
1995
1996 /* Set the clip from the currently selected range in DATA_SHEET.  If CUT is
1997    true, clears the original data from DATA_SHEET, otherwise leaves the
1998    original data in-place. */
1999 static void
2000 psppire_data_sheet_set_clip (PsppireDataSheet *data_sheet,
2001                              gboolean cut)
2002 {
2003   struct casewriter *writer ;
2004   PsppireDataStore *ds = psppire_data_sheet_get_data_store (data_sheet);
2005   struct case_map *map = NULL;
2006   struct range_set *rows, *cols;
2007   const struct range_set_node *node;
2008
2009   if (!psppire_data_sheet_get_selected_range (data_sheet, &rows, &cols))
2010     return;
2011
2012
2013   /* Destroy any existing clip */
2014   if ( clip_datasheet )
2015     {
2016       casereader_destroy (clip_datasheet);
2017       clip_datasheet = NULL;
2018     }
2019
2020   if ( clip_dict )
2021     {
2022       dict_destroy (clip_dict);
2023       clip_dict = NULL;
2024     }
2025
2026   /* Construct clip dictionary. */
2027   clip_dict = dict_create (dict_get_encoding (ds->dict->dict));
2028   RANGE_SET_FOR_EACH (node, cols)
2029     {
2030       int dict_index;
2031
2032       for (dict_index = node->start; dict_index < node->end; dict_index++)
2033         {
2034           struct variable *var = dict_get_var (ds->dict->dict, dict_index);
2035           dict_clone_var_assert (clip_dict, var);
2036         }
2037     }
2038
2039   /* Construct clip data. */
2040   map = case_map_by_name (ds->dict->dict, clip_dict);
2041   writer = autopaging_writer_create (dict_get_proto (clip_dict));
2042   RANGE_SET_FOR_EACH (node, rows)
2043     {
2044       unsigned long int row;
2045
2046       for (row = node->start; row < node->end; row++)
2047         {
2048           struct ccase *old = psppire_data_store_get_case (ds, row);
2049           if (old != NULL)
2050             casewriter_write (writer, case_map_execute (map, old));
2051           else
2052             casewriter_force_error (writer);
2053         }
2054     }
2055   case_map_destroy (map);
2056
2057   /* Clear data that we copied out, if we're doing a "cut". */
2058   if (cut && !casewriter_error (writer))
2059     {
2060       RANGE_SET_FOR_EACH (node, rows)
2061         {
2062           unsigned long int row;
2063
2064           for (row = node->start; row < node->end; row++)
2065             {
2066               const struct range_set_node *node2;
2067
2068               RANGE_SET_FOR_EACH (node2, cols)
2069                 {
2070                   int dict_index;
2071
2072                   for (dict_index = node2->start; dict_index < node2->end;
2073                        dict_index++)
2074                     {
2075                       struct variable *var;
2076
2077                       var = dict_get_var (ds->dict->dict, dict_index);
2078                       psppire_data_store_set_string (ds, "", row,
2079                                                      var, false);
2080                     }
2081                 }
2082             }
2083         }
2084     }
2085
2086   range_set_destroy (rows);
2087   range_set_destroy (cols);
2088
2089   clip_datasheet = casewriter_make_reader (writer);
2090
2091   psppire_data_sheet_update_clipboard (data_sheet);
2092 }
2093
2094 enum {
2095   SELECT_FMT_NULL,
2096   SELECT_FMT_TEXT,
2097   SELECT_FMT_HTML
2098 };
2099
2100
2101 /* Perform data_out for case CC, variable V, appending to STRING */
2102 static void
2103 data_out_g_string (GString *string, const struct variable *v,
2104                    const struct ccase *cc)
2105 {
2106   const struct fmt_spec *fs = var_get_print_format (v);
2107   const union value *val = case_data (cc, v);
2108
2109   char *s = data_out (val, var_get_encoding (v), fs);
2110
2111   g_string_append (string, s);
2112
2113   g_free (s);
2114 }
2115
2116 static GString *
2117 clip_to_text (void)
2118 {
2119   casenumber r;
2120   GString *string;
2121
2122   const size_t val_cnt = caseproto_get_n_widths (casereader_get_proto (clip_datasheet));
2123   const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
2124   const size_t var_cnt = dict_get_var_cnt (clip_dict);
2125
2126   string = g_string_sized_new (10 * val_cnt * case_cnt);
2127
2128   for (r = 0 ; r < case_cnt ; ++r )
2129     {
2130       int c;
2131       struct ccase *cc;
2132
2133       cc = casereader_peek (clip_datasheet, r);
2134       if (cc == NULL)
2135         {
2136           g_warning ("Clipboard seems to have inexplicably shrunk");
2137           break;
2138         }
2139
2140       for (c = 0 ; c < var_cnt ; ++c)
2141         {
2142           const struct variable *v = dict_get_var (clip_dict, c);
2143           data_out_g_string (string, v, cc);
2144           if ( c < val_cnt - 1 )
2145             g_string_append (string, "\t");
2146         }
2147
2148       if ( r < case_cnt)
2149         g_string_append (string, "\n");
2150
2151       case_unref (cc);
2152     }
2153
2154   return string;
2155 }
2156
2157
2158 static GString *
2159 clip_to_html (void)
2160 {
2161   casenumber r;
2162   GString *string;
2163
2164   const size_t val_cnt = caseproto_get_n_widths (casereader_get_proto (clip_datasheet));
2165   const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
2166   const size_t var_cnt = dict_get_var_cnt (clip_dict);
2167
2168   /* Guestimate the size needed */
2169   string = g_string_sized_new (80 + 20 * val_cnt * case_cnt);
2170
2171   g_string_append (string,
2172                    "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n");
2173
2174   g_string_append (string, "<table>\n");
2175   for (r = 0 ; r < case_cnt ; ++r )
2176     {
2177       int c;
2178       struct ccase *cc = casereader_peek (clip_datasheet, r);
2179       if (cc == NULL)
2180         {
2181           g_warning ("Clipboard seems to have inexplicably shrunk");
2182           break;
2183         }
2184       g_string_append (string, "<tr>\n");
2185
2186       for (c = 0 ; c < var_cnt ; ++c)
2187         {
2188           const struct variable *v = dict_get_var (clip_dict, c);
2189           g_string_append (string, "<td>");
2190           data_out_g_string (string, v, cc);
2191           g_string_append (string, "</td>\n");
2192         }
2193
2194       g_string_append (string, "</tr>\n");
2195
2196       case_unref (cc);
2197     }
2198   g_string_append (string, "</table>\n");
2199
2200   return string;
2201 }
2202
2203
2204
2205 static void
2206 psppire_data_sheet_clipboard_get_cb (GtkClipboard     *clipboard,
2207                                      GtkSelectionData *selection_data,
2208                                      guint             info,
2209                                      gpointer          data)
2210 {
2211   GString *string = NULL;
2212
2213   switch (info)
2214     {
2215     case SELECT_FMT_TEXT:
2216       string = clip_to_text ();
2217       break;
2218     case SELECT_FMT_HTML:
2219       string = clip_to_html ();
2220       break;
2221     default:
2222       g_assert_not_reached ();
2223     }
2224
2225   gtk_selection_data_set (selection_data, selection_data->target,
2226                           8,
2227                           (const guchar *) string->str, string->len);
2228
2229   g_string_free (string, TRUE);
2230 }
2231
2232 static void
2233 psppire_data_sheet_clipboard_clear_cb (GtkClipboard *clipboard,
2234                                        gpointer data)
2235 {
2236   dict_destroy (clip_dict);
2237   clip_dict = NULL;
2238
2239   casereader_destroy (clip_datasheet);
2240   clip_datasheet = NULL;
2241 }
2242
2243
2244 static const GtkTargetEntry targets[] = {
2245   { "UTF8_STRING",   0, SELECT_FMT_TEXT },
2246   { "STRING",        0, SELECT_FMT_TEXT },
2247   { "TEXT",          0, SELECT_FMT_TEXT },
2248   { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
2249   { "text/plain;charset=utf-8", 0, SELECT_FMT_TEXT },
2250   { "text/plain",    0, SELECT_FMT_TEXT },
2251   { "text/html",     0, SELECT_FMT_HTML }
2252 };
2253
2254
2255
2256 static void
2257 psppire_data_sheet_update_clipboard (PsppireDataSheet *sheet)
2258 {
2259   GtkClipboard *clipboard =
2260     gtk_widget_get_clipboard (GTK_WIDGET (sheet),
2261                               GDK_SELECTION_CLIPBOARD);
2262
2263   if (!gtk_clipboard_set_with_owner (clipboard, targets,
2264                                      G_N_ELEMENTS (targets),
2265                                      psppire_data_sheet_clipboard_get_cb,
2266                                      psppire_data_sheet_clipboard_clear_cb,
2267                                      G_OBJECT (sheet)))
2268     psppire_data_sheet_clipboard_clear_cb (clipboard, sheet);
2269 }
2270
2271 static void
2272 psppire_data_sheet_update_clip_actions (PsppireDataSheet *data_sheet)
2273 {
2274   struct range_set *rows, *cols;
2275   GtkAction *action;
2276   gboolean enable;
2277
2278   enable = psppire_data_sheet_get_selected_range (data_sheet, &rows, &cols);
2279   if (enable)
2280     {
2281       range_set_destroy (rows);
2282       range_set_destroy (cols);
2283     }
2284
2285   action = get_action_assert (data_sheet->builder, "edit_copy");
2286   gtk_action_set_sensitive (action, enable);
2287
2288   action = get_action_assert (data_sheet->builder, "edit_cut");
2289   gtk_action_set_sensitive (action, enable);
2290 }
2291