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