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