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