PsppireDataSheet: Reference handler improvements.
[pspp] / src / ui / gui / psppire-data-sheet.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2011, 2012 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "ui/gui/psppire-data-sheet.h"
20
21 #include "data/case-map.h"
22 #include "data/casereader.h"
23 #include "data/casewriter.h"
24 #include "data/data-out.h"
25 #include "data/datasheet.h"
26 #include "data/format.h"
27 #include "data/value-labels.h"
28 #include "libpspp/intern.h"
29 #include "libpspp/range-set.h"
30 #include "ui/gui/executor.h"
31 #include "ui/gui/find-dialog.h"
32 #include "ui/gui/goto-case-dialog.h"
33 #include "ui/gui/builder-wrapper.h"
34 #include "ui/gui/helper.h"
35 #include "ui/gui/pspp-sheet-selection.h"
36 #include "ui/gui/psppire-cell-renderer-button.h"
37 #include "ui/gui/psppire-data-store.h"
38 #include "ui/gui/psppire-data-window.h"
39 #include "ui/gui/psppire-dialog-action-var-info.h"
40 #include "ui/gui/psppire-empty-list-store.h"
41 #include "ui/gui/psppire-marshal.h"
42
43 #include "gl/intprops.h"
44 #include "gl/xalloc.h"
45
46 #include <gettext.h>
47 #define _(msgid) gettext (msgid)
48 #define N_(msgid) msgid
49
50 static void psppire_data_sheet_dispose (GObject *);
51 static void psppire_data_sheet_unset_data_store (PsppireDataSheet *);
52
53 static void psppire_data_sheet_update_clip_actions (PsppireDataSheet *);
54 static void psppire_data_sheet_set_clip (PsppireDataSheet *);
55
56 static void on_selection_changed (PsppSheetSelection *, gpointer);
57
58 G_DEFINE_TYPE (PsppireDataSheet, psppire_data_sheet, PSPP_TYPE_SHEET_VIEW);
59
60 static gboolean
61 get_tooltip_location (GtkWidget *widget, GtkTooltip *tooltip,
62                       gint wx, gint wy,
63                       size_t *row, PsppSheetViewColumn **columnp)
64 {
65   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
66   gint bx, by;
67   GtkTreePath *path;
68   GtkTreeIter iter;
69   PsppSheetViewColumn *tree_column;
70   GtkTreeModel *tree_model;
71   bool ok;
72
73   /* Check that WIDGET is really visible on the screen before we
74      do anything else.  This is a bug fix for a sticky situation:
75      when text_data_import_assistant() returns, it frees the data
76      necessary to compose the tool tip message, but there may be
77      a tool tip under preparation at that point (even if there is
78      no visible tool tip) that will call back into us a little
79      bit later.  Perhaps the correct solution to this problem is
80      to make the data related to the tool tips part of a GObject
81      that only gets destroyed when all references are released,
82      but this solution appears to be effective too. */
83   if (!gtk_widget_get_mapped (widget))
84     return FALSE;
85
86   pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view,
87                                                      wx, wy, &bx, &by);
88   if (!pspp_sheet_view_get_path_at_pos (tree_view, bx, by,
89                                       &path, &tree_column, NULL, NULL))
90     return FALSE;
91
92   *columnp = tree_column;
93
94   pspp_sheet_view_set_tooltip_cell (tree_view, tooltip, path, tree_column,
95                                     NULL);
96
97   tree_model = pspp_sheet_view_get_model (tree_view);
98   ok = gtk_tree_model_get_iter (tree_model, &iter, path);
99   gtk_tree_path_free (path);
100   if (!ok)
101     return FALSE;
102
103   *row = GPOINTER_TO_INT (iter.user_data);
104   return TRUE;
105 }
106
107 static gboolean
108 on_query_tooltip (GtkWidget *widget, gint wx, gint wy,
109                   gboolean keyboard_mode UNUSED,
110                   GtkTooltip *tooltip, gpointer data UNUSED)
111 {
112   PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (widget);
113   PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
114   PsppSheetViewColumn *column;
115   struct variable *var;
116   const char *label;
117   union value v;
118   size_t row;
119   int width;
120
121   g_return_val_if_fail (data_store != NULL, FALSE);
122
123   if (!get_tooltip_location (widget, tooltip, wx, wy, &row, &column))
124     return FALSE;
125
126   var = g_object_get_data (G_OBJECT (column), "variable");
127   if (var == NULL)
128     {
129       if (g_object_get_data (G_OBJECT (column), "new-var-column") == NULL)
130         return FALSE;
131
132       gtk_tooltip_set_text (tooltip,
133                             _("Enter a number to add a new variable."));
134       return TRUE;
135     }
136   else if (row >= datasheet_get_n_rows (data_store->datasheet))
137     {
138       gtk_tooltip_set_text (tooltip, _("Enter a number to add a new case."));
139       return TRUE;
140     }
141
142   width = var_get_width (var);
143
144   value_init (&v, width);
145   datasheet_get_value (data_store->datasheet, row, var_get_case_index (var),
146                        &v);
147
148   label = var_lookup_value_label (var, &v);
149   if (label != NULL)
150     {
151       if (data_sheet->show_value_labels)
152         {
153           char *s = value_to_text (v, var);
154           gtk_tooltip_set_text (tooltip, s);
155           free (s);
156         }
157       else
158         gtk_tooltip_set_text (tooltip, label);
159     }
160   value_destroy (&v, width);
161
162   return label != NULL;
163 }
164
165 static void
166 render_row_number_cell (PsppSheetViewColumn *tree_column,
167                         GtkCellRenderer *cell,
168                         GtkTreeModel *model,
169                         GtkTreeIter *iter,
170                         gpointer store_)
171 {
172   PsppireDataStore *store = store_;
173   GValue gvalue = { 0, };
174   gint row;
175
176   row = GPOINTER_TO_INT (iter->user_data);
177
178   g_value_init (&gvalue, G_TYPE_INT);
179   g_value_set_int (&gvalue, row + 1);
180   g_object_set_property (G_OBJECT (cell), "label", &gvalue);
181   g_value_unset (&gvalue);
182
183   if (row < datasheet_get_n_rows (store->datasheet))
184     g_object_set (cell, "editable", TRUE, NULL);
185   else
186     g_object_set (cell, "editable", FALSE, NULL);
187
188   g_object_set (cell,
189                 "slash", psppire_data_store_filtered (store, row),
190                 NULL);
191 }
192
193 static void
194 on_row_number_clicked (PsppireCellRendererButton *button,
195                        gchar *path_string,
196                        PsppSheetView *sheet_view)
197 {
198   PsppSheetSelection *selection;
199   GtkTreePath *path;
200
201   path = gtk_tree_path_new_from_string (path_string);
202
203   selection = pspp_sheet_view_get_selection (sheet_view);
204   pspp_sheet_selection_unselect_all (selection);
205   pspp_sheet_selection_select_path (selection, path);
206   pspp_sheet_selection_select_all_columns (selection);
207
208   gtk_tree_path_free (path);
209 }
210
211 static void
212 make_row_number_column (PsppireDataSheet *data_sheet,
213                         PsppireDataStore *ds)
214 {
215   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
216   PsppSheetViewColumn *column;
217   GtkCellRenderer *renderer;
218
219   renderer = psppire_cell_renderer_button_new ();
220   g_object_set (renderer, "xalign", 1.0, NULL);
221   g_signal_connect (renderer, "clicked", G_CALLBACK (on_row_number_clicked),
222                     sheet_view);
223
224   column = pspp_sheet_view_column_new_with_attributes (_("Case"),
225                                                        renderer, NULL);
226   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   if (data_sheet->uim == NULL)
1172     {
1173       data_sheet->uim = 
1174         GTK_UI_MANAGER (get_object_assert (data_sheet->builder,
1175                                            "data_sheet_uim",
1176                                            GTK_TYPE_UI_MANAGER));
1177       g_object_ref (data_sheet->uim);
1178     }
1179
1180   return data_sheet->uim;
1181 }
1182
1183 static void
1184 psppire_data_sheet_dispose (GObject *object)
1185 {
1186   PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (object);
1187
1188   if (data_sheet->dispose_has_run)
1189     return;
1190
1191   data_sheet->dispose_has_run = TRUE;
1192
1193   psppire_data_sheet_unset_data_store (data_sheet);
1194
1195   g_object_unref (data_sheet->builder);
1196
1197   if (data_sheet->uim)
1198     g_object_unref (data_sheet->uim);
1199
1200   G_OBJECT_CLASS (psppire_data_sheet_parent_class)->dispose (object);
1201 }
1202
1203 static void
1204 psppire_data_sheet_class_init (PsppireDataSheetClass *class)
1205 {
1206   GObjectClass *gobject_class;
1207
1208   gobject_class = G_OBJECT_CLASS (class);
1209   gobject_class->set_property = psppire_data_sheet_set_property;
1210   gobject_class->get_property = psppire_data_sheet_get_property;
1211
1212   gobject_class->dispose = psppire_data_sheet_dispose;
1213
1214   g_signal_new ("var-double-clicked",
1215                 G_OBJECT_CLASS_TYPE (gobject_class),
1216                 G_SIGNAL_RUN_LAST,
1217                 0,
1218                 g_signal_accumulator_true_handled, NULL,
1219                 psppire_marshal_BOOLEAN__INT,
1220                 G_TYPE_BOOLEAN, 1, G_TYPE_INT);
1221
1222   g_object_class_install_property (
1223     gobject_class, PROP_DATA_STORE,
1224     g_param_spec_object ("data-store",
1225                          "Data Store",
1226                          "The data store for the data sheet to display.",
1227                          PSPPIRE_TYPE_DATA_STORE,
1228                          G_PARAM_WRITABLE | G_PARAM_READABLE));
1229
1230   g_object_class_install_property (
1231     gobject_class, PROP_VALUE_LABELS,
1232     g_param_spec_boolean ("value-labels",
1233                           "Value Labels",
1234                           "Whether or not the data sheet should display labels instead of values",
1235                           FALSE,
1236                           G_PARAM_WRITABLE | G_PARAM_READABLE));
1237
1238   g_object_class_install_property (
1239     gobject_class, PROP_CASE_NUMBERS,
1240     g_param_spec_boolean ("case-numbers",
1241                           "Case Numbers",
1242                           "Whether or not the data sheet should display case numbers",
1243                           FALSE,
1244                           G_PARAM_WRITABLE | G_PARAM_READABLE));
1245
1246   g_object_class_install_property (
1247     gobject_class,
1248     PROP_CURRENT_CASE,
1249     g_param_spec_long ("current-case",
1250                        "Current Case",
1251                        "Zero based number of the selected case",
1252                        0, CASENUMBER_MAX,
1253                        0,
1254                        G_PARAM_WRITABLE | G_PARAM_READABLE));
1255
1256   g_object_class_install_property (
1257     gobject_class,
1258     PROP_MAY_CREATE_VARS,
1259     g_param_spec_boolean ("may-create-vars",
1260                           "May create variables",
1261                           "Whether the user may create more variables",
1262                           TRUE,
1263                           G_PARAM_READWRITE));
1264
1265   g_object_class_install_property (
1266     gobject_class,
1267     PROP_MAY_DELETE_VARS,
1268     g_param_spec_boolean ("may-delete-vars",
1269                           "May delete variables",
1270                           "Whether the user may delete variables",
1271                           TRUE,
1272                           G_PARAM_READWRITE));
1273
1274   g_object_class_install_property (
1275     gobject_class,
1276     PROP_UI_MANAGER,
1277     g_param_spec_object ("ui-manager",
1278                          "UI Manager",
1279                          "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.",
1280                          GTK_TYPE_UI_MANAGER,
1281                          G_PARAM_READABLE));
1282 }
1283
1284 static void
1285 do_popup_menu (GtkWidget *widget, guint button, guint32 time)
1286 {
1287   PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (widget);
1288   GtkWidget *menu;
1289
1290   menu = get_widget_assert (data_sheet->builder, "datasheet-cases-popup");
1291   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time);
1292 }
1293
1294 static void
1295 on_popup_menu (GtkWidget *widget, gpointer user_data UNUSED)
1296 {
1297   do_popup_menu (widget, 0, gtk_get_current_event_time ());
1298 }
1299
1300 static gboolean
1301 on_button_pressed (GtkWidget *widget, GdkEventButton *event,
1302                    gpointer user_data UNUSED)
1303 {
1304   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (widget);
1305
1306   if (event->type == GDK_BUTTON_PRESS && event->button == 3)
1307     {
1308       PsppSheetSelection *selection;
1309
1310       selection = pspp_sheet_view_get_selection (sheet_view);
1311       if (pspp_sheet_selection_count_selected_rows (selection) <= 1)
1312         {
1313           GtkTreePath *path;
1314
1315           if (pspp_sheet_view_get_path_at_pos (sheet_view, event->x, event->y,
1316                                                &path, NULL, NULL, NULL))
1317             {
1318               pspp_sheet_selection_unselect_all (selection);
1319               pspp_sheet_selection_select_path (selection, path);
1320               pspp_sheet_selection_select_all_columns (selection);
1321               gtk_tree_path_free (path);
1322             }
1323         }
1324
1325       do_popup_menu (widget, event->button, event->time);
1326
1327       return TRUE;
1328     }
1329
1330   return FALSE;
1331 }
1332
1333 static void
1334 on_edit_clear_cases (GtkAction *action, PsppireDataSheet *data_sheet)
1335 {
1336   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1337   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1338   const struct range_set_node *node;
1339   struct range_set *selected;
1340
1341   selected = pspp_sheet_selection_get_range_set (selection);
1342   for (node = range_set_last (selected); node != NULL;
1343        node = range_set_prev (selected, node))
1344     {
1345       unsigned long int start = range_set_node_get_start (node);
1346       unsigned long int count = range_set_node_get_width (node);
1347
1348       psppire_data_store_delete_cases (data_sheet->data_store, start, count);
1349     }
1350   range_set_destroy (selected);
1351 }
1352
1353 static void
1354 on_selection_changed (PsppSheetSelection *selection,
1355                       gpointer user_data UNUSED)
1356 {
1357   PsppSheetView *sheet_view = pspp_sheet_selection_get_tree_view (selection);
1358   PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (sheet_view);
1359   gint n_selected_rows;
1360   gboolean may_delete_cases, may_delete_vars, may_insert_vars;
1361   GList *list, *iter;
1362   GtkTreePath *path;
1363   GtkAction *action;
1364
1365   n_selected_rows = pspp_sheet_selection_count_selected_rows (selection);
1366
1367   action = get_action_assert (data_sheet->builder, "edit_insert-case");
1368   gtk_action_set_sensitive (action, n_selected_rows > 0);
1369
1370   switch (n_selected_rows)
1371     {
1372     case 0:
1373       may_delete_cases = FALSE;
1374       break;
1375
1376     case 1:
1377       /* The row used for inserting new cases cannot be deleted. */
1378       path = gtk_tree_path_new_from_indices (
1379         psppire_data_store_get_case_count (data_sheet->data_store), -1);
1380       may_delete_cases = !pspp_sheet_selection_path_is_selected (selection,
1381                                                                  path);
1382       gtk_tree_path_free (path);
1383       break;
1384
1385     default:
1386       may_delete_cases = TRUE;
1387       break;
1388     }
1389   action = get_action_assert (data_sheet->builder, "edit_clear-cases");
1390   gtk_action_set_sensitive (action, may_delete_cases);
1391
1392   may_delete_vars = may_insert_vars = FALSE;
1393   list = pspp_sheet_selection_get_selected_columns (selection);
1394   for (iter = list; iter != NULL; iter = iter->next)
1395     {
1396       PsppSheetViewColumn *column = iter->data;
1397       struct variable *var = g_object_get_data (G_OBJECT (column), "variable");
1398
1399       if (var != NULL)
1400         {
1401           may_delete_vars = may_insert_vars = TRUE;
1402           break;
1403         }
1404       if (g_object_get_data (G_OBJECT (column), "new-var-column") != NULL)
1405         may_insert_vars = TRUE;
1406     }
1407   g_list_free (list);
1408
1409   may_insert_vars = may_insert_vars && data_sheet->may_create_vars;
1410   may_delete_vars = may_delete_vars && data_sheet->may_delete_vars;
1411
1412   action = get_action_assert (data_sheet->builder, "edit_insert-variable");
1413   gtk_action_set_sensitive (action, may_insert_vars);
1414
1415   action = get_action_assert (data_sheet->builder, "edit_clear-variables");
1416   gtk_action_set_sensitive (action, may_delete_vars);
1417
1418   action = get_action_assert (data_sheet->builder, "sort-up");
1419   gtk_action_set_sensitive (action, may_delete_vars);
1420
1421   action = get_action_assert (data_sheet->builder, "sort-down");
1422   gtk_action_set_sensitive (action, may_delete_vars);
1423
1424   psppire_data_sheet_update_clip_actions (data_sheet);
1425 }
1426
1427 static gboolean
1428 psppire_data_sheet_get_selected_range (PsppireDataSheet *data_sheet,
1429                                     struct range_set **rowsp,
1430                                     struct range_set **colsp)
1431 {
1432   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1433   PsppireDataStore *data_store = data_sheet->data_store;
1434   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1435   unsigned long n_cases;
1436   struct range_set *rows, *cols;
1437   GList *list, *iter;
1438
1439   if (data_store == NULL)
1440     return FALSE;
1441   n_cases = psppire_data_store_get_case_count (data_store);
1442
1443   rows = pspp_sheet_selection_get_range_set (selection);
1444   range_set_set0 (rows, n_cases, ULONG_MAX - n_cases);
1445   if (range_set_is_empty (rows))
1446     {
1447       range_set_destroy (rows);
1448       return FALSE;
1449     }
1450
1451   cols = range_set_create ();
1452   list = pspp_sheet_selection_get_selected_columns (selection);
1453   for (iter = list; iter != NULL; iter = iter->next)
1454     {
1455       PsppSheetViewColumn *column = iter->data;
1456       struct variable *var = g_object_get_data (G_OBJECT (column), "variable");
1457
1458       if (var != NULL)
1459         range_set_set1 (cols, var_get_dict_index (var), 1);
1460     }
1461   g_list_free (list);
1462   if (range_set_is_empty (cols))
1463     {
1464       range_set_destroy (rows);
1465       range_set_destroy (cols);
1466       return FALSE;
1467     }
1468
1469   *rowsp = rows;
1470   *colsp = cols;
1471   return TRUE;
1472 }
1473
1474 static void
1475 on_edit_insert_case (GtkAction *action, PsppireDataSheet *data_sheet)
1476 {
1477   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1478   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1479   PsppireDataStore *data_store = data_sheet->data_store;
1480   struct range_set *selected;
1481   unsigned long row;
1482
1483   selected = pspp_sheet_selection_get_range_set (selection);
1484   row = range_set_scan (selected, 0);
1485   range_set_destroy (selected);
1486
1487   if (row <= psppire_data_store_get_case_count (data_store))
1488     psppire_data_store_insert_new_case (data_store, row);
1489 }
1490
1491 static void
1492 on_edit_insert_variable (GtkAction *action, PsppireDataSheet *data_sheet)
1493 {
1494   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1495   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1496   PsppireDict *dict = data_sheet->data_store->dict;
1497   PsppSheetViewColumn *column;
1498   struct variable *var;
1499   gchar name[64];
1500   GList *list;
1501   gint index;
1502
1503   list = pspp_sheet_selection_get_selected_columns (selection);
1504   if (list == NULL)
1505     return;
1506   column = list->data;
1507   g_list_free (list);
1508
1509   var = g_object_get_data (G_OBJECT (column), "variable");
1510   index = var ? var_get_dict_index (var) : psppire_dict_get_var_cnt (dict);
1511   if (psppire_dict_generate_name (dict, name, sizeof name))
1512     psppire_dict_insert_variable (dict, index, name);
1513 }
1514
1515 static void
1516 on_edit_clear_variables (GtkAction *action, PsppireDataSheet *data_sheet)
1517 {
1518   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1519   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1520   PsppireDict *dict = data_sheet->data_store->dict;
1521   GList *list, *iter;
1522
1523   list = pspp_sheet_selection_get_selected_columns (selection);
1524   if (list == NULL)
1525     return;
1526   list = g_list_reverse (list);
1527   for (iter = list; iter; iter = iter->next)
1528     {
1529       PsppSheetViewColumn *column = iter->data;
1530       struct variable *var;
1531
1532       var = g_object_get_data (G_OBJECT (column), "variable");
1533       if (var != NULL)
1534         psppire_dict_delete_variables (dict, var_get_dict_index (var), 1);
1535     }
1536   g_list_free (list);
1537 }
1538
1539 enum sort_order
1540   {
1541     SORT_ASCEND,
1542     SORT_DESCEND
1543   };
1544
1545 static void
1546 do_sort (PsppireDataSheet *data_sheet, enum sort_order order)
1547 {
1548   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1549   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1550   PsppireDataWindow *pdw;
1551   GList *list, *iter;
1552   GString *syntax;
1553   int n_vars;
1554
1555   pdw = psppire_data_window_for_data_store (data_sheet->data_store);
1556   g_return_if_fail (pdw != NULL);
1557
1558   list = pspp_sheet_selection_get_selected_columns (selection);
1559
1560   syntax = g_string_new ("SORT CASES BY");
1561   n_vars = 0;
1562   for (iter = list; iter; iter = iter->next)
1563     {
1564       PsppSheetViewColumn *column = iter->data;
1565       struct variable *var;
1566
1567       var = g_object_get_data (G_OBJECT (column), "variable");
1568       if (var != NULL)
1569         {
1570           g_string_append_printf (syntax, " %s", var_get_name (var));
1571           n_vars++;
1572         }
1573     }
1574   if (n_vars > 0)
1575     {
1576       if (order == SORT_DESCEND)
1577         g_string_append (syntax, " (DOWN)");
1578       g_string_append_c (syntax, '.');
1579       execute_const_syntax_string (pdw, syntax->str);
1580     }
1581   g_string_free (syntax, TRUE);
1582 }
1583
1584 void
1585 on_sort_up (GtkAction *action, PsppireDataSheet *data_sheet)
1586 {
1587   do_sort (data_sheet, SORT_ASCEND);
1588 }
1589
1590 void
1591 on_sort_down (GtkAction *action, PsppireDataSheet *data_sheet)
1592 {
1593   do_sort (data_sheet, SORT_DESCEND);
1594 }
1595
1596 void
1597 on_edit_goto_case (GtkAction *action, PsppireDataSheet *data_sheet)
1598 {
1599   goto_case_dialog (data_sheet);
1600 }
1601
1602 void
1603 on_edit_find (GtkAction *action, PsppireDataSheet *data_sheet)
1604 {
1605   PsppireDataWindow *pdw;
1606
1607   pdw = psppire_data_window_for_data_store (data_sheet->data_store);
1608   g_return_if_fail (pdw != NULL);
1609
1610   find_dialog (pdw);
1611 }
1612
1613 void
1614 on_edit_copy (GtkAction *action, PsppireDataSheet *data_sheet)
1615 {
1616   psppire_data_sheet_set_clip (data_sheet);
1617 }
1618
1619 static void
1620 psppire_data_sheet_init (PsppireDataSheet *obj)
1621 {
1622   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (obj);
1623   GtkAction *action;
1624
1625   obj->show_value_labels = FALSE;
1626   obj->show_case_numbers = TRUE;
1627   obj->may_create_vars = TRUE;
1628   obj->may_delete_vars = TRUE;
1629
1630   obj->scroll_to_bottom_signal = 0;
1631   obj->scroll_to_right_signal = 0;
1632   obj->new_variable_column = NULL;
1633   obj->container = NULL;
1634
1635   obj->uim = NULL;
1636   obj->dispose_has_run = FALSE;
1637
1638   pspp_sheet_view_set_special_cells (sheet_view, PSPP_SHEET_VIEW_SPECIAL_CELLS_YES);
1639
1640   g_signal_connect (obj, "notify::model",
1641                     G_CALLBACK (psppire_data_sheet_model_changed), NULL);
1642
1643   pspp_sheet_view_set_rubber_banding (sheet_view, TRUE);
1644   pspp_sheet_selection_set_mode (pspp_sheet_view_get_selection (sheet_view),
1645                                  PSPP_SHEET_SELECTION_RECTANGLE);
1646
1647   g_object_set (G_OBJECT (obj), "has-tooltip", TRUE, (void *) NULL);
1648   g_signal_connect (obj, "query-tooltip",
1649                     G_CALLBACK (on_query_tooltip), NULL);
1650   g_signal_connect (obj, "button-press-event",
1651                     G_CALLBACK (on_button_pressed), NULL);
1652   g_signal_connect (obj, "popup-menu", G_CALLBACK (on_popup_menu), NULL);
1653
1654   obj->builder = builder_new ("data-sheet.ui");
1655
1656   action = get_action_assert (obj->builder, "edit_clear-cases");
1657   g_signal_connect (action, "activate", G_CALLBACK (on_edit_clear_cases),
1658                     obj);
1659   gtk_action_set_sensitive (action, FALSE);
1660   g_signal_connect (pspp_sheet_view_get_selection (sheet_view),
1661                     "changed", G_CALLBACK (on_selection_changed), NULL);
1662
1663   action = get_action_assert (obj->builder, "edit_insert-case");
1664   g_signal_connect (action, "activate", G_CALLBACK (on_edit_insert_case),
1665                     obj);
1666
1667   action = get_action_assert (obj->builder, "edit_insert-variable");
1668   g_signal_connect (action, "activate", G_CALLBACK (on_edit_insert_variable),
1669                     obj);
1670
1671   action = get_action_assert (obj->builder, "edit_goto-case");
1672   g_signal_connect (action, "activate", G_CALLBACK (on_edit_goto_case),
1673                     obj);
1674
1675   action = get_action_assert (obj->builder, "edit_copy");
1676   g_signal_connect (action, "activate", G_CALLBACK (on_edit_copy), obj);
1677
1678   action = get_action_assert (obj->builder, "edit_clear-variables");
1679   g_signal_connect (action, "activate", G_CALLBACK (on_edit_clear_variables),
1680                     obj);
1681
1682   action = get_action_assert (obj->builder, "edit_find");
1683   g_signal_connect (action, "activate", G_CALLBACK (on_edit_find), obj);
1684
1685   action = get_action_assert (obj->builder, "sort-up");
1686   g_signal_connect (action, "activate", G_CALLBACK (on_sort_up), obj);
1687
1688   action = get_action_assert (obj->builder, "sort-down");
1689   g_signal_connect (action, "activate", G_CALLBACK (on_sort_down), obj);
1690
1691 }
1692
1693 GtkWidget *
1694 psppire_data_sheet_new (void)
1695 {
1696   return g_object_new (PSPP_TYPE_DATA_SHEET, NULL);
1697 }
1698
1699 PsppireDataStore *
1700 psppire_data_sheet_get_data_store (PsppireDataSheet *data_sheet)
1701 {
1702   return data_sheet->data_store;
1703 }
1704
1705 static void
1706 refresh_model (PsppireDataSheet *data_sheet)
1707 {
1708   pspp_sheet_view_set_model (PSPP_SHEET_VIEW (data_sheet), NULL);
1709
1710   if (data_sheet->data_store != NULL)
1711     {
1712       PsppireEmptyListStore *model;
1713       GtkAction *action;
1714       int n_rows;
1715
1716       n_rows = psppire_data_store_get_case_count (data_sheet->data_store) + 1;
1717       model = psppire_empty_list_store_new (n_rows);
1718       pspp_sheet_view_set_model (PSPP_SHEET_VIEW (data_sheet),
1719                                  GTK_TREE_MODEL (model));
1720       g_object_unref (model);
1721
1722       action = get_action_assert (data_sheet->builder, "edit_copy");
1723       g_signal_connect (action, "activate", G_CALLBACK (on_edit_copy),
1724                         data_sheet);
1725     }
1726 }
1727
1728 static void
1729 on_case_inserted (PsppireDataStore *data_store, gint row,
1730                   PsppireDataSheet *data_sheet)
1731 {
1732   PsppireEmptyListStore *empty_list_store;
1733   GtkTreeModel *tree_model;
1734   gint n_rows;
1735
1736   g_return_if_fail (data_store == data_sheet->data_store);
1737
1738   n_rows = psppire_data_store_get_case_count (data_store) + 1;
1739   if (row == n_rows - 1)
1740     row++;
1741
1742   tree_model = pspp_sheet_view_get_model (PSPP_SHEET_VIEW (data_sheet));
1743   empty_list_store = PSPPIRE_EMPTY_LIST_STORE (tree_model);
1744   psppire_empty_list_store_set_n_rows (empty_list_store, n_rows);
1745   psppire_empty_list_store_row_inserted (empty_list_store, row);
1746 }
1747
1748 static void
1749 on_cases_deleted (PsppireDataStore *data_store, gint first, gint n_cases,
1750                   PsppireDataSheet *data_sheet)
1751 {
1752
1753   g_return_if_fail (data_store == data_sheet->data_store);
1754
1755   if (n_cases > 1)
1756     {
1757       /* This is a bit of a cop-out.  We could do better, if it ever turns out
1758          that this performs too poorly. */
1759       refresh_model (data_sheet);
1760     }
1761   else
1762     {
1763       PsppireEmptyListStore *empty_list_store;
1764       GtkTreeModel *tree_model;
1765       gint n_rows = psppire_data_store_get_case_count (data_store) + 1;
1766
1767       tree_model = pspp_sheet_view_get_model (PSPP_SHEET_VIEW (data_sheet));
1768       empty_list_store = PSPPIRE_EMPTY_LIST_STORE (tree_model);
1769       psppire_empty_list_store_set_n_rows (empty_list_store, n_rows);
1770       psppire_empty_list_store_row_deleted (empty_list_store, first);
1771     }
1772 }
1773
1774 static void
1775 on_case_change (PsppireDataStore *data_store, gint row,
1776                 PsppireDataSheet *data_sheet)
1777 {
1778   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1779
1780   pspp_sheet_view_stop_editing (sheet_view, TRUE);
1781   gtk_widget_queue_draw (GTK_WIDGET (data_sheet));
1782 }
1783
1784 static void
1785 on_backend_changed (PsppireDataStore *data_store,
1786                     PsppireDataSheet *data_sheet)
1787 {
1788   g_return_if_fail (data_store == data_sheet->data_store);
1789   refresh_model (data_sheet);
1790 }
1791
1792 static void
1793 on_variable_display_width_changed (PsppireDict *dict, int dict_index,
1794                                    PsppireDataSheet *data_sheet)
1795 {
1796   PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
1797   PsppSheetViewColumn *column;
1798   struct variable *var;
1799   int display_width;
1800   gint pixel_width;
1801
1802   g_return_if_fail (data_sheet->data_store != NULL);
1803   g_return_if_fail (dict == data_sheet->data_store->dict);
1804
1805   column = psppire_data_sheet_find_column_for_variable (data_sheet,
1806                                                         dict_index);
1807   if (column == NULL)
1808     return;
1809
1810   var = psppire_dict_get_variable (data_store->dict, dict_index);
1811   g_return_if_fail (var != NULL);
1812
1813   pixel_width = pspp_sheet_view_column_get_fixed_width (column);
1814   display_width = display_width_from_pixel_width (data_sheet, pixel_width);
1815   if (display_width != var_get_display_width (var))
1816     {
1817       gint base_width, incr_width;
1818
1819       display_width = var_get_display_width (var);
1820       calc_width_conversion (data_sheet, &base_width, &incr_width);
1821       pixel_width = display_width_to_pixel_width (data_sheet, display_width,
1822                                                   base_width, incr_width);
1823       pspp_sheet_view_column_set_fixed_width (column, pixel_width);
1824     }
1825 }
1826
1827 static void
1828 on_variable_changed (PsppireDict *dict, int dict_index,
1829                      PsppireDataSheet *data_sheet)
1830 {
1831   PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
1832   PsppSheetViewColumn *column;
1833   GtkCellRenderer *cell;
1834   struct variable *var;
1835   GList *cells;
1836   char *name;
1837
1838   g_return_if_fail (data_sheet->data_store != NULL);
1839   g_return_if_fail (dict == data_sheet->data_store->dict);
1840
1841   column = psppire_data_sheet_find_column_for_variable (data_sheet,
1842                                                         dict_index);
1843   if (column == NULL)
1844     return;
1845
1846   var = psppire_dict_get_variable (data_store->dict, dict_index);
1847   g_return_if_fail (var != NULL);
1848
1849   name = escape_underscores (var_get_name (var));
1850   if (strcmp (name, pspp_sheet_view_column_get_title (column)))
1851     pspp_sheet_view_column_set_title (column, name);
1852   free (name);
1853
1854   cells = pspp_sheet_view_column_get_cell_renderers (column);
1855   g_return_if_fail (cells);
1856   cell = cells->data;
1857   g_list_free (cells);
1858
1859   if (var_has_value_labels (var) != GTK_IS_CELL_RENDERER_COMBO (cell))
1860     {
1861       /* Stop editing before we delete and replace the cell renderers.
1862          Otherwise if this column is currently being edited, an eventual call
1863          to pspp_sheet_view_stop_editing() will obtain a NULL cell and pass
1864          that to gtk_cell_renderer_stop_editing(), which causes a critical.
1865
1866          It's possible that this is a bug in PsppSheetView, and it's possible
1867          that PsppSheetView inherits that from GtkTreeView, but I haven't
1868          investigated yet. */
1869       pspp_sheet_view_stop_editing (PSPP_SHEET_VIEW (data_sheet), TRUE);
1870
1871       add_data_column_cell_renderer (data_sheet, column);
1872     }
1873 }
1874
1875 static void
1876 on_variable_inserted (PsppireDict *dict, int var_index,
1877                       PsppireDataSheet *data_sheet)
1878 {
1879   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1880   gint base_width, incr_width;
1881   PsppSheetViewColumn *column;
1882
1883   calc_width_conversion (data_sheet, &base_width, &incr_width);
1884   column = make_data_column (data_sheet, var_index, base_width, incr_width);
1885   pspp_sheet_view_insert_column (sheet_view, column, var_index + 1);
1886 }
1887
1888 static void
1889 on_variable_deleted (PsppireDict *dict,
1890                      const struct variable *var, int case_idx, int width,
1891                      PsppireDataSheet *data_sheet)
1892 {
1893   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1894   GList *columns, *iter;
1895
1896   columns = pspp_sheet_view_get_columns (sheet_view);
1897   for (iter = columns; iter != NULL; iter = iter->next)
1898     {
1899       PsppSheetViewColumn *column = iter->data;
1900       const struct variable *column_var;
1901
1902       column_var = g_object_get_data (G_OBJECT (column), "variable");
1903       if (column_var == var)
1904         pspp_sheet_view_remove_column (sheet_view, column);
1905     }
1906   g_list_free (columns);
1907 }
1908
1909 static void
1910 psppire_data_sheet_unset_data_store (PsppireDataSheet *data_sheet)
1911 {
1912   PsppireDataStore *store = data_sheet->data_store;
1913
1914   if (store == NULL)
1915     return;
1916
1917   data_sheet->data_store = NULL;
1918
1919   g_signal_handlers_disconnect_by_func (
1920     store, G_CALLBACK (on_backend_changed), data_sheet);
1921   g_signal_handlers_disconnect_by_func (
1922     store, G_CALLBACK (on_case_inserted), data_sheet);
1923   g_signal_handlers_disconnect_by_func (
1924     store, G_CALLBACK (on_cases_deleted), data_sheet);
1925   g_signal_handlers_disconnect_by_func (
1926     store, G_CALLBACK (on_case_change), data_sheet);
1927
1928   g_signal_handlers_disconnect_by_func (
1929     store->dict, G_CALLBACK (on_variable_changed), data_sheet);
1930   g_signal_handlers_disconnect_by_func (
1931     store->dict, G_CALLBACK (on_variable_display_width_changed), data_sheet);
1932   g_signal_handlers_disconnect_by_func (
1933     store->dict, G_CALLBACK (on_variable_inserted), data_sheet);
1934   g_signal_handlers_disconnect_by_func (
1935     store->dict, G_CALLBACK (on_variable_deleted), data_sheet);
1936
1937   g_object_unref (store);
1938 }
1939
1940 void
1941 psppire_data_sheet_set_data_store (PsppireDataSheet *data_sheet,
1942                                 PsppireDataStore *data_store)
1943 {
1944   psppire_data_sheet_unset_data_store (data_sheet);
1945
1946   data_sheet->data_store = data_store;
1947   if (data_store != NULL)
1948     {
1949       g_object_ref (data_store);
1950       g_signal_connect (data_store, "backend-changed",
1951                         G_CALLBACK (on_backend_changed), data_sheet);
1952       g_signal_connect (data_store, "case-inserted",
1953                         G_CALLBACK (on_case_inserted), data_sheet);
1954       g_signal_connect (data_store, "cases-deleted",
1955                         G_CALLBACK (on_cases_deleted), data_sheet);
1956       g_signal_connect (data_store, "case-changed",
1957                         G_CALLBACK (on_case_change), data_sheet);
1958
1959       /* XXX it's unclean to hook into the dict this way--what if the dict
1960          changes?  As of this writing, though, nothing ever changes the
1961          data_store's dict. */
1962       g_signal_connect (data_store->dict, "variable-changed",
1963                         G_CALLBACK (on_variable_changed),
1964                         data_sheet);
1965       g_signal_connect (data_store->dict, "variable-display-width-changed",
1966                         G_CALLBACK (on_variable_display_width_changed),
1967                         data_sheet);
1968       g_signal_connect (data_store->dict, "variable-inserted",
1969                         G_CALLBACK (on_variable_inserted), data_sheet);
1970       g_signal_connect (data_store->dict, "variable-deleted",
1971                         G_CALLBACK (on_variable_deleted), data_sheet);
1972     }
1973   refresh_model (data_sheet);
1974 }
1975 \f
1976 /* Clipboard stuff */
1977
1978 /* A casereader and dictionary holding the data currently in the clip */
1979 static struct casereader *clip_datasheet = NULL;
1980 static struct dictionary *clip_dict = NULL;
1981
1982
1983 static void psppire_data_sheet_update_clipboard (PsppireDataSheet *);
1984
1985 /* Set the clip according to the currently
1986    selected range in the data sheet */
1987 static void
1988 psppire_data_sheet_set_clip (PsppireDataSheet *data_sheet)
1989 {
1990   struct casewriter *writer ;
1991   PsppireDataStore *ds = psppire_data_sheet_get_data_store (data_sheet);
1992   struct case_map *map = NULL;
1993   struct range_set *rows, *cols;
1994   const struct range_set_node *node;
1995
1996   if (!psppire_data_sheet_get_selected_range (data_sheet, &rows, &cols))
1997     return;
1998
1999
2000   /* Destroy any existing clip */
2001   if ( clip_datasheet )
2002     {
2003       casereader_destroy (clip_datasheet);
2004       clip_datasheet = NULL;
2005     }
2006
2007   if ( clip_dict )
2008     {
2009       dict_destroy (clip_dict);
2010       clip_dict = NULL;
2011     }
2012
2013   /* Construct clip dictionary. */
2014   clip_dict = dict_create (dict_get_encoding (ds->dict->dict));
2015   RANGE_SET_FOR_EACH (node, cols)
2016     {
2017       int dict_index;
2018
2019       for (dict_index = node->start; dict_index < node->end; dict_index++)
2020         {
2021           struct variable *var = dict_get_var (ds->dict->dict, dict_index);
2022           dict_clone_var_assert (clip_dict, var);
2023         }
2024     }
2025
2026   /* Construct clip data. */
2027   map = case_map_by_name (ds->dict->dict, clip_dict);
2028   writer = autopaging_writer_create (dict_get_proto (clip_dict));
2029   RANGE_SET_FOR_EACH (node, rows)
2030     {
2031       unsigned long int row;
2032
2033       for (row = node->start; row < node->end; row++)
2034         {
2035           struct ccase *old = psppire_data_store_get_case (ds, row);
2036           if (old != NULL)
2037             casewriter_write (writer, case_map_execute (map, old));
2038           else
2039             casewriter_force_error (writer);
2040         }
2041     }
2042   case_map_destroy (map);
2043
2044   range_set_destroy (rows);
2045   range_set_destroy (cols);
2046
2047   clip_datasheet = casewriter_make_reader (writer);
2048
2049   psppire_data_sheet_update_clipboard (data_sheet);
2050 }
2051
2052 enum {
2053   SELECT_FMT_NULL,
2054   SELECT_FMT_TEXT,
2055   SELECT_FMT_HTML
2056 };
2057
2058
2059 /* Perform data_out for case CC, variable V, appending to STRING */
2060 static void
2061 data_out_g_string (GString *string, const struct variable *v,
2062                    const struct ccase *cc)
2063 {
2064   const struct fmt_spec *fs = var_get_print_format (v);
2065   const union value *val = case_data (cc, v);
2066
2067   char *s = data_out (val, var_get_encoding (v), fs);
2068
2069   g_string_append (string, s);
2070
2071   g_free (s);
2072 }
2073
2074 static GString *
2075 clip_to_text (void)
2076 {
2077   casenumber r;
2078   GString *string;
2079
2080   const size_t val_cnt = caseproto_get_n_widths (casereader_get_proto (clip_datasheet));
2081   const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
2082   const size_t var_cnt = dict_get_var_cnt (clip_dict);
2083
2084   string = g_string_sized_new (10 * val_cnt * case_cnt);
2085
2086   for (r = 0 ; r < case_cnt ; ++r )
2087     {
2088       int c;
2089       struct ccase *cc;
2090
2091       cc = casereader_peek (clip_datasheet, r);
2092       if (cc == NULL)
2093         {
2094           g_warning ("Clipboard seems to have inexplicably shrunk");
2095           break;
2096         }
2097
2098       for (c = 0 ; c < var_cnt ; ++c)
2099         {
2100           const struct variable *v = dict_get_var (clip_dict, c);
2101           data_out_g_string (string, v, cc);
2102           if ( c < val_cnt - 1 )
2103             g_string_append (string, "\t");
2104         }
2105
2106       if ( r < case_cnt)
2107         g_string_append (string, "\n");
2108
2109       case_unref (cc);
2110     }
2111
2112   return string;
2113 }
2114
2115
2116 static GString *
2117 clip_to_html (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   /* Guestimate the size needed */
2127   string = g_string_sized_new (80 + 20 * val_cnt * case_cnt);
2128
2129   g_string_append (string,
2130                    "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n");
2131
2132   g_string_append (string, "<table>\n");
2133   for (r = 0 ; r < case_cnt ; ++r )
2134     {
2135       int c;
2136       struct ccase *cc = casereader_peek (clip_datasheet, r);
2137       if (cc == NULL)
2138         {
2139           g_warning ("Clipboard seems to have inexplicably shrunk");
2140           break;
2141         }
2142       g_string_append (string, "<tr>\n");
2143
2144       for (c = 0 ; c < var_cnt ; ++c)
2145         {
2146           const struct variable *v = dict_get_var (clip_dict, c);
2147           g_string_append (string, "<td>");
2148           data_out_g_string (string, v, cc);
2149           g_string_append (string, "</td>\n");
2150         }
2151
2152       g_string_append (string, "</tr>\n");
2153
2154       case_unref (cc);
2155     }
2156   g_string_append (string, "</table>\n");
2157
2158   return string;
2159 }
2160
2161
2162
2163 static void
2164 psppire_data_sheet_clipboard_get_cb (GtkClipboard     *clipboard,
2165                                      GtkSelectionData *selection_data,
2166                                      guint             info,
2167                                      gpointer          data)
2168 {
2169   GString *string = NULL;
2170
2171   switch (info)
2172     {
2173     case SELECT_FMT_TEXT:
2174       string = clip_to_text ();
2175       break;
2176     case SELECT_FMT_HTML:
2177       string = clip_to_html ();
2178       break;
2179     default:
2180       g_assert_not_reached ();
2181     }
2182
2183   gtk_selection_data_set (selection_data, selection_data->target,
2184                           8,
2185                           (const guchar *) string->str, string->len);
2186
2187   g_string_free (string, TRUE);
2188 }
2189
2190 static void
2191 psppire_data_sheet_clipboard_clear_cb (GtkClipboard *clipboard,
2192                                        gpointer data)
2193 {
2194   dict_destroy (clip_dict);
2195   clip_dict = NULL;
2196
2197   casereader_destroy (clip_datasheet);
2198   clip_datasheet = NULL;
2199 }
2200
2201
2202 static const GtkTargetEntry targets[] = {
2203   { "UTF8_STRING",   0, SELECT_FMT_TEXT },
2204   { "STRING",        0, SELECT_FMT_TEXT },
2205   { "TEXT",          0, SELECT_FMT_TEXT },
2206   { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
2207   { "text/plain;charset=utf-8", 0, SELECT_FMT_TEXT },
2208   { "text/plain",    0, SELECT_FMT_TEXT },
2209   { "text/html",     0, SELECT_FMT_HTML }
2210 };
2211
2212
2213
2214 static void
2215 psppire_data_sheet_update_clipboard (PsppireDataSheet *sheet)
2216 {
2217   GtkClipboard *clipboard =
2218     gtk_widget_get_clipboard (GTK_WIDGET (sheet),
2219                               GDK_SELECTION_CLIPBOARD);
2220
2221   if (!gtk_clipboard_set_with_owner (clipboard, targets,
2222                                      G_N_ELEMENTS (targets),
2223                                      psppire_data_sheet_clipboard_get_cb,
2224                                      psppire_data_sheet_clipboard_clear_cb,
2225                                      G_OBJECT (sheet)))
2226     psppire_data_sheet_clipboard_clear_cb (clipboard, sheet);
2227 }
2228
2229 static void
2230 psppire_data_sheet_update_clip_actions (PsppireDataSheet *data_sheet)
2231 {
2232   struct range_set *rows, *cols;
2233   GtkAction *action;
2234   gboolean enable;
2235
2236   enable = psppire_data_sheet_get_selected_range (data_sheet, &rows, &cols);
2237   if (enable)
2238     {
2239       range_set_destroy (rows);
2240       range_set_destroy (cols);
2241     }
2242
2243   action = get_action_assert (data_sheet->builder, "edit_copy");
2244   gtk_action_set_sensitive (action, enable);
2245
2246   action = get_action_assert (data_sheet->builder, "edit_cut");
2247   gtk_action_set_sensitive (action, enable);
2248 }
2249