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