e0f104db1448c4e061b5aff7d5e2a710341653b5
[pspp] / src / ui / gui / psppire-var-sheet.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2008, 2009, 2011, 2012, 2013 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-var-sheet.h"
20
21 #include "data/format.h"
22 #include "data/value-labels.h"
23 #include "libpspp/range-set.h"
24 #include "ui/gui/builder-wrapper.h"
25 #include "ui/gui/helper.h"
26 #include "ui/gui/missing-val-dialog.h"
27 #include "ui/gui/pspp-sheet-selection.h"
28 #include "ui/gui/psppire-cell-renderer-button.h"
29 #include "ui/gui/psppire-data-editor.h"
30 #include "ui/gui/psppire-data-window.h"
31 #include "ui/gui/psppire-dialog-action-var-info.h"
32 #include "ui/gui/psppire-dictview.h"
33 #include "ui/gui/psppire-empty-list-store.h"
34 #include "ui/gui/psppire-marshal.h"
35 #include "ui/gui/val-labs-dialog.h"
36 #include "ui/gui/var-type-dialog.h"
37 #include "ui/gui/var-display.h"
38 #include "ui/gui/var-type-dialog.h"
39
40 #include "gl/intprops.h"
41
42 #include <gettext.h>
43 #define _(msgid) gettext (msgid)
44 #define N_(msgid) msgid
45
46 enum vs_column
47   {
48     VS_NAME,
49     VS_TYPE,
50     VS_WIDTH,
51     VS_DECIMALS,
52     VS_LABEL,
53     VS_VALUES,
54     VS_MISSING,
55     VS_COLUMNS,
56     VS_ALIGN,
57     VS_MEASURE,
58     VS_ROLE
59   };
60
61 G_DEFINE_TYPE (PsppireVarSheet, psppire_var_sheet, PSPP_TYPE_SHEET_VIEW);
62
63 static void
64 set_spin_cell (GtkCellRenderer *cell, int value, int min, int max, int step)
65 {
66   char text[INT_BUFSIZE_BOUND (int)];
67   GtkAdjustment *adjust;
68
69   if (max > min)
70     adjust = GTK_ADJUSTMENT (gtk_adjustment_new (value, min, max,
71                                                  step, step, 0.0));
72   else
73     adjust = NULL;
74
75   sprintf (text, "%d", value);
76   g_object_set (cell,
77                 "text", text,
78                 "adjustment", adjust,
79                 "editable", TRUE,
80                 NULL);
81 }
82
83 static void
84 error_dialog (GtkWindow *w, gchar *primary_text, gchar *secondary_text)
85 {
86   GtkWidget *dialog =
87     gtk_message_dialog_new (w,
88                             GTK_DIALOG_DESTROY_WITH_PARENT,
89                             GTK_MESSAGE_ERROR,
90                             GTK_BUTTONS_CLOSE, "%s", primary_text);
91
92   g_object_set (dialog, "icon-name", "psppicon", NULL);
93
94   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
95                                             "%s", secondary_text);
96
97   gtk_dialog_run (GTK_DIALOG (dialog));
98
99   gtk_widget_destroy (dialog);
100 }
101
102 static void
103 on_name_column_editing_started (GtkCellRenderer *cell,
104                                 GtkCellEditable *editable,
105                                 const gchar     *path,
106                                 gpointer         user_data)
107 {
108   PsppireVarSheet *var_sheet = g_object_get_data (G_OBJECT (cell), "var-sheet");
109   PsppireDict *dict = psppire_var_sheet_get_dictionary (var_sheet);
110
111   g_return_if_fail (var_sheet);
112           g_return_if_fail (dict);
113
114   if (GTK_IS_ENTRY (editable))
115     {
116       GtkEntry *entry = GTK_ENTRY (editable);
117       if (gtk_entry_get_text (entry)[0] == '\0')
118         {
119           gchar name[64];
120           if (psppire_dict_generate_name (dict, name, sizeof name))
121             gtk_entry_set_text (entry, name);
122         }
123     }
124 }
125
126 static void
127 scroll_to_bottom (GtkWidget      *widget,
128                   GtkRequisition *requisition,
129                   gpointer        unused UNUSED)
130 {
131   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (widget);
132   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (widget);
133   GtkAdjustment *vadjust;
134
135   vadjust = pspp_sheet_view_get_vadjustment (sheet_view);
136   gtk_adjustment_set_value (vadjust, gtk_adjustment_get_upper (vadjust));
137
138   if (var_sheet->scroll_to_bottom_signal)
139     {
140       g_signal_handler_disconnect (var_sheet,
141                                    var_sheet->scroll_to_bottom_signal);
142       var_sheet->scroll_to_bottom_signal = 0;
143     }
144 }
145
146 static struct variable *
147 path_string_to_var (PsppireVarSheet *var_sheet, gchar *path_string)
148 {
149   GtkTreePath *path;
150   gint row;
151
152   path = gtk_tree_path_new_from_string (path_string);
153   row = gtk_tree_path_get_indices (path)[0];
154   gtk_tree_path_free (path);
155
156   return psppire_dict_get_variable (var_sheet->dict, row);
157 }
158
159 static void
160 on_var_column_edited (GtkCellRendererText *cell,
161                       gchar               *path_string,
162                       gchar               *new_text,
163                       gpointer             user_data)
164 {
165   PsppireVarSheet *var_sheet = user_data;
166   GtkWindow *window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (var_sheet)));
167   struct dictionary *dict = var_sheet->dict->dict;
168   enum vs_column column_id;
169   struct variable *var;
170   int width, decimals;
171
172   column_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cell),
173                                                   "column-id"));
174
175   var = path_string_to_var (var_sheet, path_string);
176   if (var == NULL)
177     {
178       g_return_if_fail (column_id == VS_NAME);
179
180       if (!dict_id_is_valid (dict, new_text, false))
181         error_dialog (window,
182                       g_strdup (_("Cannot create variable.")),
183                       g_strdup_printf (_("\"%s\" is not a valid variable "
184                                          "name."), new_text));
185       else if (dict_lookup_var (dict, new_text) != NULL)
186         error_dialog (window,
187                       g_strdup (_("Cannot create variable.")),
188                       g_strdup_printf (_("This dictionary already contains "
189                                          "a variable named \"%s\"."),
190                                          new_text));
191       else
192         {
193           dict_create_var (var_sheet->dict->dict, new_text, 0);
194           if (!var_sheet->scroll_to_bottom_signal)
195             {
196               gtk_widget_queue_resize (GTK_WIDGET (var_sheet));
197               var_sheet->scroll_to_bottom_signal =
198                 g_signal_connect (var_sheet, "size-request",
199                                   G_CALLBACK (scroll_to_bottom), NULL);
200             }
201         }
202
203       return;
204     }
205
206   switch (column_id)
207     {
208     case VS_NAME:
209       if (!dict_id_is_valid (dict, new_text, false))
210         error_dialog (window,
211                       g_strdup (_("Cannot rename variable.")),
212                       g_strdup_printf (_("\"%s\" is not a valid variable "
213                                          "name."), new_text));
214       else if (dict_lookup_var (dict, new_text) != NULL
215                && dict_lookup_var (dict, new_text) != var)
216         error_dialog (window,
217                       g_strdup (_("Cannot rename variable.")),
218                       g_strdup_printf (_("This dictionary already contains "
219                                          "a variable named \"%s\"."),
220                                          new_text));
221       else
222         dict_rename_var (dict, var, new_text);
223       break;
224
225     case VS_TYPE:
226       /* Not reachable. */
227       break;
228
229     case VS_WIDTH:
230       width = atoi (new_text);
231       if (width > 0)
232         {
233           struct fmt_spec format;
234
235           format = *var_get_print_format (var);
236           fmt_change_width (&format, width, var_sheet->format_use);
237           var_set_width (var, fmt_var_width (&format));
238           var_set_both_formats (var, &format);
239         }
240       break;
241
242     case VS_DECIMALS:
243       decimals = atoi (new_text);
244       if (decimals >= 0)
245         {
246           struct fmt_spec format;
247
248           format = *var_get_print_format (var);
249           fmt_change_decimals (&format, decimals, var_sheet->format_use);
250           var_set_print_format (var, &format);
251         }
252       break;
253
254     case VS_LABEL:
255       var_set_label (var, new_text, false);
256       break;
257
258     case VS_VALUES:
259     case VS_MISSING:
260       break;
261
262     case VS_COLUMNS:
263       width = atoi (new_text);
264       if (width > 0 && width < 2 * MAX_STRING)
265         var_set_display_width (var, width);
266       break;
267
268     case VS_ALIGN:
269       if (!strcmp (new_text, alignment_to_string (ALIGN_LEFT)))
270         var_set_alignment (var, ALIGN_LEFT);
271       else if (!strcmp (new_text, alignment_to_string (ALIGN_CENTRE)))
272         var_set_alignment (var, ALIGN_CENTRE);
273       else if (!strcmp (new_text, alignment_to_string (ALIGN_RIGHT)))
274         var_set_alignment (var, ALIGN_RIGHT);
275       break;
276
277     case VS_MEASURE:
278       if (!strcmp (new_text, measure_to_string (MEASURE_NOMINAL)))
279         var_set_measure (var, MEASURE_NOMINAL);
280       else if (!strcmp (new_text, measure_to_string (MEASURE_ORDINAL)))
281         var_set_measure (var, MEASURE_ORDINAL);
282       else if (!strcmp (new_text, measure_to_string (MEASURE_SCALE)))
283         var_set_measure (var, MEASURE_SCALE);
284       break;
285
286     case VS_ROLE:
287       if (!strcmp (new_text, var_role_to_string (ROLE_INPUT)))
288         var_set_role (var, ROLE_INPUT);
289       else if (!strcmp (new_text, var_role_to_string (ROLE_OUTPUT)))
290         var_set_role (var, ROLE_OUTPUT);
291       else if (!strcmp (new_text, var_role_to_string (ROLE_BOTH)))
292         var_set_role (var, ROLE_BOTH);
293       else if (!strcmp (new_text, var_role_to_string (ROLE_NONE)))
294         var_set_role (var, ROLE_NONE);
295       else if (!strcmp (new_text, var_role_to_string (ROLE_PARTITION)))
296         var_set_role (var, ROLE_PARTITION);
297       else if (!strcmp (new_text, var_role_to_string (ROLE_SPLIT)))
298         var_set_role (var, ROLE_SPLIT);
299       break;
300     }
301 }
302
303 static void
304 render_popup_cell (PsppSheetViewColumn *tree_column,
305                    GtkCellRenderer *cell,
306                    GtkTreeModel *model,
307                    GtkTreeIter *iter,
308                    void *user_data)
309 {
310   PsppireVarSheet *var_sheet = user_data;
311   gint row;
312
313   row = GPOINTER_TO_INT (iter->user_data);
314   g_object_set (cell,
315                 "editable", row < psppire_dict_get_var_cnt (var_sheet->dict),
316                 NULL);
317 }
318
319 const char *
320 get_var_align_stock_id (enum alignment alignment)
321 {
322   switch (alignment)
323     {
324     case ALIGN_LEFT:
325       return GTK_STOCK_JUSTIFY_LEFT;
326
327     case ALIGN_CENTRE:
328       return GTK_STOCK_JUSTIFY_CENTER;
329
330     case ALIGN_RIGHT:
331       return GTK_STOCK_JUSTIFY_RIGHT;
332
333     default:
334       g_return_val_if_reached ("");
335     }
336 }
337
338 static void
339 render_var_cell (PsppSheetViewColumn *tree_column,
340                  GtkCellRenderer *cell,
341                  GtkTreeModel *model,
342                  GtkTreeIter *iter,
343                  void *user_data)
344 {
345   PsppireVarSheet *var_sheet = user_data;
346   const struct fmt_spec *print;
347   enum vs_column column_id;
348   struct variable *var;
349   gint row;
350
351   column_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
352                                                   "column-number")) - 1;
353   row = GPOINTER_TO_INT (iter->user_data);
354
355   if (row >= psppire_dict_get_var_cnt (var_sheet->dict))
356     {
357       if (GTK_IS_CELL_RENDERER_TEXT (cell))
358         {
359           g_object_set (cell,
360                         "text", "",
361                         "editable", column_id == VS_NAME,
362                         NULL);
363           if (column_id == VS_WIDTH
364               || column_id == VS_DECIMALS
365               || column_id == VS_COLUMNS)
366             g_object_set (cell, "adjustment", NULL, NULL);
367         }
368       else
369         g_object_set (cell, "stock-id", "", NULL);
370       return;
371     }
372
373   var = psppire_dict_get_variable (var_sheet->dict, row);
374
375   print = var_get_print_format (var);
376   switch (column_id)
377     {
378     case VS_NAME:
379       g_object_set (cell,
380                     "text", var_get_name (var),
381                     "editable", TRUE,
382                     NULL);
383       break;
384
385     case VS_TYPE:
386       g_object_set (cell,
387                     "text", fmt_gui_name (print->type),
388                     "editable", FALSE,
389                     NULL);
390       break;
391
392     case VS_WIDTH:
393       set_spin_cell (cell, print->w,
394                      fmt_min_width (print->type, var_sheet->format_use),
395                      fmt_max_width (print->type, var_sheet->format_use),
396                      fmt_step_width (print->type));
397       break;
398
399     case VS_DECIMALS:
400       if (fmt_takes_decimals (print->type))
401         {
402           int max_w = fmt_max_width (print->type, var_sheet->format_use);
403           int max_d = fmt_max_decimals (print->type, max_w,
404                                         var_sheet->format_use);
405           set_spin_cell (cell, print->d, 0, max_d, 1);
406         }
407       else
408         g_object_set (cell,
409                       "text", "",
410                       "editable", FALSE,
411                       "adjustment", NULL,
412                       NULL);
413       break;
414
415     case VS_LABEL:
416       g_object_set (cell,
417                     "text", var_has_label (var) ? var_get_label (var) : "",
418                     "editable", TRUE,
419                     NULL);
420       break;
421
422     case VS_VALUES:
423       g_object_set (cell, "editable", FALSE, NULL);
424       if ( ! var_has_value_labels (var))
425         g_object_set (cell, "text", _("None"), NULL);
426       else
427         {
428           const struct val_labs *vls = var_get_value_labels (var);
429           const struct val_lab **labels = val_labs_sorted (vls);
430           const struct val_lab *vl = labels[0];
431           gchar *vstr = value_to_text (vl->value, var);
432           char *text = xasprintf (_("{%s, %s}..."), vstr,
433                                   val_lab_get_escaped_label (vl));
434           free (vstr);
435
436           g_object_set (cell, "text", text, NULL);
437           free (text);
438           free (labels);
439         }
440       break;
441
442     case VS_MISSING:
443       {
444         char *text = missing_values_to_string (var_sheet->dict, var, NULL);
445         g_object_set (cell,
446                       "text", text,
447                       "editable", FALSE,
448                       NULL);
449         free (text);
450       }
451       break;
452
453     case VS_COLUMNS:
454       set_spin_cell (cell, var_get_display_width (var), 1, 2 * MAX_STRING, 1);
455       break;
456
457     case VS_ALIGN:
458       if (GTK_IS_CELL_RENDERER_TEXT (cell))
459         g_object_set (cell,
460                       "text", alignment_to_string (var_get_alignment (var)),
461                       "editable", TRUE,
462                       NULL);
463       else
464         g_object_set (cell, "stock-id",
465                       get_var_align_stock_id (var_get_alignment (var)), NULL);
466       break;
467
468     case VS_MEASURE:
469       if (GTK_IS_CELL_RENDERER_TEXT (cell))
470         g_object_set (cell,
471                       "text", measure_to_string (var_get_measure (var)),
472                       "editable", TRUE,
473                       NULL);
474       else
475         {
476           enum fmt_type type = var_get_print_format (var)->type;
477           enum measure measure = var_get_measure (var);
478
479           g_object_set (cell, "stock-id",
480                         get_var_measurement_stock_id (type, measure),
481                         NULL);
482         }
483       break;
484
485     case VS_ROLE:
486       g_object_set (cell,
487                     "text", var_role_to_string (var_get_role (var)),
488                     "editable", TRUE,
489                     NULL);
490       break;
491     }
492 }
493
494 static struct variable *
495 path_string_to_variable (PsppireVarSheet *var_sheet, gchar *path_string)
496 {
497   PsppireDict *dict;
498   GtkTreePath *path;
499   gint row;
500
501   path = gtk_tree_path_new_from_string (path_string);
502   row = gtk_tree_path_get_indices (path)[0];
503   gtk_tree_path_free (path);
504
505   dict = psppire_var_sheet_get_dictionary (var_sheet);
506   g_return_val_if_fail (dict != NULL, NULL);
507
508   return psppire_dict_get_variable (dict, row);
509 }
510
511 static void
512 on_type_click (PsppireCellRendererButton *cell,
513                gchar *path,
514                PsppireVarSheet *var_sheet)
515 {
516   GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (var_sheet));
517   struct fmt_spec format;
518   struct variable *var;
519
520   var = path_string_to_variable (var_sheet, path);
521   g_return_if_fail (var != NULL);
522
523   format = *var_get_print_format (var);
524   psppire_var_type_dialog_run (GTK_WINDOW (toplevel), &format);
525
526   var_set_width_and_formats (var, fmt_var_width (&format), &format, &format);
527 }
528
529 static void
530 on_value_labels_click (PsppireCellRendererButton *cell,
531                        gchar *path,
532                        PsppireVarSheet *var_sheet)
533 {
534   GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (var_sheet));
535   struct val_labs *labels;
536   struct variable *var;
537
538   var = path_string_to_variable (var_sheet, path);
539   g_return_if_fail (var != NULL);
540
541   labels = psppire_val_labs_dialog_run (GTK_WINDOW (toplevel), var);
542   if (labels)
543     {
544       var_set_value_labels (var, labels);
545       val_labs_destroy (labels);
546     }
547 }
548
549 static void
550 on_missing_values_click (PsppireCellRendererButton *cell,
551                          gchar *path,
552                          PsppireVarSheet *var_sheet)
553 {
554   GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (var_sheet));
555   struct missing_values mv;
556   struct variable *var;
557
558   var = path_string_to_variable (var_sheet, path);
559   g_return_if_fail (var != NULL);
560
561   psppire_missing_val_dialog_run (GTK_WINDOW (toplevel), var, &mv);
562   var_set_missing_values (var, &mv);
563   mv_destroy (&mv);
564 }
565
566 static gint
567 get_string_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
568                   const char *string)
569 {
570   gint width;
571   g_object_set (G_OBJECT (renderer),
572                 PSPPIRE_IS_CELL_RENDERER_BUTTON (renderer) ? "label" : "text",
573                 string, (void *) NULL);
574   gtk_cell_renderer_get_size (renderer, GTK_WIDGET (treeview),
575                               NULL, NULL, NULL, &width, NULL);
576   return width;
577 }
578
579 static gint
580 get_monospace_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
581                      size_t char_cnt)
582 {
583   struct string s;
584   gint width;
585
586   ds_init_empty (&s);
587   ds_put_byte_multiple (&s, '0', char_cnt);
588   ds_put_byte (&s, ' ');
589   width = get_string_width (treeview, renderer, ds_cstr (&s));
590   ds_destroy (&s);
591
592   return width;
593 }
594
595 static PsppSheetViewColumn *
596 add_var_sheet_column (PsppireVarSheet *var_sheet, GtkCellRenderer *renderer,
597                       enum vs_column column_id,
598                       const char *title, int width)
599 {
600   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
601   int title_width, content_width;
602   PsppSheetViewColumn *column;
603
604   column = pspp_sheet_view_column_new_with_attributes (title, renderer, NULL);
605   g_object_set_data (G_OBJECT (column), "column-number",
606                      GINT_TO_POINTER (column_id) + 1);
607
608   pspp_sheet_view_column_set_cell_data_func (
609     column, renderer, render_var_cell, var_sheet, NULL);
610
611   title_width = get_string_width (sheet_view, renderer, title);
612   content_width = get_monospace_width (sheet_view, renderer, width);
613   g_object_set_data (G_OBJECT (column), "content-width",
614                      GINT_TO_POINTER (content_width));
615
616   pspp_sheet_view_column_set_fixed_width (column,
617                                           MAX (title_width, content_width));
618   pspp_sheet_view_column_set_resizable (column, TRUE);
619
620   pspp_sheet_view_append_column (sheet_view, column);
621
622   g_signal_connect (renderer, "edited",
623                     G_CALLBACK (on_var_column_edited),
624                     var_sheet);
625   g_object_set_data (G_OBJECT (renderer), "column-id",
626                      GINT_TO_POINTER (column_id));
627   g_object_set_data (G_OBJECT (renderer), "var-sheet", var_sheet);
628
629   return column;
630 }
631
632 static PsppSheetViewColumn *
633 add_text_column (PsppireVarSheet *var_sheet, enum vs_column column_id,
634                  const char *title, int width)
635 {
636   return add_var_sheet_column (var_sheet, gtk_cell_renderer_text_new (),
637                                column_id, title, width);
638 }
639
640 static PsppSheetViewColumn *
641 add_spin_column (PsppireVarSheet *var_sheet, enum vs_column column_id,
642                  const char *title, int width)
643 {
644   return add_var_sheet_column (var_sheet, gtk_cell_renderer_spin_new (),
645                                column_id, title, width);
646 }
647
648 static const char *
649 measure_to_stock_id (enum fmt_type type, int measure)
650 {
651   return get_var_measurement_stock_id (type, measure);
652 }
653
654 static const char *
655 alignment_to_stock_id (enum fmt_type type, int alignment)
656 {
657   return get_var_align_stock_id (alignment);
658 }
659
660 static void
661 render_measure (GtkCellLayout *cell_layout,
662                 GtkCellRenderer *cell,
663                 GtkTreeModel *tree_model,
664                 GtkTreeIter *iter,
665                 gpointer data)
666 {
667   const char *(*value_to_stock_id) (enum fmt_type, int value);
668   enum fmt_type type = GPOINTER_TO_INT (data);
669   gint value;
670
671   value_to_stock_id = g_object_get_data (G_OBJECT (cell), "value-to-stock-id");
672
673   gtk_tree_model_get (tree_model, iter, 0, &value, -1);
674   g_object_set (cell, "stock-id", value_to_stock_id (type, value), NULL);
675 }
676
677 static void
678 on_combo_editing_started (GtkCellRenderer *renderer,
679                           GtkCellEditable *editable,
680                           gchar           *path_string,
681                           gpointer         user_data)
682 {
683   PsppireVarSheet *var_sheet = user_data;
684
685   if (GTK_IS_COMBO_BOX (editable))
686     {
687       struct variable *var = path_string_to_variable (var_sheet, path_string);
688       const struct fmt_spec *format = var_get_print_format (var);
689       const char *(*value_to_stock_id) (enum fmt_type, int value);
690       GtkCellRenderer *cell;
691
692       value_to_stock_id = g_object_get_data (G_OBJECT (renderer),
693                                              "value-to-stock-id");
694
695       cell = gtk_cell_renderer_pixbuf_new ();
696       g_object_set (cell, "width", 16, "height", 16, NULL);
697       g_object_set_data (G_OBJECT (cell),
698                          "value-to-stock-id", value_to_stock_id);
699       gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (editable), cell, FALSE);
700       gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (editable), cell,
701                                           render_measure,
702                                           GINT_TO_POINTER (format->type),
703                                           NULL);
704     }
705 }
706
707 static PsppSheetViewColumn *
708 add_combo_column (PsppireVarSheet *var_sheet, enum vs_column column_id,
709                   const char *title, int width,
710                   const char *(*value_to_stock_id) (enum fmt_type, int value),
711                   ...)
712 {
713   GtkCellRenderer *cell;
714   GtkListStore *store;
715   const char *name;
716   va_list args;
717
718   store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
719   va_start (args, value_to_stock_id);
720   while ((name = va_arg (args, const char *)) != NULL)
721     {
722       int value = va_arg (args, int);
723       gtk_list_store_insert_with_values (store, NULL, G_MAXINT,
724                                          0, value,
725                                          1, name,
726                                          -1);
727     }
728   va_end (args);
729
730   cell = gtk_cell_renderer_combo_new ();
731   g_object_set (cell,
732                 "has-entry", FALSE,
733                 "model", GTK_TREE_MODEL (store),
734                 "text-column", 1,
735                 NULL);
736   if (value_to_stock_id != NULL)
737     {
738       g_object_set_data (G_OBJECT (cell),
739                          "value-to-stock-id", value_to_stock_id);
740       g_signal_connect (cell, "editing-started",
741                         G_CALLBACK (on_combo_editing_started),
742                         var_sheet);
743     }
744
745   return add_var_sheet_column (var_sheet, cell, column_id, title, width);
746 }
747
748 static void
749 add_popup_menu (PsppireVarSheet *var_sheet,
750                 PsppSheetViewColumn *column,
751                 void (*on_click) (PsppireCellRendererButton *,
752                                   gchar *path,
753                                   PsppireVarSheet *var_sheet))
754 {
755   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
756   const char *button_label = "...";
757   GtkCellRenderer *button_renderer;
758   gint content_width;
759
760   button_renderer = psppire_cell_renderer_button_new ();
761   g_object_set (button_renderer,
762                 "label", button_label,
763                 "editable", TRUE,
764                 NULL);
765   g_signal_connect (button_renderer, "clicked", G_CALLBACK (on_click),
766                     var_sheet);
767   pspp_sheet_view_column_pack_start (column, button_renderer, FALSE);
768   pspp_sheet_view_column_set_cell_data_func (
769     column, button_renderer, render_popup_cell, var_sheet, NULL);
770
771   content_width = GPOINTER_TO_INT (g_object_get_data (
772                                      G_OBJECT (column), "content-width"));
773   content_width += get_string_width (sheet_view, button_renderer,
774                                      button_label);
775   if (content_width > pspp_sheet_view_column_get_fixed_width (column))
776     pspp_sheet_view_column_set_fixed_width (column, content_width);
777 }
778
779 static gboolean
780 get_tooltip_location (GtkWidget *widget, GtkTooltip *tooltip,
781                       gint wx, gint wy, size_t *row, size_t *column)
782 {
783   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
784   gint bx, by;
785   GtkTreePath *path;
786   GtkTreeIter iter;
787   PsppSheetViewColumn *tree_column;
788   GtkTreeModel *tree_model;
789   gpointer column_ptr;
790   bool ok;
791
792   /* Check that WIDGET is really visible on the screen before we
793      do anything else.  This is a bug fix for a sticky situation:
794      when text_data_import_assistant() returns, it frees the data
795      necessary to compose the tool tip message, but there may be
796      a tool tip under preparation at that point (even if there is
797      no visible tool tip) that will call back into us a little
798      bit later.  Perhaps the correct solution to this problem is
799      to make the data related to the tool tips part of a GObject
800      that only gets destroyed when all references are released,
801      but this solution appears to be effective too. */
802   if (!gtk_widget_get_mapped (widget))
803     return FALSE;
804
805   pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view,
806                                                      wx, wy, &bx, &by);
807   if (!pspp_sheet_view_get_path_at_pos (tree_view, bx, by,
808                                       &path, &tree_column, NULL, NULL))
809     return FALSE;
810
811   column_ptr = g_object_get_data (G_OBJECT (tree_column), "column-number");
812   if (column_ptr == NULL)
813     return FALSE;
814   *column = GPOINTER_TO_INT (column_ptr) - 1;
815
816   pspp_sheet_view_set_tooltip_cell (tree_view, tooltip, path, tree_column,
817                                     NULL);
818
819   tree_model = pspp_sheet_view_get_model (tree_view);
820   ok = gtk_tree_model_get_iter (tree_model, &iter, path);
821   gtk_tree_path_free (path);
822   if (!ok)
823     return FALSE;
824
825   *row = GPOINTER_TO_INT (iter.user_data);
826   return TRUE;
827 }
828
829 static gboolean
830 on_query_var_tooltip (GtkWidget *widget, gint wx, gint wy,
831                       gboolean keyboard_mode UNUSED,
832                       GtkTooltip *tooltip, gpointer *user_data UNUSED)
833 {
834   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (widget);
835   PsppireDict *dict;
836   struct variable *var;
837   size_t row, column;
838
839   if (!get_tooltip_location (widget, tooltip, wx, wy, &row, &column))
840     return FALSE;
841
842   dict = psppire_var_sheet_get_dictionary (var_sheet);
843   g_return_val_if_fail (dict != NULL, FALSE);
844
845   if (row >= psppire_dict_get_var_cnt (dict))
846     {
847       gtk_tooltip_set_text (tooltip, _("Enter a variable name to add a "
848                                        "new variable."));
849       return TRUE;
850     }
851
852   var = psppire_dict_get_variable (dict, row);
853   g_return_val_if_fail (var != NULL, FALSE);
854
855   switch (column)
856     {
857     case VS_TYPE:
858       {
859         char text[FMT_STRING_LEN_MAX + 1];
860
861         fmt_to_string (var_get_print_format (var), text);
862         gtk_tooltip_set_text (tooltip, text);
863         return TRUE;
864       }
865
866     case VS_VALUES:
867       if (var_has_value_labels (var))
868         {
869           const struct val_labs *vls = var_get_value_labels (var);
870           const struct val_lab **labels = val_labs_sorted (vls);
871           struct string s;
872           size_t i;
873
874           ds_init_empty (&s);
875           for (i = 0; i < val_labs_count (vls); i++)
876             {
877               const struct val_lab *vl = labels[i];
878               gchar *vstr;
879
880               if (i >= 10 || ds_length (&s) > 500)
881                 {
882                   ds_put_cstr (&s, "...");
883                   break;
884                 }
885
886               vstr = value_to_text (vl->value, var);
887               ds_put_format (&s, _("{%s, %s}\n"), vstr,
888                              val_lab_get_escaped_label (vl));
889               free (vstr);
890
891             }
892           ds_chomp_byte (&s, '\n');
893
894           gtk_tooltip_set_text (tooltip, ds_cstr (&s));
895           ds_destroy (&s);
896           free (labels);
897
898           return TRUE;
899         }
900     }
901
902   return FALSE;
903 }
904
905 static void
906 do_popup_menu (GtkWidget *widget, guint button, guint32 time)
907 {
908   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (widget);
909   GtkWidget *menu;
910
911   menu = get_widget_assert (var_sheet->builder, "varsheet-variable-popup");
912   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time);
913 }
914
915 static void
916 on_popup_menu (GtkWidget *widget, gpointer user_data UNUSED)
917 {
918   do_popup_menu (widget, 0, gtk_get_current_event_time ());
919 }
920
921 static gboolean
922 on_button_pressed (GtkWidget *widget, GdkEventButton *event,
923                    gpointer user_data UNUSED)
924 {
925   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (widget);
926
927   if (event->type == GDK_BUTTON_PRESS && event->button == 3)
928     {
929       PsppSheetSelection *selection;
930
931       selection = pspp_sheet_view_get_selection (sheet_view);
932       if (pspp_sheet_selection_count_selected_rows (selection) <= 1)
933         {
934           GtkTreePath *path;
935
936           if (pspp_sheet_view_get_path_at_pos (sheet_view, event->x, event->y,
937                                                &path, NULL, NULL, NULL))
938             {
939               pspp_sheet_selection_unselect_all (selection);
940               pspp_sheet_selection_select_path (selection, path);
941               gtk_tree_path_free (path);
942             }
943         }
944
945       do_popup_menu (widget, event->button, event->time);
946       return TRUE;
947     }
948
949   return FALSE;
950 }
951 \f
952 GType
953 psppire_fmt_use_get_type (void)
954 {
955   static GType etype = 0;
956   if (etype == 0)
957     {
958       static const GEnumValue values[] =
959         {
960           { FMT_FOR_INPUT, "FMT_FOR_INPUT", "input" },
961           { FMT_FOR_OUTPUT, "FMT_FOR_OUTPUT", "output" },
962           { 0, NULL, NULL }
963         };
964
965       etype = g_enum_register_static
966         (g_intern_static_string ("PsppireFmtUse"), values);
967     }
968   return etype;
969 }
970
971 enum
972   {
973     PROP_0,
974     PROP_DICTIONARY,
975     PROP_MAY_CREATE_VARS,
976     PROP_MAY_DELETE_VARS,
977     PROP_FORMAT_TYPE,
978     PROP_UI_MANAGER
979   };
980
981 static void
982 psppire_var_sheet_set_property (GObject      *object,
983                              guint         prop_id,
984                              const GValue *value,
985                              GParamSpec   *pspec)
986 {
987   PsppireVarSheet *obj = PSPPIRE_VAR_SHEET (object);
988
989   switch (prop_id)
990     {
991     case PROP_DICTIONARY:
992       psppire_var_sheet_set_dictionary (obj,
993                                         PSPPIRE_DICT (g_value_get_object (
994                                                         value)));
995       break;
996
997     case PROP_MAY_CREATE_VARS:
998       psppire_var_sheet_set_may_create_vars (obj,
999                                              g_value_get_boolean (value));
1000       break;
1001
1002     case PROP_MAY_DELETE_VARS:
1003       psppire_var_sheet_set_may_delete_vars (obj,
1004                                              g_value_get_boolean (value));
1005       break;
1006
1007     case PROP_FORMAT_TYPE:
1008       obj->format_use = g_value_get_enum (value);
1009       break;
1010
1011     case PROP_UI_MANAGER:
1012     default:
1013       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1014       break;
1015     }
1016 }
1017
1018 static void
1019 psppire_var_sheet_get_property (GObject      *object,
1020                                 guint         prop_id,
1021                                 GValue       *value,
1022                                 GParamSpec   *pspec)
1023 {
1024   PsppireVarSheet *obj = PSPPIRE_VAR_SHEET (object);
1025
1026   switch (prop_id)
1027     {
1028     case PROP_DICTIONARY:
1029       g_value_set_object (value,
1030                           G_OBJECT (psppire_var_sheet_get_dictionary (obj)));
1031       break;
1032
1033     case PROP_MAY_CREATE_VARS:
1034       g_value_set_boolean (value, obj->may_create_vars);
1035       break;
1036
1037     case PROP_MAY_DELETE_VARS:
1038       g_value_set_boolean (value, obj->may_delete_vars);
1039       break;
1040
1041     case PROP_FORMAT_TYPE:
1042       g_value_set_enum (value, obj->format_use);
1043       break;
1044
1045     case PROP_UI_MANAGER:
1046       g_value_set_object (value, psppire_var_sheet_get_ui_manager (obj));
1047       break;
1048
1049     default:
1050       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1051       break;
1052     }
1053 }
1054
1055 static void
1056 psppire_var_sheet_dispose (GObject *obj)
1057 {
1058   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (obj);
1059   int i;
1060
1061   if (var_sheet->dispose_has_run)
1062     return;
1063
1064   var_sheet->dispose_has_run = TRUE;
1065
1066   for (i = 0; i < PSPPIRE_VAR_SHEET_N_SIGNALS; i++)
1067     if ( var_sheet->dict_signals[i])
1068       g_signal_handler_disconnect (var_sheet->dict,
1069                                    var_sheet->dict_signals[i]);
1070
1071   if (var_sheet->dict)
1072     g_object_unref (var_sheet->dict);
1073   
1074   if (var_sheet->uim)
1075     g_object_unref (var_sheet->uim);
1076
1077   /* These dialogs are not GObjects (although they should be!)
1078     But for now, unreffing them only causes a GCritical Error
1079     so comment them out for now. (and accept the memory leakage)
1080
1081   g_object_unref (var_sheet->val_labs_dialog);
1082   g_object_unref (var_sheet->missing_val_dialog);
1083   g_object_unref (var_sheet->var_type_dialog);
1084   */
1085
1086   G_OBJECT_CLASS (psppire_var_sheet_parent_class)->dispose (obj);
1087 }
1088
1089 static void
1090 psppire_var_sheet_class_init (PsppireVarSheetClass *class)
1091 {
1092   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
1093   GParamSpec *pspec;
1094
1095   gobject_class->set_property = psppire_var_sheet_set_property;
1096   gobject_class->get_property = psppire_var_sheet_get_property;
1097   gobject_class->dispose = psppire_var_sheet_dispose;
1098
1099   g_signal_new ("var-double-clicked",
1100                 G_OBJECT_CLASS_TYPE (gobject_class),
1101                 G_SIGNAL_RUN_LAST,
1102                 0,
1103                 g_signal_accumulator_true_handled, NULL,
1104                 psppire_marshal_BOOLEAN__INT,
1105                 G_TYPE_BOOLEAN, 1, G_TYPE_INT);
1106
1107   pspec = g_param_spec_object ("dictionary",
1108                                "Dictionary displayed by the sheet",
1109                                "The PsppireDict that the sheet displays "
1110                                "may allow the user to edit",
1111                                PSPPIRE_TYPE_DICT,
1112                                G_PARAM_READWRITE);
1113   g_object_class_install_property (gobject_class, PROP_DICTIONARY, pspec);
1114
1115   pspec = g_param_spec_boolean ("may-create-vars",
1116                                 "May create variables",
1117                                 "Whether the user may create more variables",
1118                                 TRUE,
1119                                 G_PARAM_READWRITE);
1120   g_object_class_install_property (gobject_class, PROP_MAY_CREATE_VARS, pspec);
1121
1122   pspec = g_param_spec_boolean ("may-delete-vars",
1123                                 "May delete variables",
1124                                 "Whether the user may delete variables",
1125                                 TRUE,
1126                                 G_PARAM_READWRITE);
1127   g_object_class_install_property (gobject_class, PROP_MAY_DELETE_VARS, pspec);
1128
1129   pspec = g_param_spec_enum ("format-use",
1130                              "Use of variable format",
1131                              ("Whether variables have input or output "
1132                               "formats"),
1133                              PSPPIRE_TYPE_FMT_USE,
1134                              FMT_FOR_OUTPUT,
1135                              G_PARAM_READWRITE);
1136   g_object_class_install_property (gobject_class, PROP_FORMAT_TYPE, pspec);
1137
1138   pspec = g_param_spec_object ("ui-manager",
1139                                "UI Manager",
1140                                "UI manager for the variable sheet.  The client should merge this UI manager with the active UI manager to obtain variable sheet specific menu items and tool bar items.",
1141                                GTK_TYPE_UI_MANAGER,
1142                                G_PARAM_READABLE);
1143   g_object_class_install_property (gobject_class, PROP_UI_MANAGER, pspec);
1144 }
1145
1146 static void
1147 render_row_number_cell (PsppSheetViewColumn *tree_column,
1148                         GtkCellRenderer *cell,
1149                         GtkTreeModel *model,
1150                         GtkTreeIter *iter,
1151                         gpointer user_data)
1152 {
1153   PsppireVarSheet *var_sheet = user_data;
1154   GValue gvalue = { 0, };
1155   gint row;
1156
1157   row = GPOINTER_TO_INT (iter->user_data);
1158
1159   g_value_init (&gvalue, G_TYPE_INT);
1160   g_value_set_int (&gvalue, row + 1);
1161   g_object_set_property (G_OBJECT (cell), "label", &gvalue);
1162   g_value_unset (&gvalue);
1163
1164   if (!var_sheet->dict || row < psppire_dict_get_var_cnt (var_sheet->dict))
1165     g_object_set (cell, "editable", TRUE, NULL);
1166   else
1167     g_object_set (cell, "editable", FALSE, NULL);
1168 }
1169
1170 static void
1171 psppire_var_sheet_row_number_double_clicked (PsppireCellRendererButton *button,
1172                                              gchar *path_string,
1173                                              PsppireVarSheet *var_sheet)
1174 {
1175   GtkTreePath *path;
1176
1177   g_return_if_fail (var_sheet->dict != NULL);
1178
1179   path = gtk_tree_path_new_from_string (path_string);
1180   if (gtk_tree_path_get_depth (path) == 1)
1181     {
1182       gint *indices = gtk_tree_path_get_indices (path);
1183       if (indices[0] < psppire_dict_get_var_cnt (var_sheet->dict))
1184         {
1185           gboolean handled;
1186           g_signal_emit_by_name (var_sheet, "var-double-clicked",
1187                                  indices[0], &handled);
1188         }
1189     }
1190   gtk_tree_path_free (path);
1191 }
1192
1193 static void
1194 psppire_var_sheet_variables_column_clicked (PsppSheetViewColumn *column,
1195                                             PsppireVarSheet *var_sheet)
1196 {
1197   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
1198   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1199
1200   pspp_sheet_selection_select_all (selection);
1201 }
1202
1203 static PsppSheetViewColumn *
1204 make_row_number_column (PsppireVarSheet *var_sheet)
1205 {
1206   PsppSheetViewColumn *column;
1207   GtkCellRenderer *renderer;
1208
1209   renderer = psppire_cell_renderer_button_new ();
1210   g_object_set (renderer, "xalign", 1.0, NULL);
1211   g_signal_connect (renderer, "double-clicked",
1212                     G_CALLBACK (psppire_var_sheet_row_number_double_clicked),
1213                     var_sheet);
1214
1215   column = pspp_sheet_view_column_new_with_attributes (_("Variable"),
1216                                                        renderer, NULL);
1217   pspp_sheet_view_column_set_clickable (column, TRUE);
1218   pspp_sheet_view_column_set_cell_data_func (
1219     column, renderer, render_row_number_cell, var_sheet, NULL);
1220   pspp_sheet_view_column_set_fixed_width (column, 50);
1221   g_signal_connect (column, "clicked",
1222                     G_CALLBACK (psppire_var_sheet_variables_column_clicked),
1223                     var_sheet);
1224
1225   return column;
1226 }
1227
1228 static void
1229 on_edit_clear_variables (GtkAction *action, PsppireVarSheet *var_sheet)
1230 {
1231   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
1232   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1233   PsppireDict *dict = var_sheet->dict;
1234   const struct range_set_node *node;
1235   struct range_set *selected;
1236
1237   selected = pspp_sheet_selection_get_range_set (selection);
1238   for (node = range_set_last (selected); node != NULL;
1239        node = range_set_prev (selected, node))
1240     {
1241       int i;
1242
1243       for (i = 1; i <= range_set_node_get_width (node); i++)
1244         {
1245           unsigned long row = range_set_node_get_end (node) - i;
1246           if (row >= 0 && row < psppire_dict_get_var_cnt (dict))
1247             psppire_dict_delete_variables (dict, row, 1);
1248         }
1249     }
1250   range_set_destroy (selected);
1251 }
1252
1253 static void
1254 on_selection_changed (PsppSheetSelection *selection,
1255                       gpointer user_data UNUSED)
1256 {
1257   PsppSheetView *sheet_view = pspp_sheet_selection_get_tree_view (selection);
1258   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (sheet_view);
1259   gint n_selected_rows;
1260   gboolean may_delete;
1261   GtkTreePath *path;
1262   GtkAction *action;
1263
1264   n_selected_rows = pspp_sheet_selection_count_selected_rows (selection);
1265
1266   action = get_action_assert (var_sheet->builder, "edit_insert-variable");
1267   gtk_action_set_sensitive (action, (var_sheet->may_create_vars
1268                                      && n_selected_rows > 0));
1269
1270   switch (n_selected_rows)
1271     {
1272     case 0:
1273       may_delete = FALSE;
1274       break;
1275
1276     case 1:
1277       /* The row used for inserting new variables cannot be deleted. */
1278       path = gtk_tree_path_new_from_indices (
1279         psppire_dict_get_var_cnt (var_sheet->dict), -1);
1280       may_delete = !pspp_sheet_selection_path_is_selected (selection, path);
1281       gtk_tree_path_free (path);
1282       break;
1283
1284     default:
1285       may_delete = TRUE;
1286       break;
1287     }
1288   action = get_action_assert (var_sheet->builder, "edit_clear-variables");
1289   gtk_action_set_sensitive (action, var_sheet->may_delete_vars && may_delete);
1290 }
1291
1292 static void
1293 on_edit_insert_variable (GtkAction *action, PsppireVarSheet *var_sheet)
1294 {
1295   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
1296   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1297   PsppireDict *dict = var_sheet->dict;
1298   struct range_set *selected;
1299   unsigned long row;
1300
1301   selected = pspp_sheet_selection_get_range_set (selection);
1302   row = range_set_scan (selected, 0);
1303   range_set_destroy (selected);
1304
1305   if (row <= psppire_dict_get_var_cnt (dict))
1306     {
1307       gchar name[64];;
1308       if (psppire_dict_generate_name (dict, name, sizeof name))
1309         psppire_dict_insert_variable (dict, row, name);
1310     }
1311 }
1312
1313 static void
1314 psppire_var_sheet_init (PsppireVarSheet *obj)
1315 {
1316   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (obj);
1317   PsppSheetViewColumn *column;
1318   GtkCellRenderer *cell;
1319   GtkAction *action;
1320   GList *list;
1321
1322   obj->dict = NULL;
1323   obj->format_use = FMT_FOR_OUTPUT;
1324   obj->may_create_vars = TRUE;
1325   obj->may_delete_vars = TRUE;
1326
1327   obj->scroll_to_bottom_signal = 0;
1328
1329   obj->container = NULL;
1330   obj->dispose_has_run = FALSE;
1331   obj->uim = NULL;
1332
1333   pspp_sheet_view_append_column (sheet_view, make_row_number_column (obj));
1334
1335   column = add_text_column (obj, VS_NAME, _("Name"), 12);
1336   list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
1337   g_signal_connect (list->data, "editing-started",
1338                     G_CALLBACK (on_name_column_editing_started), NULL);
1339   g_list_free (list);
1340
1341   column = add_text_column (obj, VS_TYPE, _("Type"), 8);
1342   add_popup_menu (obj, column, on_type_click);
1343
1344   add_spin_column (obj, VS_WIDTH, _("Width"), 5);
1345
1346   add_spin_column (obj, VS_DECIMALS, _("Decimals"), 2);
1347
1348   add_text_column (obj, VS_LABEL, _("Label"), 20);
1349
1350   column = add_text_column (obj, VS_VALUES, _("Value Labels"), 20);
1351   add_popup_menu (obj, column, on_value_labels_click);
1352
1353   column = add_text_column (obj, VS_MISSING, _("Missing Values"), 20);
1354   add_popup_menu (obj, column, on_missing_values_click);
1355
1356   add_spin_column (obj, VS_COLUMNS, _("Columns"), 3);
1357
1358   column
1359    = add_combo_column (obj, VS_ALIGN, _("Align"), 8, alignment_to_stock_id,
1360                        alignment_to_string (ALIGN_LEFT), ALIGN_LEFT,
1361                        alignment_to_string (ALIGN_CENTRE), ALIGN_CENTRE,
1362                        alignment_to_string (ALIGN_RIGHT), ALIGN_RIGHT,
1363                        NULL);
1364   cell = gtk_cell_renderer_pixbuf_new ();
1365   g_object_set (cell, "width", 16, "height", 16, NULL);
1366   pspp_sheet_view_column_pack_end (column, cell, FALSE);
1367   pspp_sheet_view_column_set_cell_data_func (
1368     column, cell, render_var_cell, obj, NULL);
1369
1370   column
1371     = add_combo_column (obj, VS_MEASURE, _("Measure"), 12, measure_to_stock_id,
1372                         measure_to_string (MEASURE_NOMINAL), MEASURE_NOMINAL,
1373                         measure_to_string (MEASURE_ORDINAL), MEASURE_ORDINAL,
1374                         measure_to_string (MEASURE_SCALE), MEASURE_SCALE,
1375                         NULL);
1376   cell = gtk_cell_renderer_pixbuf_new ();
1377   g_object_set (cell, "width", 16, "height", 16, NULL);
1378   pspp_sheet_view_column_pack_end (column, cell, FALSE);
1379   pspp_sheet_view_column_set_cell_data_func (
1380     column, cell, render_var_cell, obj, NULL);
1381
1382   add_combo_column (obj, VS_ROLE, _("Role"), 12, NULL,
1383                     var_role_to_string (ROLE_INPUT), ROLE_INPUT,
1384                     var_role_to_string (ROLE_OUTPUT), ROLE_OUTPUT,
1385                     var_role_to_string (ROLE_BOTH), ROLE_BOTH,
1386                     var_role_to_string (ROLE_NONE), ROLE_NONE,
1387                     var_role_to_string (ROLE_PARTITION), ROLE_PARTITION,
1388                     var_role_to_string (ROLE_SPLIT), ROLE_SPLIT,
1389                     NULL);
1390
1391   pspp_sheet_view_set_rubber_banding (sheet_view, TRUE);
1392   pspp_sheet_selection_set_mode (pspp_sheet_view_get_selection (sheet_view),
1393                                  PSPP_SHEET_SELECTION_MULTIPLE);
1394
1395   g_object_set (G_OBJECT (obj), "has-tooltip", TRUE, NULL);
1396   g_signal_connect (obj, "query-tooltip",
1397                     G_CALLBACK (on_query_var_tooltip), NULL);
1398   g_signal_connect (obj, "button-press-event",
1399                     G_CALLBACK (on_button_pressed), NULL);
1400   g_signal_connect (obj, "popup-menu", G_CALLBACK (on_popup_menu), NULL);
1401
1402   obj->builder = builder_new ("var-sheet.ui");
1403
1404   action = get_action_assert (obj->builder, "edit_clear-variables");
1405   g_signal_connect (action, "activate", G_CALLBACK (on_edit_clear_variables),
1406                     obj);
1407   gtk_action_set_sensitive (action, FALSE);
1408   g_signal_connect (pspp_sheet_view_get_selection (sheet_view),
1409                     "changed", G_CALLBACK (on_selection_changed), NULL);
1410
1411   action = get_action_assert (obj->builder, "edit_insert-variable");
1412   gtk_action_set_sensitive (action, FALSE);
1413   g_signal_connect (action, "activate", G_CALLBACK (on_edit_insert_variable),
1414                     obj);
1415 }
1416
1417 GtkWidget *
1418 psppire_var_sheet_new (void)
1419 {
1420   return g_object_new (PSPPIRE_VAR_SHEET_TYPE, NULL);
1421 }
1422
1423 PsppireDict *
1424 psppire_var_sheet_get_dictionary (PsppireVarSheet *var_sheet)
1425 {
1426   return var_sheet->dict;
1427 }
1428
1429 static void
1430 refresh_model (PsppireVarSheet *var_sheet)
1431 {
1432   pspp_sheet_view_set_model (PSPP_SHEET_VIEW (var_sheet), NULL);
1433
1434   if (var_sheet->dict != NULL)
1435     {
1436       PsppireEmptyListStore *store;
1437       int n_rows;
1438
1439       n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
1440                 + var_sheet->may_create_vars);
1441       store = psppire_empty_list_store_new (n_rows);
1442       pspp_sheet_view_set_model (PSPP_SHEET_VIEW (var_sheet),
1443                                  GTK_TREE_MODEL (store));
1444       g_object_unref (store);
1445     }
1446 }
1447
1448 static void
1449 on_var_changed (PsppireDict *dict, glong row,
1450                 guint what, const struct variable *oldvar,
1451                 PsppireVarSheet *var_sheet)
1452 {
1453   PsppireEmptyListStore *store;
1454
1455   g_return_if_fail (dict == var_sheet->dict);
1456
1457   store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
1458                                       PSPP_SHEET_VIEW (var_sheet)));
1459   g_return_if_fail (store != NULL);
1460
1461   psppire_empty_list_store_row_changed (store, row);
1462 }
1463
1464 static void
1465 on_var_inserted (PsppireDict *dict, glong row, PsppireVarSheet *var_sheet)
1466 {
1467   PsppireEmptyListStore *store;
1468   int n_rows;
1469
1470   g_return_if_fail (dict == var_sheet->dict);
1471
1472   store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
1473                                       PSPP_SHEET_VIEW (var_sheet)));
1474   g_return_if_fail (store != NULL);
1475
1476   n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
1477             + var_sheet->may_create_vars);
1478   psppire_empty_list_store_set_n_rows (store, n_rows);
1479   psppire_empty_list_store_row_inserted (store, row);
1480 }
1481
1482 static void
1483 on_var_deleted (PsppireDict *dict,
1484                 const struct variable *var, int dict_idx, int case_idx,
1485                 PsppireVarSheet *var_sheet)
1486 {
1487   PsppireEmptyListStore *store;
1488   int n_rows;
1489
1490   g_return_if_fail (dict == var_sheet->dict);
1491
1492   store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
1493                                       PSPP_SHEET_VIEW (var_sheet)));
1494   g_return_if_fail (store != NULL);
1495
1496   n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
1497             + var_sheet->may_create_vars);
1498   psppire_empty_list_store_set_n_rows (store, n_rows);
1499   psppire_empty_list_store_row_deleted (store, dict_idx);
1500 }
1501
1502 static void
1503 on_backend_changed (PsppireDict *dict, PsppireVarSheet *var_sheet)
1504 {
1505   g_return_if_fail (dict == var_sheet->dict);
1506   refresh_model (var_sheet);
1507 }
1508
1509 void
1510 psppire_var_sheet_set_dictionary (PsppireVarSheet *var_sheet,
1511                                   PsppireDict *dict)
1512 {
1513   if (var_sheet->dict != NULL)
1514     {
1515       int i;
1516       
1517       for (i = 0; i < PSPPIRE_VAR_SHEET_N_SIGNALS; i++)
1518         {
1519           if (var_sheet->dict_signals[i])
1520             g_signal_handler_disconnect (var_sheet->dict,
1521                                          var_sheet->dict_signals[i]);
1522           
1523           var_sheet->dict_signals[i] = 0;
1524         }
1525
1526       g_object_unref (var_sheet->dict);
1527     }
1528
1529   var_sheet->dict = dict;
1530
1531   if (dict != NULL)
1532     {
1533       g_object_ref (dict);
1534
1535       var_sheet->dict_signals[PSPPIRE_VAR_SHEET_BACKEND_CHANGED]
1536         = g_signal_connect (dict, "backend-changed",
1537                             G_CALLBACK (on_backend_changed), var_sheet);
1538
1539       var_sheet->dict_signals[PSPPIRE_VAR_SHEET_VARIABLE_CHANGED]
1540         = g_signal_connect (dict, "variable-changed",
1541                             G_CALLBACK (on_var_changed), var_sheet);
1542
1543       var_sheet->dict_signals[PSPPIRE_VAR_SHEET_VARIABLE_DELETED]
1544         = g_signal_connect (dict, "variable-inserted",
1545                             G_CALLBACK (on_var_inserted), var_sheet);
1546
1547       var_sheet->dict_signals[PSPPIRE_VAR_SHEET_VARIABLE_INSERTED]
1548         = g_signal_connect (dict, "variable-deleted",
1549                             G_CALLBACK (on_var_deleted), var_sheet);
1550     }
1551
1552   refresh_model (var_sheet);
1553 }
1554
1555 gboolean
1556 psppire_var_sheet_get_may_create_vars (PsppireVarSheet *var_sheet)
1557 {
1558   return var_sheet->may_create_vars;
1559 }
1560
1561 void
1562 psppire_var_sheet_set_may_create_vars (PsppireVarSheet *var_sheet,
1563                                        gboolean may_create_vars)
1564 {
1565   if (var_sheet->may_create_vars != may_create_vars)
1566     {
1567       PsppireEmptyListStore *store;
1568       gint n_rows;
1569
1570       var_sheet->may_create_vars = may_create_vars;
1571
1572       store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
1573                                           PSPP_SHEET_VIEW (var_sheet)));
1574       g_return_if_fail (store != NULL);
1575
1576       n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
1577                 + var_sheet->may_create_vars);
1578       psppire_empty_list_store_set_n_rows (store, n_rows);
1579
1580       if (may_create_vars)
1581         psppire_empty_list_store_row_inserted (store, n_rows - 1);
1582       else
1583         psppire_empty_list_store_row_deleted (store, n_rows);
1584
1585       on_selection_changed (pspp_sheet_view_get_selection (
1586                               PSPP_SHEET_VIEW (var_sheet)), NULL);
1587     }
1588 }
1589
1590 gboolean
1591 psppire_var_sheet_get_may_delete_vars (PsppireVarSheet *var_sheet)
1592 {
1593   return var_sheet->may_delete_vars;
1594 }
1595
1596 void
1597 psppire_var_sheet_set_may_delete_vars (PsppireVarSheet *var_sheet,
1598                                        gboolean may_delete_vars)
1599 {
1600   if (var_sheet->may_delete_vars != may_delete_vars)
1601     {
1602       var_sheet->may_delete_vars = may_delete_vars;
1603       on_selection_changed (pspp_sheet_view_get_selection (
1604                               PSPP_SHEET_VIEW (var_sheet)), NULL);
1605     }
1606 }
1607
1608 void
1609 psppire_var_sheet_goto_variable (PsppireVarSheet *var_sheet, int dict_index)
1610 {
1611   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
1612   GtkTreePath *path;
1613
1614   path = gtk_tree_path_new_from_indices (dict_index, -1);
1615   pspp_sheet_view_scroll_to_cell (sheet_view, path, NULL, FALSE, 0.0, 0.0);
1616   pspp_sheet_view_set_cursor (sheet_view, path, NULL, FALSE);
1617   gtk_tree_path_free (path);
1618 }
1619
1620 GtkUIManager *
1621 psppire_var_sheet_get_ui_manager (PsppireVarSheet *var_sheet)
1622 {
1623   if (var_sheet->uim == NULL)
1624     {
1625       var_sheet->uim = GTK_UI_MANAGER (get_object_assert (var_sheet->builder,
1626                                                           "var_sheet_uim",
1627                                                           GTK_TYPE_UI_MANAGER));
1628       g_object_ref (var_sheet->uim);
1629     }
1630
1631   return var_sheet->uim;
1632 }
1633