pspp-sheet-view: Edit cells on the first click by default.
[pspp] / src / ui / gui / pspp-sheet-view-column.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 /* gtktreeviewcolumn.c
18  * Copyright (C) 2000  Red Hat, Inc.,  Jonathan Blandford <jrb@redhat.com>
19  *
20  * This library is free software; you can redistribute it and/or
21  * modify it under the terms of the GNU Library General Public
22  * License as published by the Free Software Foundation; either
23  * version 2 of the License, or (at your option) any later version.
24  *
25  * This library is distributed in the hope that it will be useful,
26  * but WITHOUT ANY WARRANTY; without even the implied warranty of
27  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
28  * Library General Public License for more details.
29  *
30  * You should have received a copy of the GNU Library General Public
31  * License along with this library; if not, write to the
32  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
33  * Boston, MA 02111-1307, USA.
34  */
35
36 #include <config.h>
37
38 #include "ui/gui/pspp-sheet-private.h"
39
40 #include <errno.h>
41 #include <gtk/gtk.h>
42 #include <stdlib.h>
43 #include <string.h>
44
45 #include "ui/gui/psppire-marshal.h"
46
47 #define P_(STRING) STRING
48 #define GTK_PARAM_READABLE G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
49 #define GTK_PARAM_READWRITE G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
50
51 enum
52 {
53   PROP_0,
54   PROP_VISIBLE,
55   PROP_RESIZABLE,
56   PROP_WIDTH,
57   PROP_SPACING,
58   PROP_FIXED_WIDTH,
59   PROP_MIN_WIDTH,
60   PROP_MAX_WIDTH,
61   PROP_TITLE,
62   PROP_EXPAND,
63   PROP_CLICKABLE,
64   PROP_WIDGET,
65   PROP_ALIGNMENT,
66   PROP_REORDERABLE,
67   PROP_SORT_INDICATOR,
68   PROP_SORT_ORDER,
69   PROP_SORT_COLUMN_ID,
70   PROP_QUICK_EDIT
71 };
72
73 enum
74 {
75   CLICKED,
76   QUERY_TOOLTIP,
77   LAST_SIGNAL
78 };
79
80 typedef struct _PsppSheetViewColumnCellInfo PsppSheetViewColumnCellInfo;
81 struct _PsppSheetViewColumnCellInfo
82 {
83   GtkCellRenderer *cell;
84   GSList *attributes;
85   PsppSheetCellDataFunc func;
86   gpointer func_data;
87   GDestroyNotify destroy;
88   gint requested_width;
89   gint real_width;
90   guint expand : 1;
91   guint pack : 1;
92   guint has_focus : 1;
93   guint in_editing_mode : 1;
94 };
95
96 /* Type methods */
97 static void pspp_sheet_view_column_cell_layout_init              (GtkCellLayoutIface      *iface);
98
99 /* GObject methods */
100 static void pspp_sheet_view_column_set_property                  (GObject                 *object,
101                                                                 guint                    prop_id,
102                                                                 const GValue            *value,
103                                                                 GParamSpec              *pspec);
104 static void pspp_sheet_view_column_get_property                  (GObject                 *object,
105                                                                 guint                    prop_id,
106                                                                 GValue                  *value,
107                                                                 GParamSpec              *pspec);
108 static void pspp_sheet_view_column_finalize                      (GObject                 *object);
109
110 /* GtkCellLayout implementation */
111 static void pspp_sheet_view_column_cell_layout_pack_start         (GtkCellLayout         *cell_layout,
112                                                                  GtkCellRenderer       *cell,
113                                                                  gboolean               expand);
114 static void pspp_sheet_view_column_cell_layout_pack_end           (GtkCellLayout         *cell_layout,
115                                                                  GtkCellRenderer       *cell,
116                                                                  gboolean               expand);
117 static void pspp_sheet_view_column_cell_layout_clear              (GtkCellLayout         *cell_layout);
118 static void pspp_sheet_view_column_cell_layout_add_attribute      (GtkCellLayout         *cell_layout,
119                                                                  GtkCellRenderer       *cell,
120                                                                  const gchar           *attribute,
121                                                                  gint                   column);
122 static void pspp_sheet_view_column_cell_layout_set_cell_data_func (GtkCellLayout         *cell_layout,
123                                                                  GtkCellRenderer       *cell,
124                                                                  GtkCellLayoutDataFunc  func,
125                                                                  gpointer               func_data,
126                                                                  GDestroyNotify         destroy);
127 static void pspp_sheet_view_column_cell_layout_clear_attributes   (GtkCellLayout         *cell_layout,
128                                                                  GtkCellRenderer       *cell);
129 static void pspp_sheet_view_column_cell_layout_reorder            (GtkCellLayout         *cell_layout,
130                                                                  GtkCellRenderer       *cell,
131                                                                  gint                   position);
132 static GList *pspp_sheet_view_column_cell_layout_get_cells        (GtkCellLayout         *cell_layout);
133
134 /* Button handling code */
135 static void pspp_sheet_view_column_create_button                 (PsppSheetViewColumn       *tree_column);
136 static void pspp_sheet_view_column_update_button                 (PsppSheetViewColumn       *tree_column);
137
138 /* Button signal handlers */
139 static gint pspp_sheet_view_column_button_event                  (GtkWidget               *widget,
140                                                                 GdkEvent                *event,
141                                                                 gpointer                 data);
142 static void pspp_sheet_view_column_button_clicked                (GtkWidget               *widget,
143                                                                 gpointer                 data);
144 static gboolean pspp_sheet_view_column_mnemonic_activate         (GtkWidget *widget,
145                                                                 gboolean   group_cycling,
146                                                                 gpointer   data);
147
148 /* Property handlers */
149 static void pspp_sheet_view_model_sort_column_changed            (GtkTreeSortable         *sortable,
150                                                                 PsppSheetViewColumn       *tree_column);
151
152 /* Internal functions */
153 static void pspp_sheet_view_column_sort                          (PsppSheetViewColumn       *tree_column,
154                                                                 gpointer                 data);
155 static void pspp_sheet_view_column_setup_sort_column_id_callback (PsppSheetViewColumn       *tree_column);
156 static void pspp_sheet_view_column_set_attributesv               (PsppSheetViewColumn       *tree_column,
157                                                                 GtkCellRenderer         *cell_renderer,
158                                                                 va_list                  args);
159 static PsppSheetViewColumnCellInfo *pspp_sheet_view_column_get_cell_info (PsppSheetViewColumn *tree_column,
160                                                                       GtkCellRenderer   *cell_renderer);
161
162 /* cell list manipulation */
163 static GList *pspp_sheet_view_column_cell_first                  (PsppSheetViewColumn      *tree_column);
164 static GList *pspp_sheet_view_column_cell_last                   (PsppSheetViewColumn      *tree_column);
165 static GList *pspp_sheet_view_column_cell_next                   (PsppSheetViewColumn      *tree_column,
166                                                                 GList                  *current);
167 static GList *pspp_sheet_view_column_cell_prev                   (PsppSheetViewColumn      *tree_column,
168                                                                 GList                  *current);
169 static void pspp_sheet_view_column_clear_attributes_by_info      (PsppSheetViewColumn      *tree_column,
170                                                                 PsppSheetViewColumnCellInfo *info);
171 /* GtkBuildable implementation */
172 static void pspp_sheet_view_column_buildable_init                 (GtkBuildableIface     *iface);
173
174 static guint tree_column_signals[LAST_SIGNAL] = { 0 };
175
176 G_DEFINE_TYPE_WITH_CODE (PsppSheetViewColumn, pspp_sheet_view_column, GTK_TYPE_OBJECT,
177                          G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
178                                                 pspp_sheet_view_column_cell_layout_init)
179                          G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
180                                                 pspp_sheet_view_column_buildable_init))
181
182
183 static void
184 pspp_sheet_view_column_class_init (PsppSheetViewColumnClass *class)
185 {
186   GObjectClass *object_class;
187
188   object_class = (GObjectClass*) class;
189
190   class->clicked = NULL;
191
192   object_class->finalize = pspp_sheet_view_column_finalize;
193   object_class->set_property = pspp_sheet_view_column_set_property;
194   object_class->get_property = pspp_sheet_view_column_get_property;
195   
196   tree_column_signals[CLICKED] =
197     g_signal_new ("clicked",
198                   G_OBJECT_CLASS_TYPE (object_class),
199                   G_SIGNAL_RUN_LAST,
200                   G_STRUCT_OFFSET (PsppSheetViewColumnClass, clicked),
201                   NULL, NULL,
202                   g_cclosure_marshal_VOID__VOID,
203                   G_TYPE_NONE, 0);
204
205   tree_column_signals[QUERY_TOOLTIP] =
206     g_signal_new ("query-tooltip",
207                   G_OBJECT_CLASS_TYPE (object_class),
208                   G_SIGNAL_RUN_LAST,
209                   0,
210                   g_signal_accumulator_true_handled, NULL,
211                   psppire_marshal_BOOLEAN__OBJECT,
212                   G_TYPE_BOOLEAN, 1,
213                   GTK_TYPE_TOOLTIP);
214
215   g_object_class_install_property (object_class,
216                                    PROP_VISIBLE,
217                                    g_param_spec_boolean ("visible",
218                                                         P_("Visible"),
219                                                         P_("Whether to display the column"),
220                                                          TRUE,
221                                                          GTK_PARAM_READWRITE));
222   
223   g_object_class_install_property (object_class,
224                                    PROP_RESIZABLE,
225                                    g_param_spec_boolean ("resizable",
226                                                          P_("Resizable"),
227                                                          P_("Column is user-resizable"),
228                                                          FALSE,
229                                                          GTK_PARAM_READWRITE));
230   
231   g_object_class_install_property (object_class,
232                                    PROP_WIDTH,
233                                    g_param_spec_int ("width",
234                                                      P_("Width"),
235                                                      P_("Current width of the column"),
236                                                      0,
237                                                      G_MAXINT,
238                                                      0,
239                                                      GTK_PARAM_READABLE));
240   g_object_class_install_property (object_class,
241                                    PROP_SPACING,
242                                    g_param_spec_int ("spacing",
243                                                      P_("Spacing"),
244                                                      P_("Space which is inserted between cells"),
245                                                      0,
246                                                      G_MAXINT,
247                                                      0,
248                                                      GTK_PARAM_READWRITE));
249   
250   g_object_class_install_property (object_class,
251                                    PROP_FIXED_WIDTH,
252                                    g_param_spec_int ("fixed-width",
253                                                      P_("Fixed Width"),
254                                                      P_("Current fixed width of the column"),
255                                                      1,
256                                                      G_MAXINT,
257                                                      100,
258                                                      GTK_PARAM_READWRITE));
259
260   g_object_class_install_property (object_class,
261                                    PROP_MIN_WIDTH,
262                                    g_param_spec_int ("min-width",
263                                                      P_("Minimum Width"),
264                                                      P_("Minimum allowed width of the column"),
265                                                      -1,
266                                                      G_MAXINT,
267                                                      -1,
268                                                      GTK_PARAM_READWRITE));
269
270   g_object_class_install_property (object_class,
271                                    PROP_MAX_WIDTH,
272                                    g_param_spec_int ("max-width",
273                                                      P_("Maximum Width"),
274                                                      P_("Maximum allowed width of the column"),
275                                                      -1,
276                                                      G_MAXINT,
277                                                      -1,
278                                                      GTK_PARAM_READWRITE));
279
280   g_object_class_install_property (object_class,
281                                    PROP_TITLE,
282                                    g_param_spec_string ("title",
283                                                         P_("Title"),
284                                                         P_("Title to appear in column header"),
285                                                         "",
286                                                         GTK_PARAM_READWRITE));
287   
288   g_object_class_install_property (object_class,
289                                    PROP_EXPAND,
290                                    g_param_spec_boolean ("expand",
291                                                          P_("Expand"),
292                                                          P_("Column gets share of extra width allocated to the widget"),
293                                                          FALSE,
294                                                          GTK_PARAM_READWRITE));
295   
296   g_object_class_install_property (object_class,
297                                    PROP_CLICKABLE,
298                                    g_param_spec_boolean ("clickable",
299                                                         P_("Clickable"),
300                                                         P_("Whether the header can be clicked"),
301                                                          FALSE,
302                                                          GTK_PARAM_READWRITE));
303   
304
305   g_object_class_install_property (object_class,
306                                    PROP_WIDGET,
307                                    g_param_spec_object ("widget",
308                                                         P_("Widget"),
309                                                         P_("Widget to put in column header button instead of column title"),
310                                                         GTK_TYPE_WIDGET,
311                                                         GTK_PARAM_READWRITE));
312
313   g_object_class_install_property (object_class,
314                                    PROP_ALIGNMENT,
315                                    g_param_spec_float ("alignment",
316                                                        P_("Alignment"),
317                                                        P_("X Alignment of the column header text or widget"),
318                                                        0.0,
319                                                        1.0,
320                                                        0.0,
321                                                        GTK_PARAM_READWRITE));
322
323   g_object_class_install_property (object_class,
324                                    PROP_REORDERABLE,
325                                    g_param_spec_boolean ("reorderable",
326                                                          P_("Reorderable"),
327                                                          P_("Whether the column can be reordered around the headers"),
328                                                          FALSE,
329                                                          GTK_PARAM_READWRITE));
330
331   g_object_class_install_property (object_class,
332                                    PROP_SORT_INDICATOR,
333                                    g_param_spec_boolean ("sort-indicator",
334                                                         P_("Sort indicator"),
335                                                         P_("Whether to show a sort indicator"),
336                                                          FALSE,
337                                                          GTK_PARAM_READWRITE));
338
339   g_object_class_install_property (object_class,
340                                    PROP_SORT_ORDER,
341                                    g_param_spec_enum ("sort-order",
342                                                       P_("Sort order"),
343                                                       P_("Sort direction the sort indicator should indicate"),
344                                                       GTK_TYPE_SORT_TYPE,
345                                                       GTK_SORT_ASCENDING,
346                                                       GTK_PARAM_READWRITE));
347
348   /**
349    * PsppSheetViewColumn:sort-column-id:
350    *
351    * Logical sort column ID this column sorts on when selected for sorting. Setting the sort column ID makes the column header
352    * clickable. Set to %-1 to make the column unsortable.
353    *
354    * Since: 2.18
355    **/
356   g_object_class_install_property (object_class,
357                                    PROP_SORT_COLUMN_ID,
358                                    g_param_spec_int ("sort-column-id",
359                                                      P_("Sort column ID"),
360                                                      P_("Logical sort column ID this column sorts on when selected for sorting"),
361                                                      -1,
362                                                      G_MAXINT,
363                                                      -1,
364                                                      GTK_PARAM_READWRITE));
365
366   g_object_class_install_property (object_class,
367                                    PROP_QUICK_EDIT,
368                                    g_param_spec_boolean ("quick-edit",
369                                                          P_("Quick edit"),
370                                                          P_("If true, editing starts upon the first click in the column.  If false, the first click selects the column and a second click is needed to begin editing.  This has no effect on cells that are not editable."),
371                                                          TRUE,
372                                                          GTK_PARAM_READWRITE));
373 }
374
375 static void
376 pspp_sheet_view_column_buildable_init (GtkBuildableIface *iface)
377 {
378   iface->add_child = _gtk_cell_layout_buildable_add_child;
379   iface->custom_tag_start = _gtk_cell_layout_buildable_custom_tag_start;
380   iface->custom_tag_end = _gtk_cell_layout_buildable_custom_tag_end;
381 }
382
383 static void
384 pspp_sheet_view_column_cell_layout_init (GtkCellLayoutIface *iface)
385 {
386   iface->pack_start = pspp_sheet_view_column_cell_layout_pack_start;
387   iface->pack_end = pspp_sheet_view_column_cell_layout_pack_end;
388   iface->clear = pspp_sheet_view_column_cell_layout_clear;
389   iface->add_attribute = pspp_sheet_view_column_cell_layout_add_attribute;
390   iface->set_cell_data_func = pspp_sheet_view_column_cell_layout_set_cell_data_func;
391   iface->clear_attributes = pspp_sheet_view_column_cell_layout_clear_attributes;
392   iface->reorder = pspp_sheet_view_column_cell_layout_reorder;
393   iface->get_cells = pspp_sheet_view_column_cell_layout_get_cells;
394 }
395
396 static void
397 pspp_sheet_view_column_init (PsppSheetViewColumn *tree_column)
398 {
399   tree_column->button = NULL;
400   tree_column->xalign = 0.0;
401   tree_column->width = 0;
402   tree_column->spacing = 0;
403   tree_column->requested_width = -1;
404   tree_column->min_width = -1;
405   tree_column->max_width = -1;
406   tree_column->resized_width = 0;
407   tree_column->visible = TRUE;
408   tree_column->resizable = FALSE;
409   tree_column->expand = FALSE;
410   tree_column->clickable = FALSE;
411   tree_column->dirty = TRUE;
412   tree_column->sort_order = GTK_SORT_ASCENDING;
413   tree_column->show_sort_indicator = FALSE;
414   tree_column->property_changed_signal = 0;
415   tree_column->sort_clicked_signal = 0;
416   tree_column->sort_column_changed_signal = 0;
417   tree_column->sort_column_id = -1;
418   tree_column->reorderable = FALSE;
419   tree_column->maybe_reordered = FALSE;
420   tree_column->fixed_width = 1;
421   tree_column->use_resized_width = FALSE;
422   tree_column->title = g_strdup ("");
423   tree_column->quick_edit = TRUE;
424 }
425
426 static void
427 pspp_sheet_view_column_finalize (GObject *object)
428 {
429   PsppSheetViewColumn *tree_column = (PsppSheetViewColumn *) object;
430   GList *list;
431
432   for (list = tree_column->cell_list; list; list = list->next)
433     {
434       PsppSheetViewColumnCellInfo *info = (PsppSheetViewColumnCellInfo *) list->data;
435
436       if (info->destroy)
437         {
438           GDestroyNotify d = info->destroy;
439
440           info->destroy = NULL;
441           d (info->func_data);
442         }
443       pspp_sheet_view_column_clear_attributes_by_info (tree_column, info);
444       g_object_unref (info->cell);
445       g_free (info);
446     }
447
448   g_free (tree_column->title);
449   g_list_free (tree_column->cell_list);
450
451   if (tree_column->child)
452     g_object_unref (tree_column->child);
453
454   G_OBJECT_CLASS (pspp_sheet_view_column_parent_class)->finalize (object);
455 }
456
457 static void
458 pspp_sheet_view_column_set_property (GObject         *object,
459                                    guint            prop_id,
460                                    const GValue    *value,
461                                    GParamSpec      *pspec)
462 {
463   PsppSheetViewColumn *tree_column;
464
465   tree_column = PSPP_SHEET_VIEW_COLUMN (object);
466
467   switch (prop_id)
468     {
469     case PROP_VISIBLE:
470       pspp_sheet_view_column_set_visible (tree_column,
471                                         g_value_get_boolean (value));
472       break;
473
474     case PROP_RESIZABLE:
475       pspp_sheet_view_column_set_resizable (tree_column,
476                                           g_value_get_boolean (value));
477       break;
478
479     case PROP_FIXED_WIDTH:
480       pspp_sheet_view_column_set_fixed_width (tree_column,
481                                             g_value_get_int (value));
482       break;
483
484     case PROP_MIN_WIDTH:
485       pspp_sheet_view_column_set_min_width (tree_column,
486                                           g_value_get_int (value));
487       break;
488
489     case PROP_MAX_WIDTH:
490       pspp_sheet_view_column_set_max_width (tree_column,
491                                           g_value_get_int (value));
492       break;
493
494     case PROP_SPACING:
495       pspp_sheet_view_column_set_spacing (tree_column,
496                                         g_value_get_int (value));
497       break;
498
499     case PROP_TITLE:
500       pspp_sheet_view_column_set_title (tree_column,
501                                       g_value_get_string (value));
502       break;
503
504     case PROP_EXPAND:
505       pspp_sheet_view_column_set_expand (tree_column,
506                                        g_value_get_boolean (value));
507       break;
508
509     case PROP_CLICKABLE:
510       pspp_sheet_view_column_set_clickable (tree_column,
511                                           g_value_get_boolean (value));
512       break;
513
514     case PROP_WIDGET:
515       pspp_sheet_view_column_set_widget (tree_column,
516                                        (GtkWidget*) g_value_get_object (value));
517       break;
518
519     case PROP_ALIGNMENT:
520       pspp_sheet_view_column_set_alignment (tree_column,
521                                           g_value_get_float (value));
522       break;
523
524     case PROP_REORDERABLE:
525       pspp_sheet_view_column_set_reorderable (tree_column,
526                                             g_value_get_boolean (value));
527       break;
528
529     case PROP_SORT_INDICATOR:
530       pspp_sheet_view_column_set_sort_indicator (tree_column,
531                                                g_value_get_boolean (value));
532       break;
533
534     case PROP_SORT_ORDER:
535       pspp_sheet_view_column_set_sort_order (tree_column,
536                                            g_value_get_enum (value));
537       break;
538       
539     case PROP_SORT_COLUMN_ID:
540       pspp_sheet_view_column_set_sort_column_id (tree_column,
541                                                g_value_get_int (value));
542       break;
543
544     case PROP_QUICK_EDIT:
545       pspp_sheet_view_column_set_quick_edit (tree_column,
546                                              g_value_get_boolean (value));
547       break;
548
549     default:
550       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
551       break;
552     }
553 }
554
555 static void
556 pspp_sheet_view_column_get_property (GObject         *object,
557                                    guint            prop_id,
558                                    GValue          *value,
559                                    GParamSpec      *pspec)
560 {
561   PsppSheetViewColumn *tree_column;
562
563   tree_column = PSPP_SHEET_VIEW_COLUMN (object);
564
565   switch (prop_id)
566     {
567     case PROP_VISIBLE:
568       g_value_set_boolean (value,
569                            pspp_sheet_view_column_get_visible (tree_column));
570       break;
571
572     case PROP_RESIZABLE:
573       g_value_set_boolean (value,
574                            pspp_sheet_view_column_get_resizable (tree_column));
575       break;
576
577     case PROP_WIDTH:
578       g_value_set_int (value,
579                        pspp_sheet_view_column_get_width (tree_column));
580       break;
581
582     case PROP_SPACING:
583       g_value_set_int (value,
584                        pspp_sheet_view_column_get_spacing (tree_column));
585       break;
586
587     case PROP_FIXED_WIDTH:
588       g_value_set_int (value,
589                        pspp_sheet_view_column_get_fixed_width (tree_column));
590       break;
591
592     case PROP_MIN_WIDTH:
593       g_value_set_int (value,
594                        pspp_sheet_view_column_get_min_width (tree_column));
595       break;
596
597     case PROP_MAX_WIDTH:
598       g_value_set_int (value,
599                        pspp_sheet_view_column_get_max_width (tree_column));
600       break;
601
602     case PROP_TITLE:
603       g_value_set_string (value,
604                           pspp_sheet_view_column_get_title (tree_column));
605       break;
606
607     case PROP_EXPAND:
608       g_value_set_boolean (value,
609                           pspp_sheet_view_column_get_expand (tree_column));
610       break;
611
612     case PROP_CLICKABLE:
613       g_value_set_boolean (value,
614                            pspp_sheet_view_column_get_clickable (tree_column));
615       break;
616
617     case PROP_WIDGET:
618       g_value_set_object (value,
619                           (GObject*) pspp_sheet_view_column_get_widget (tree_column));
620       break;
621
622     case PROP_ALIGNMENT:
623       g_value_set_float (value,
624                          pspp_sheet_view_column_get_alignment (tree_column));
625       break;
626
627     case PROP_REORDERABLE:
628       g_value_set_boolean (value,
629                            pspp_sheet_view_column_get_reorderable (tree_column));
630       break;
631
632     case PROP_SORT_INDICATOR:
633       g_value_set_boolean (value,
634                            pspp_sheet_view_column_get_sort_indicator (tree_column));
635       break;
636
637     case PROP_SORT_ORDER:
638       g_value_set_enum (value,
639                         pspp_sheet_view_column_get_sort_order (tree_column));
640       break;
641       
642     case PROP_SORT_COLUMN_ID:
643       g_value_set_int (value,
644                        pspp_sheet_view_column_get_sort_column_id (tree_column));
645       break;
646
647     case PROP_QUICK_EDIT:
648       g_value_set_boolean (value,
649                            pspp_sheet_view_column_get_quick_edit (tree_column));
650       break;
651
652     default:
653       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
654       break;
655     }
656 }
657
658 /* Implementation of GtkCellLayout interface
659  */
660
661 static void
662 pspp_sheet_view_column_cell_layout_pack_start (GtkCellLayout   *cell_layout,
663                                              GtkCellRenderer *cell,
664                                              gboolean         expand)
665 {
666   PsppSheetViewColumn *column;
667   PsppSheetViewColumnCellInfo *cell_info;
668
669   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (cell_layout));
670   column = PSPP_SHEET_VIEW_COLUMN (cell_layout);
671   g_return_if_fail (! pspp_sheet_view_column_get_cell_info (column, cell));
672
673   g_object_ref_sink (cell);
674
675   cell_info = g_new0 (PsppSheetViewColumnCellInfo, 1);
676   cell_info->cell = cell;
677   cell_info->expand = expand ? TRUE : FALSE;
678   cell_info->pack = GTK_PACK_START;
679   cell_info->has_focus = 0;
680   cell_info->attributes = NULL;
681
682   column->cell_list = g_list_append (column->cell_list, cell_info);
683 }
684
685 static void
686 pspp_sheet_view_column_cell_layout_pack_end (GtkCellLayout   *cell_layout,
687                                            GtkCellRenderer *cell,
688                                            gboolean         expand)
689 {
690   PsppSheetViewColumn *column;
691   PsppSheetViewColumnCellInfo *cell_info;
692
693   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (cell_layout));
694   column = PSPP_SHEET_VIEW_COLUMN (cell_layout);
695   g_return_if_fail (! pspp_sheet_view_column_get_cell_info (column, cell));
696
697   g_object_ref_sink (cell);
698
699   cell_info = g_new0 (PsppSheetViewColumnCellInfo, 1);
700   cell_info->cell = cell;
701   cell_info->expand = expand ? TRUE : FALSE;
702   cell_info->pack = GTK_PACK_END;
703   cell_info->has_focus = 0;
704   cell_info->attributes = NULL;
705
706   column->cell_list = g_list_append (column->cell_list, cell_info);
707 }
708
709 static void
710 pspp_sheet_view_column_cell_layout_clear (GtkCellLayout *cell_layout)
711 {
712   PsppSheetViewColumn *column;
713
714   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (cell_layout));
715   column = PSPP_SHEET_VIEW_COLUMN (cell_layout);
716
717   while (column->cell_list)
718     {
719       PsppSheetViewColumnCellInfo *info = (PsppSheetViewColumnCellInfo *)column->cell_list->data;
720
721       pspp_sheet_view_column_cell_layout_clear_attributes (cell_layout, info->cell);
722       g_object_unref (info->cell);
723       g_free (info);
724       column->cell_list = g_list_delete_link (column->cell_list, 
725                                               column->cell_list);
726     }
727 }
728
729 static void
730 pspp_sheet_view_column_cell_layout_add_attribute (GtkCellLayout   *cell_layout,
731                                                 GtkCellRenderer *cell,
732                                                 const gchar     *attribute,
733                                                 gint             column)
734 {
735   PsppSheetViewColumn *tree_column;
736   PsppSheetViewColumnCellInfo *info;
737
738   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (cell_layout));
739   tree_column = PSPP_SHEET_VIEW_COLUMN (cell_layout);
740
741   info = pspp_sheet_view_column_get_cell_info (tree_column, cell);
742   g_return_if_fail (info != NULL);
743
744   info->attributes = g_slist_prepend (info->attributes, GINT_TO_POINTER (column));
745   info->attributes = g_slist_prepend (info->attributes, g_strdup (attribute));
746
747   if (tree_column->tree_view)
748     _pspp_sheet_view_column_cell_set_dirty (tree_column);
749 }
750
751 static void
752 pspp_sheet_view_column_cell_layout_set_cell_data_func (GtkCellLayout         *cell_layout,
753                                                      GtkCellRenderer       *cell,
754                                                      GtkCellLayoutDataFunc  func,
755                                                      gpointer               func_data,
756                                                      GDestroyNotify         destroy)
757 {
758   PsppSheetViewColumn *column;
759   PsppSheetViewColumnCellInfo *info;
760
761   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (cell_layout));
762   column = PSPP_SHEET_VIEW_COLUMN (cell_layout);
763
764   info = pspp_sheet_view_column_get_cell_info (column, cell);
765   g_return_if_fail (info != NULL);
766
767   if (info->destroy)
768     {
769       GDestroyNotify d = info->destroy;
770
771       info->destroy = NULL;
772       d (info->func_data);
773     }
774
775   info->func = (PsppSheetCellDataFunc)func;
776   info->func_data = func_data;
777   info->destroy = destroy;
778
779   if (column->tree_view)
780     _pspp_sheet_view_column_cell_set_dirty (column);
781 }
782
783 static void
784 pspp_sheet_view_column_cell_layout_clear_attributes (GtkCellLayout    *cell_layout,
785                                                    GtkCellRenderer  *cell_renderer)
786 {
787   PsppSheetViewColumn *column;
788   PsppSheetViewColumnCellInfo *info;
789
790   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (cell_layout));
791   column = PSPP_SHEET_VIEW_COLUMN (cell_layout);
792
793   info = pspp_sheet_view_column_get_cell_info (column, cell_renderer);
794   if (info)
795     pspp_sheet_view_column_clear_attributes_by_info (column, info);
796 }
797
798 static void
799 pspp_sheet_view_column_cell_layout_reorder (GtkCellLayout   *cell_layout,
800                                           GtkCellRenderer *cell,
801                                           gint             position)
802 {
803   GList *link;
804   PsppSheetViewColumn *column;
805   PsppSheetViewColumnCellInfo *info;
806
807   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (cell_layout));
808   column = PSPP_SHEET_VIEW_COLUMN (cell_layout);
809
810   info = pspp_sheet_view_column_get_cell_info (column, cell);
811
812   g_return_if_fail (info != NULL);
813   g_return_if_fail (position >= 0);
814
815   link = g_list_find (column->cell_list, info);
816
817   g_return_if_fail (link != NULL);
818
819   column->cell_list = g_list_delete_link (column->cell_list, link);
820   column->cell_list = g_list_insert (column->cell_list, info, position);
821
822   if (column->tree_view)
823     gtk_widget_queue_draw (column->tree_view);
824 }
825
826 static void
827 pspp_sheet_view_column_clear_attributes_by_info (PsppSheetViewColumn *tree_column,
828                                                PsppSheetViewColumnCellInfo *info)
829 {
830   GSList *list;
831
832   list = info->attributes;
833
834   while (list && list->next)
835     {
836       g_free (list->data);
837       list = list->next->next;
838     }
839   g_slist_free (info->attributes);
840   info->attributes = NULL;
841
842   if (tree_column->tree_view)
843     _pspp_sheet_view_column_cell_set_dirty (tree_column);
844 }
845
846 static gboolean
847 on_query_tooltip (GtkWidget  *widget,
848                   gint        x,
849                   gint        y,
850                   gboolean    keyboard_mode,
851                   GtkTooltip *tooltip,
852                   gpointer    user_data)
853 {
854   PsppSheetViewColumn *tree_column = user_data;
855   gboolean handled;
856
857   g_signal_emit (tree_column, tree_column_signals[QUERY_TOOLTIP], 0,
858                  tooltip, &handled);
859   return handled;
860 }
861
862 /* Helper functions
863  */
864
865 /* Button handling code
866  */
867 static void
868 pspp_sheet_view_column_create_button (PsppSheetViewColumn *tree_column)
869 {
870   PsppSheetView *tree_view;
871   GtkWidget *child;
872   GtkWidget *hbox;
873
874   tree_view = (PsppSheetView *) tree_column->tree_view;
875
876   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
877   g_return_if_fail (tree_column->button == NULL);
878
879   gtk_widget_push_composite_child ();
880   tree_column->button = gtk_button_new ();
881   gtk_widget_add_events (tree_column->button, GDK_POINTER_MOTION_MASK);
882   gtk_widget_pop_composite_child ();
883
884   /* make sure we own a reference to it as well. */
885   if (tree_view->priv->header_window)
886     gtk_widget_set_parent_window (tree_column->button, tree_view->priv->header_window);
887   gtk_widget_set_parent (tree_column->button, GTK_WIDGET (tree_view));
888
889   g_signal_connect (tree_column->button, "event",
890                     G_CALLBACK (pspp_sheet_view_column_button_event),
891                     tree_column);
892   g_signal_connect (tree_column->button, "clicked",
893                     G_CALLBACK (pspp_sheet_view_column_button_clicked),
894                     tree_column);
895
896   g_signal_connect (tree_column->button, "query-tooltip",
897                     G_CALLBACK (on_query_tooltip), tree_column);
898   g_object_set (tree_column->button, "has-tooltip", TRUE, NULL);
899
900   tree_column->alignment = gtk_alignment_new (tree_column->xalign, 0.5, 0.0, 0.0);
901
902   hbox = gtk_hbox_new (FALSE, 2);
903   tree_column->arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_IN);
904
905   if (tree_column->child)
906     child = tree_column->child;
907   else
908     {
909       child = gtk_label_new (tree_column->title);
910       gtk_widget_show (child);
911     }
912
913   g_signal_connect (child, "mnemonic-activate",
914                     G_CALLBACK (pspp_sheet_view_column_mnemonic_activate),
915                     tree_column);
916
917   if (tree_column->xalign <= 0.5)
918     gtk_box_pack_end (GTK_BOX (hbox), tree_column->arrow, FALSE, FALSE, 0);
919   else
920     gtk_box_pack_start (GTK_BOX (hbox), tree_column->arrow, FALSE, FALSE, 0);
921
922   gtk_box_pack_start (GTK_BOX (hbox), tree_column->alignment, TRUE, TRUE, 0);
923         
924   gtk_container_add (GTK_CONTAINER (tree_column->alignment), child);
925   gtk_container_add (GTK_CONTAINER (tree_column->button), hbox);
926
927   gtk_widget_show (hbox);
928   gtk_widget_show (tree_column->alignment);
929   pspp_sheet_view_column_update_button (tree_column);
930 }
931
932 static void 
933 pspp_sheet_view_column_update_button (PsppSheetViewColumn *tree_column)
934 {
935   gint sort_column_id = -1;
936   GtkWidget *hbox;
937   GtkWidget *alignment;
938   GtkWidget *arrow;
939   GtkWidget *current_child;
940   GtkArrowType arrow_type = GTK_ARROW_NONE;
941   GtkTreeModel *model;
942
943   if (tree_column->tree_view)
944     model = pspp_sheet_view_get_model (PSPP_SHEET_VIEW (tree_column->tree_view));
945   else
946     model = NULL;
947
948   /* Create a button if necessary */
949   if (tree_column->visible &&
950       tree_column->button == NULL &&
951       tree_column->tree_view &&
952       gtk_widget_get_realized (tree_column->tree_view))
953     pspp_sheet_view_column_create_button (tree_column);
954   
955   if (! tree_column->button)
956     return;
957
958   hbox = GTK_BIN (tree_column->button)->child;
959   alignment = tree_column->alignment;
960   arrow = tree_column->arrow;
961   current_child = GTK_BIN (alignment)->child;
962
963   /* Set up the actual button */
964   gtk_alignment_set (GTK_ALIGNMENT (alignment), tree_column->xalign,
965                      0.5, 0.0, 0.0);
966       
967   if (tree_column->child)
968     {
969       if (current_child != tree_column->child)
970         {
971           gtk_container_remove (GTK_CONTAINER (alignment),
972                                 current_child);
973           gtk_container_add (GTK_CONTAINER (alignment),
974                              tree_column->child);
975         }
976     }
977   else 
978     {
979       if (current_child == NULL)
980         {
981           current_child = gtk_label_new (NULL);
982           gtk_widget_show (current_child);
983           gtk_container_add (GTK_CONTAINER (alignment),
984                              current_child);
985         }
986
987       g_return_if_fail (GTK_IS_LABEL (current_child));
988
989       if (tree_column->title)
990         gtk_label_set_text_with_mnemonic (GTK_LABEL (current_child),
991                                           tree_column->title);
992       else
993         gtk_label_set_text_with_mnemonic (GTK_LABEL (current_child),
994                                           "");
995     }
996
997   if (GTK_IS_TREE_SORTABLE (model))
998     gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (model),
999                                           &sort_column_id,
1000                                           NULL);
1001
1002   if (tree_column->show_sort_indicator)
1003     {
1004       gboolean alternative;
1005
1006       g_object_get (gtk_widget_get_settings (tree_column->tree_view),
1007                     "gtk-alternative-sort-arrows", &alternative,
1008                     NULL);
1009
1010       switch (tree_column->sort_order)
1011         {
1012           case GTK_SORT_ASCENDING:
1013             arrow_type = alternative ? GTK_ARROW_UP : GTK_ARROW_DOWN;
1014             break;
1015
1016           case GTK_SORT_DESCENDING:
1017             arrow_type = alternative ? GTK_ARROW_DOWN : GTK_ARROW_UP;
1018             break;
1019
1020           default:
1021             g_warning (G_STRLOC": bad sort order");
1022             break;
1023         }
1024     }
1025
1026   gtk_arrow_set (GTK_ARROW (arrow),
1027                  arrow_type,
1028                  GTK_SHADOW_IN);
1029
1030   /* Put arrow on the right if the text is left-or-center justified, and on the
1031    * left otherwise; do this by packing boxes, so flipping text direction will
1032    * reverse things
1033    */
1034   g_object_ref (arrow);
1035   gtk_container_remove (GTK_CONTAINER (hbox), arrow);
1036
1037   if (tree_column->xalign <= 0.5)
1038     {
1039       gtk_box_pack_end (GTK_BOX (hbox), arrow, FALSE, FALSE, 0);
1040     }
1041   else
1042     {
1043       gtk_box_pack_start (GTK_BOX (hbox), arrow, FALSE, FALSE, 0);
1044       /* move it to the front */
1045       gtk_box_reorder_child (GTK_BOX (hbox), arrow, 0);
1046     }
1047   g_object_unref (arrow);
1048
1049   if (tree_column->show_sort_indicator
1050       || (GTK_IS_TREE_SORTABLE (model) && tree_column->sort_column_id >= 0))
1051     gtk_widget_show (arrow);
1052   else
1053     gtk_widget_hide (arrow);
1054
1055   /* It's always safe to hide the button.  It isn't always safe to show it, as
1056    * if you show it before it's realized, it'll get the wrong window. */
1057   if (tree_column->button &&
1058       tree_column->tree_view != NULL &&
1059       gtk_widget_get_realized (tree_column->tree_view))
1060     {
1061       if (tree_column->visible)
1062         {
1063           gtk_widget_show_now (tree_column->button);
1064           if (tree_column->window)
1065             {
1066               if (tree_column->resizable)
1067                 {
1068                   gdk_window_show (tree_column->window);
1069                   gdk_window_raise (tree_column->window);
1070                 }
1071               else
1072                 {
1073                   gdk_window_hide (tree_column->window);
1074                 }
1075             }
1076         }
1077       else
1078         {
1079           gtk_widget_hide (tree_column->button);
1080           if (tree_column->window)
1081             gdk_window_hide (tree_column->window);
1082         }
1083     }
1084   
1085   if (tree_column->reorderable || tree_column->clickable)
1086     {
1087       gtk_widget_set_can_focus (tree_column->button, TRUE);
1088     }
1089   else
1090     {
1091       gtk_widget_set_can_focus (tree_column->button, FALSE);
1092       if (gtk_widget_has_focus (tree_column->button))
1093         {
1094           GtkWidget *toplevel = gtk_widget_get_toplevel (tree_column->tree_view);
1095           if (gtk_widget_is_toplevel (toplevel))
1096             {
1097               gtk_window_set_focus (GTK_WINDOW (toplevel), NULL);
1098             }
1099         }
1100     }
1101   /* Queue a resize on the assumption that we always want to catch all changes
1102    * and columns don't change all that often.
1103    */
1104   if (gtk_widget_get_realized (tree_column->tree_view))
1105      gtk_widget_queue_resize (tree_column->tree_view);
1106
1107 }
1108
1109 /* Button signal handlers
1110  */
1111
1112 static gint
1113 pspp_sheet_view_column_button_event (GtkWidget *widget,
1114                                    GdkEvent  *event,
1115                                    gpointer   data)
1116 {
1117   PsppSheetViewColumn *column = (PsppSheetViewColumn *) data;
1118
1119   g_return_val_if_fail (event != NULL, FALSE);
1120
1121   if (event->type == GDK_BUTTON_PRESS &&
1122       column->reorderable &&
1123       ((GdkEventButton *)event)->button == 1)
1124     {
1125       column->maybe_reordered = TRUE;
1126       gdk_window_get_pointer (GTK_BUTTON (widget)->event_window,
1127                               &column->drag_x,
1128                               &column->drag_y,
1129                               NULL);
1130       gtk_widget_grab_focus (widget);
1131     }
1132
1133   if (event->type == GDK_BUTTON_RELEASE ||
1134       event->type == GDK_LEAVE_NOTIFY)
1135     column->maybe_reordered = FALSE;
1136   
1137   if (event->type == GDK_MOTION_NOTIFY &&
1138       column->maybe_reordered &&
1139       (gtk_drag_check_threshold (widget,
1140                                  column->drag_x,
1141                                  column->drag_y,
1142                                  (gint) ((GdkEventMotion *)event)->x,
1143                                  (gint) ((GdkEventMotion *)event)->y)))
1144     {
1145       column->maybe_reordered = FALSE;
1146       _pspp_sheet_view_column_start_drag (PSPP_SHEET_VIEW (column->tree_view), column);
1147       return TRUE;
1148     }
1149   if (column->clickable == FALSE)
1150     {
1151       switch (event->type)
1152         {
1153         case GDK_BUTTON_PRESS:
1154         case GDK_2BUTTON_PRESS:
1155         case GDK_3BUTTON_PRESS:
1156         case GDK_MOTION_NOTIFY:
1157         case GDK_BUTTON_RELEASE:
1158         case GDK_ENTER_NOTIFY:
1159         case GDK_LEAVE_NOTIFY:
1160           return TRUE;
1161         default:
1162           return FALSE;
1163         }
1164     }
1165   return FALSE;
1166 }
1167
1168
1169 static void
1170 pspp_sheet_view_column_button_clicked (GtkWidget *widget, gpointer data)
1171 {
1172   g_signal_emit_by_name (data, "clicked");
1173 }
1174
1175 static gboolean
1176 pspp_sheet_view_column_mnemonic_activate (GtkWidget *widget,
1177                                         gboolean   group_cycling,
1178                                         gpointer   data)
1179 {
1180   PsppSheetViewColumn *column = (PsppSheetViewColumn *)data;
1181
1182   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (column), FALSE);
1183
1184   PSPP_SHEET_VIEW (column->tree_view)->priv->focus_column = column;
1185   if (column->clickable)
1186     gtk_button_clicked (GTK_BUTTON (column->button));
1187   else if (gtk_widget_get_can_focus (column->button))
1188     gtk_widget_grab_focus (column->button);
1189   else
1190     gtk_widget_grab_focus (column->tree_view);
1191
1192   return TRUE;
1193 }
1194
1195 static void
1196 pspp_sheet_view_model_sort_column_changed (GtkTreeSortable   *sortable,
1197                                          PsppSheetViewColumn *column)
1198 {
1199   gint sort_column_id;
1200   GtkSortType order;
1201
1202   if (gtk_tree_sortable_get_sort_column_id (sortable,
1203                                             &sort_column_id,
1204                                             &order))
1205     {
1206       if (sort_column_id == column->sort_column_id)
1207         {
1208           pspp_sheet_view_column_set_sort_indicator (column, TRUE);
1209           pspp_sheet_view_column_set_sort_order (column, order);
1210         }
1211       else
1212         {
1213           pspp_sheet_view_column_set_sort_indicator (column, FALSE);
1214         }
1215     }
1216   else
1217     {
1218       pspp_sheet_view_column_set_sort_indicator (column, FALSE);
1219     }
1220 }
1221
1222 static void
1223 pspp_sheet_view_column_sort (PsppSheetViewColumn *tree_column,
1224                            gpointer           data)
1225 {
1226   gint sort_column_id;
1227   GtkSortType order;
1228   gboolean has_sort_column;
1229   gboolean has_default_sort_func;
1230
1231   g_return_if_fail (tree_column->tree_view != NULL);
1232
1233   has_sort_column =
1234     gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (PSPP_SHEET_VIEW (tree_column->tree_view)->priv->model),
1235                                           &sort_column_id,
1236                                           &order);
1237   has_default_sort_func =
1238     gtk_tree_sortable_has_default_sort_func (GTK_TREE_SORTABLE (PSPP_SHEET_VIEW (tree_column->tree_view)->priv->model));
1239
1240   if (has_sort_column &&
1241       sort_column_id == tree_column->sort_column_id)
1242     {
1243       if (order == GTK_SORT_ASCENDING)
1244         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (PSPP_SHEET_VIEW (tree_column->tree_view)->priv->model),
1245                                               tree_column->sort_column_id,
1246                                               GTK_SORT_DESCENDING);
1247       else if (order == GTK_SORT_DESCENDING && has_default_sort_func)
1248         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (PSPP_SHEET_VIEW (tree_column->tree_view)->priv->model),
1249                                               GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
1250                                               GTK_SORT_ASCENDING);
1251       else
1252         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (PSPP_SHEET_VIEW (tree_column->tree_view)->priv->model),
1253                                               tree_column->sort_column_id,
1254                                               GTK_SORT_ASCENDING);
1255     }
1256   else
1257     {
1258       gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (PSPP_SHEET_VIEW (tree_column->tree_view)->priv->model),
1259                                             tree_column->sort_column_id,
1260                                             GTK_SORT_ASCENDING);
1261     }
1262 }
1263
1264
1265 static void
1266 pspp_sheet_view_column_setup_sort_column_id_callback (PsppSheetViewColumn *tree_column)
1267 {
1268   GtkTreeModel *model;
1269
1270   if (tree_column->tree_view == NULL)
1271     return;
1272
1273   model = pspp_sheet_view_get_model (PSPP_SHEET_VIEW (tree_column->tree_view));
1274
1275   if (model == NULL)
1276     return;
1277
1278   if (GTK_IS_TREE_SORTABLE (model) &&
1279       tree_column->sort_column_id != -1)
1280     {
1281       gint real_sort_column_id;
1282       GtkSortType real_order;
1283
1284       if (tree_column->sort_column_changed_signal == 0)
1285         tree_column->sort_column_changed_signal =
1286           g_signal_connect (model, "sort-column-changed",
1287                             G_CALLBACK (pspp_sheet_view_model_sort_column_changed),
1288                             tree_column);
1289       
1290       if (gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (model),
1291                                                 &real_sort_column_id,
1292                                                 &real_order) &&
1293           (real_sort_column_id == tree_column->sort_column_id))
1294         {
1295           pspp_sheet_view_column_set_sort_indicator (tree_column, TRUE);
1296           pspp_sheet_view_column_set_sort_order (tree_column, real_order);
1297         }
1298       else 
1299         {
1300           pspp_sheet_view_column_set_sort_indicator (tree_column, FALSE);
1301         }
1302    }
1303 }
1304
1305
1306 /* Exported Private Functions.
1307  * These should only be called by gtktreeview.c or gtktreeviewcolumn.c
1308  */
1309
1310 void
1311 _pspp_sheet_view_column_realize_button (PsppSheetViewColumn *column)
1312 {
1313   PsppSheetView *tree_view;
1314   GdkWindowAttr attr;
1315   guint attributes_mask;
1316   gboolean rtl;
1317
1318   tree_view = (PsppSheetView *)column->tree_view;
1319   rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
1320
1321   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
1322   g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (tree_view)));
1323   g_return_if_fail (tree_view->priv->header_window != NULL);
1324   g_return_if_fail (column->button != NULL);
1325
1326   gtk_widget_set_parent_window (column->button, tree_view->priv->header_window);
1327
1328   if (column->visible)
1329     gtk_widget_show (column->button);
1330
1331   attr.window_type = GDK_WINDOW_CHILD;
1332   attr.wclass = GDK_INPUT_ONLY;
1333   attr.visual = gtk_widget_get_visual (GTK_WIDGET (tree_view));
1334   attr.colormap = gtk_widget_get_colormap (GTK_WIDGET (tree_view));
1335   attr.event_mask = gtk_widget_get_events (GTK_WIDGET (tree_view)) |
1336                     (GDK_BUTTON_PRESS_MASK |
1337                      GDK_BUTTON_RELEASE_MASK |
1338                      GDK_POINTER_MOTION_MASK |
1339                      GDK_POINTER_MOTION_HINT_MASK |
1340                      GDK_KEY_PRESS_MASK);
1341   attributes_mask = GDK_WA_CURSOR | GDK_WA_X | GDK_WA_Y;
1342   attr.cursor = gdk_cursor_new_for_display (gdk_drawable_get_display (tree_view->priv->header_window),
1343                                             GDK_SB_H_DOUBLE_ARROW);
1344   attr.y = 0;
1345   attr.width = TREE_VIEW_DRAG_WIDTH;
1346   attr.height = tree_view->priv->header_height;
1347
1348   attr.x = (column->button->allocation.x + (rtl ? 0 : column->button->allocation.width)) - TREE_VIEW_DRAG_WIDTH / 2;
1349   column->window = gdk_window_new (tree_view->priv->header_window,
1350                                    &attr, attributes_mask);
1351   gdk_window_set_user_data (column->window, tree_view);
1352
1353   pspp_sheet_view_column_update_button (column);
1354
1355   gdk_cursor_unref (attr.cursor);
1356 }
1357
1358 void
1359 _pspp_sheet_view_column_unrealize_button (PsppSheetViewColumn *column)
1360 {
1361   g_return_if_fail (column != NULL);
1362   g_return_if_fail (column->window != NULL);
1363
1364   gdk_window_set_user_data (column->window, NULL);
1365   gdk_window_destroy (column->window);
1366   column->window = NULL;
1367 }
1368
1369 void
1370 _pspp_sheet_view_column_unset_model (PsppSheetViewColumn *column,
1371                                    GtkTreeModel      *old_model)
1372 {
1373   if (column->sort_column_changed_signal)
1374     {
1375       g_signal_handler_disconnect (old_model,
1376                                    column->sort_column_changed_signal);
1377       column->sort_column_changed_signal = 0;
1378     }
1379   pspp_sheet_view_column_set_sort_indicator (column, FALSE);
1380 }
1381
1382 void
1383 _pspp_sheet_view_column_set_tree_view (PsppSheetViewColumn *column,
1384                                      PsppSheetView       *tree_view)
1385 {
1386   g_assert (column->tree_view == NULL);
1387
1388   column->tree_view = GTK_WIDGET (tree_view);
1389   pspp_sheet_view_column_create_button (column);
1390
1391   column->property_changed_signal =
1392           g_signal_connect_swapped (tree_view,
1393                                     "notify::model",
1394                                     G_CALLBACK (pspp_sheet_view_column_setup_sort_column_id_callback),
1395                                     column);
1396
1397   pspp_sheet_view_column_setup_sort_column_id_callback (column);
1398 }
1399
1400 void
1401 _pspp_sheet_view_column_unset_tree_view (PsppSheetViewColumn *column)
1402 {
1403   if (column->tree_view && column->button)
1404     {
1405       gtk_container_remove (GTK_CONTAINER (column->tree_view), column->button);
1406     }
1407   if (column->property_changed_signal)
1408     {
1409       g_signal_handler_disconnect (column->tree_view, column->property_changed_signal);
1410       column->property_changed_signal = 0;
1411     }
1412
1413   if (column->sort_column_changed_signal)
1414     {
1415       g_signal_handler_disconnect (pspp_sheet_view_get_model (PSPP_SHEET_VIEW (column->tree_view)),
1416                                    column->sort_column_changed_signal);
1417       column->sort_column_changed_signal = 0;
1418     }
1419
1420   column->tree_view = NULL;
1421   column->button = NULL;
1422 }
1423
1424 gboolean
1425 _pspp_sheet_view_column_has_editable_cell (PsppSheetViewColumn *column)
1426 {
1427   GList *list;
1428
1429   for (list = column->cell_list; list; list = list->next)
1430     if (((PsppSheetViewColumnCellInfo *)list->data)->cell->mode ==
1431         GTK_CELL_RENDERER_MODE_EDITABLE)
1432       return TRUE;
1433
1434   return FALSE;
1435 }
1436
1437 /* gets cell being edited */
1438 GtkCellRenderer *
1439 _pspp_sheet_view_column_get_edited_cell (PsppSheetViewColumn *column)
1440 {
1441   GList *list;
1442
1443   for (list = column->cell_list; list; list = list->next)
1444     if (((PsppSheetViewColumnCellInfo *)list->data)->in_editing_mode)
1445       return ((PsppSheetViewColumnCellInfo *)list->data)->cell;
1446
1447   return NULL;
1448 }
1449
1450 gint
1451 _pspp_sheet_view_column_count_special_cells (PsppSheetViewColumn *column)
1452 {
1453   gint i = 0;
1454   GList *list;
1455
1456   for (list = column->cell_list; list; list = list->next)
1457     {
1458       PsppSheetViewColumnCellInfo *cellinfo = list->data;
1459
1460       if ((cellinfo->cell->mode == GTK_CELL_RENDERER_MODE_EDITABLE ||
1461           cellinfo->cell->mode == GTK_CELL_RENDERER_MODE_ACTIVATABLE) &&
1462           cellinfo->cell->visible)
1463         i++;
1464     }
1465
1466   return i;
1467 }
1468
1469 GtkCellRenderer *
1470 _pspp_sheet_view_column_get_cell_at_pos (PsppSheetViewColumn *column,
1471                                        gint               x)
1472 {
1473   GList *list;
1474   gint current_x = 0;
1475
1476   list = pspp_sheet_view_column_cell_first (column);
1477   for (; list; list = pspp_sheet_view_column_cell_next (column, list))
1478    {
1479      PsppSheetViewColumnCellInfo *cellinfo = list->data;
1480      if (current_x <= x && x <= current_x + cellinfo->real_width)
1481        return cellinfo->cell;
1482      current_x += cellinfo->real_width;
1483    }
1484
1485   return NULL;
1486 }
1487
1488 /* Public Functions */
1489
1490
1491 /**
1492  * pspp_sheet_view_column_new:
1493  * 
1494  * Creates a new #PsppSheetViewColumn.
1495  * 
1496  * Return value: A newly created #PsppSheetViewColumn.
1497  **/
1498 PsppSheetViewColumn *
1499 pspp_sheet_view_column_new (void)
1500 {
1501   PsppSheetViewColumn *tree_column;
1502
1503   tree_column = g_object_new (PSPP_TYPE_SHEET_VIEW_COLUMN, NULL);
1504
1505   return tree_column;
1506 }
1507
1508 /**
1509  * pspp_sheet_view_column_new_with_attributes:
1510  * @title: The title to set the header to.
1511  * @cell: The #GtkCellRenderer.
1512  * @Varargs: A %NULL-terminated list of attributes.
1513  * 
1514  * Creates a new #PsppSheetViewColumn with a number of default values.  This is
1515  * equivalent to calling pspp_sheet_view_column_set_title(),
1516  * pspp_sheet_view_column_pack_start(), and
1517  * pspp_sheet_view_column_set_attributes() on the newly created #PsppSheetViewColumn.
1518  *
1519  * Here's a simple example:
1520  * |[
1521  *  enum { TEXT_COLUMN, COLOR_COLUMN, N_COLUMNS };
1522  *  ...
1523  *  {
1524  *    PsppSheetViewColumn *column;
1525  *    GtkCellRenderer   *renderer = gtk_cell_renderer_text_new ();
1526  *  
1527  *    column = pspp_sheet_view_column_new_with_attributes ("Title",
1528  *                                                       renderer,
1529  *                                                       "text", TEXT_COLUMN,
1530  *                                                       "foreground", COLOR_COLUMN,
1531  *                                                       NULL);
1532  *  }
1533  * ]|
1534  * 
1535  * Return value: A newly created #PsppSheetViewColumn.
1536  **/
1537 PsppSheetViewColumn *
1538 pspp_sheet_view_column_new_with_attributes (const gchar     *title,
1539                                           GtkCellRenderer *cell,
1540                                           ...)
1541 {
1542   PsppSheetViewColumn *retval;
1543   va_list args;
1544
1545   retval = pspp_sheet_view_column_new ();
1546
1547   pspp_sheet_view_column_set_title (retval, title);
1548   pspp_sheet_view_column_pack_start (retval, cell, TRUE);
1549
1550   va_start (args, cell);
1551   pspp_sheet_view_column_set_attributesv (retval, cell, args);
1552   va_end (args);
1553
1554   return retval;
1555 }
1556
1557 static PsppSheetViewColumnCellInfo *
1558 pspp_sheet_view_column_get_cell_info (PsppSheetViewColumn *tree_column,
1559                                     GtkCellRenderer   *cell_renderer)
1560 {
1561   GList *list;
1562   for (list = tree_column->cell_list; list; list = list->next)
1563     if (((PsppSheetViewColumnCellInfo *)list->data)->cell == cell_renderer)
1564       return (PsppSheetViewColumnCellInfo *) list->data;
1565   return NULL;
1566 }
1567
1568
1569 /**
1570  * pspp_sheet_view_column_pack_start:
1571  * @tree_column: A #PsppSheetViewColumn.
1572  * @cell: The #GtkCellRenderer. 
1573  * @expand: %TRUE if @cell is to be given extra space allocated to @tree_column.
1574  *
1575  * Packs the @cell into the beginning of the column. If @expand is %FALSE, then
1576  * the @cell is allocated no more space than it needs. Any unused space is divided
1577  * evenly between cells for which @expand is %TRUE.
1578  **/
1579 void
1580 pspp_sheet_view_column_pack_start (PsppSheetViewColumn *tree_column,
1581                                  GtkCellRenderer   *cell,
1582                                  gboolean           expand)
1583 {
1584   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (tree_column), cell, expand);
1585 }
1586
1587 /**
1588  * pspp_sheet_view_column_pack_end:
1589  * @tree_column: A #PsppSheetViewColumn.
1590  * @cell: The #GtkCellRenderer. 
1591  * @expand: %TRUE if @cell is to be given extra space allocated to @tree_column.
1592  *
1593  * Adds the @cell to end of the column. If @expand is %FALSE, then the @cell
1594  * is allocated no more space than it needs. Any unused space is divided
1595  * evenly between cells for which @expand is %TRUE.
1596  **/
1597 void
1598 pspp_sheet_view_column_pack_end (PsppSheetViewColumn  *tree_column,
1599                                GtkCellRenderer    *cell,
1600                                gboolean            expand)
1601 {
1602   gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (tree_column), cell, expand);
1603 }
1604
1605 /**
1606  * pspp_sheet_view_column_clear:
1607  * @tree_column: A #PsppSheetViewColumn
1608  * 
1609  * Unsets all the mappings on all renderers on the @tree_column.
1610  **/
1611 void
1612 pspp_sheet_view_column_clear (PsppSheetViewColumn *tree_column)
1613 {
1614   gtk_cell_layout_clear (GTK_CELL_LAYOUT (tree_column));
1615 }
1616
1617 static GList *
1618 pspp_sheet_view_column_cell_layout_get_cells (GtkCellLayout *layout)
1619 {
1620   PsppSheetViewColumn *tree_column = PSPP_SHEET_VIEW_COLUMN (layout);
1621   GList *retval = NULL, *list;
1622
1623   g_return_val_if_fail (tree_column != NULL, NULL);
1624
1625   for (list = tree_column->cell_list; list; list = list->next)
1626     {
1627       PsppSheetViewColumnCellInfo *info = (PsppSheetViewColumnCellInfo *)list->data;
1628
1629       retval = g_list_append (retval, info->cell);
1630     }
1631
1632   return retval;
1633 }
1634
1635 /**
1636  * pspp_sheet_view_column_get_cell_renderers:
1637  * @tree_column: A #PsppSheetViewColumn
1638  *
1639  * Returns a newly-allocated #GList of all the cell renderers in the column,
1640  * in no particular order.  The list must be freed with g_list_free().
1641  *
1642  * Return value: A list of #GtkCellRenderers
1643  *
1644  * Deprecated: 2.18: use gtk_cell_layout_get_cells() instead.
1645  **/
1646 GList *
1647 pspp_sheet_view_column_get_cell_renderers (PsppSheetViewColumn *tree_column)
1648 {
1649   return pspp_sheet_view_column_cell_layout_get_cells (GTK_CELL_LAYOUT (tree_column));
1650 }
1651
1652 /**
1653  * pspp_sheet_view_column_add_attribute:
1654  * @tree_column: A #PsppSheetViewColumn.
1655  * @cell_renderer: the #GtkCellRenderer to set attributes on
1656  * @attribute: An attribute on the renderer
1657  * @column: The column position on the model to get the attribute from.
1658  * 
1659  * Adds an attribute mapping to the list in @tree_column.  The @column is the
1660  * column of the model to get a value from, and the @attribute is the
1661  * parameter on @cell_renderer to be set from the value. So for example
1662  * if column 2 of the model contains strings, you could have the
1663  * "text" attribute of a #GtkCellRendererText get its values from
1664  * column 2.
1665  **/
1666 void
1667 pspp_sheet_view_column_add_attribute (PsppSheetViewColumn *tree_column,
1668                                     GtkCellRenderer   *cell_renderer,
1669                                     const gchar       *attribute,
1670                                     gint               column)
1671 {
1672   gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (tree_column),
1673                                  cell_renderer, attribute, column);
1674 }
1675
1676 static void
1677 pspp_sheet_view_column_set_attributesv (PsppSheetViewColumn *tree_column,
1678                                       GtkCellRenderer   *cell_renderer,
1679                                       va_list            args)
1680 {
1681   gchar *attribute;
1682   gint column;
1683
1684   attribute = va_arg (args, gchar *);
1685
1686   pspp_sheet_view_column_clear_attributes (tree_column, cell_renderer);
1687   
1688   while (attribute != NULL)
1689     {
1690       column = va_arg (args, gint);
1691       pspp_sheet_view_column_add_attribute (tree_column, cell_renderer, attribute, column);
1692       attribute = va_arg (args, gchar *);
1693     }
1694 }
1695
1696 /**
1697  * pspp_sheet_view_column_set_attributes:
1698  * @tree_column: A #PsppSheetViewColumn.
1699  * @cell_renderer: the #GtkCellRenderer we're setting the attributes of
1700  * @Varargs: A %NULL-terminated list of attributes.
1701  * 
1702  * Sets the attributes in the list as the attributes of @tree_column.
1703  * The attributes should be in attribute/column order, as in
1704  * pspp_sheet_view_column_add_attribute(). All existing attributes
1705  * are removed, and replaced with the new attributes.
1706  **/
1707 void
1708 pspp_sheet_view_column_set_attributes (PsppSheetViewColumn *tree_column,
1709                                      GtkCellRenderer   *cell_renderer,
1710                                      ...)
1711 {
1712   va_list args;
1713
1714   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
1715   g_return_if_fail (GTK_IS_CELL_RENDERER (cell_renderer));
1716   g_return_if_fail (pspp_sheet_view_column_get_cell_info (tree_column, cell_renderer));
1717
1718   va_start (args, cell_renderer);
1719   pspp_sheet_view_column_set_attributesv (tree_column, cell_renderer, args);
1720   va_end (args);
1721 }
1722
1723
1724 /**
1725  * pspp_sheet_view_column_set_cell_data_func:
1726  * @tree_column: A #PsppSheetViewColumn
1727  * @cell_renderer: A #GtkCellRenderer
1728  * @func: The #PsppSheetViewColumnFunc to use. 
1729  * @func_data: The user data for @func.
1730  * @destroy: The destroy notification for @func_data
1731  * 
1732  * Sets the #PsppSheetViewColumnFunc to use for the column.  This
1733  * function is used instead of the standard attributes mapping for
1734  * setting the column value, and should set the value of @tree_column's
1735  * cell renderer as appropriate.  @func may be %NULL to remove an
1736  * older one.
1737  **/
1738 void
1739 pspp_sheet_view_column_set_cell_data_func (PsppSheetViewColumn   *tree_column,
1740                                          GtkCellRenderer     *cell_renderer,
1741                                          PsppSheetCellDataFunc  func,
1742                                          gpointer             func_data,
1743                                          GDestroyNotify       destroy)
1744 {
1745   gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (tree_column),
1746                                       cell_renderer,
1747                                       (GtkCellLayoutDataFunc)func,
1748                                       func_data, destroy);
1749 }
1750
1751
1752 /**
1753  * pspp_sheet_view_column_clear_attributes:
1754  * @tree_column: a #PsppSheetViewColumn
1755  * @cell_renderer: a #GtkCellRenderer to clear the attribute mapping on.
1756  * 
1757  * Clears all existing attributes previously set with
1758  * pspp_sheet_view_column_set_attributes().
1759  **/
1760 void
1761 pspp_sheet_view_column_clear_attributes (PsppSheetViewColumn *tree_column,
1762                                        GtkCellRenderer   *cell_renderer)
1763 {
1764   gtk_cell_layout_clear_attributes (GTK_CELL_LAYOUT (tree_column),
1765                                     cell_renderer);
1766 }
1767
1768 /**
1769  * pspp_sheet_view_column_set_spacing:
1770  * @tree_column: A #PsppSheetViewColumn.
1771  * @spacing: distance between cell renderers in pixels.
1772  * 
1773  * Sets the spacing field of @tree_column, which is the number of pixels to
1774  * place between cell renderers packed into it.
1775  **/
1776 void
1777 pspp_sheet_view_column_set_spacing (PsppSheetViewColumn *tree_column,
1778                                   gint               spacing)
1779 {
1780   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
1781   g_return_if_fail (spacing >= 0);
1782
1783   if (tree_column->spacing == spacing)
1784     return;
1785
1786   tree_column->spacing = spacing;
1787   if (tree_column->tree_view)
1788     _pspp_sheet_view_column_cell_set_dirty (tree_column);
1789 }
1790
1791 /**
1792  * pspp_sheet_view_column_get_spacing:
1793  * @tree_column: A #PsppSheetViewColumn.
1794  * 
1795  * Returns the spacing of @tree_column.
1796  * 
1797  * Return value: the spacing of @tree_column.
1798  **/
1799 gint
1800 pspp_sheet_view_column_get_spacing (PsppSheetViewColumn *tree_column)
1801 {
1802   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), 0);
1803
1804   return tree_column->spacing;
1805 }
1806
1807 /* Options for manipulating the columns */
1808
1809 /**
1810  * pspp_sheet_view_column_set_visible:
1811  * @tree_column: A #PsppSheetViewColumn.
1812  * @visible: %TRUE if the @tree_column is visible.
1813  * 
1814  * Sets the visibility of @tree_column.
1815  **/
1816 void
1817 pspp_sheet_view_column_set_visible (PsppSheetViewColumn *tree_column,
1818                                   gboolean           visible)
1819 {
1820   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
1821
1822   visible = !! visible;
1823   
1824   if (tree_column->visible == visible)
1825     return;
1826
1827   tree_column->visible = visible;
1828
1829   if (tree_column->visible)
1830     _pspp_sheet_view_column_cell_set_dirty (tree_column);
1831
1832   pspp_sheet_view_column_update_button (tree_column);
1833   g_object_notify (G_OBJECT (tree_column), "visible");
1834 }
1835
1836 /**
1837  * pspp_sheet_view_column_get_visible:
1838  * @tree_column: A #PsppSheetViewColumn.
1839  * 
1840  * Returns %TRUE if @tree_column is visible.
1841  * 
1842  * Return value: whether the column is visible or not.  If it is visible, then
1843  * the tree will show the column.
1844  **/
1845 gboolean
1846 pspp_sheet_view_column_get_visible (PsppSheetViewColumn *tree_column)
1847 {
1848   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), FALSE);
1849
1850   return tree_column->visible;
1851 }
1852
1853 /**
1854  * pspp_sheet_view_column_set_resizable:
1855  * @tree_column: A #PsppSheetViewColumn
1856  * @resizable: %TRUE, if the column can be resized
1857  * 
1858  * If @resizable is %TRUE, then the user can explicitly resize the column by
1859  * grabbing the outer edge of the column button.
1860  **/
1861 void
1862 pspp_sheet_view_column_set_resizable (PsppSheetViewColumn *tree_column,
1863                                     gboolean           resizable)
1864 {
1865   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
1866
1867   resizable = !! resizable;
1868
1869   if (tree_column->resizable == resizable)
1870     return;
1871
1872   tree_column->resizable = resizable;
1873
1874   pspp_sheet_view_column_update_button (tree_column);
1875
1876   g_object_notify (G_OBJECT (tree_column), "resizable");
1877 }
1878
1879 /**
1880  * pspp_sheet_view_column_get_resizable:
1881  * @tree_column: A #PsppSheetViewColumn
1882  * 
1883  * Returns %TRUE if the @tree_column can be resized by the end user.
1884  * 
1885  * Return value: %TRUE, if the @tree_column can be resized.
1886  **/
1887 gboolean
1888 pspp_sheet_view_column_get_resizable (PsppSheetViewColumn *tree_column)
1889 {
1890   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), FALSE);
1891
1892   return tree_column->resizable;
1893 }
1894
1895
1896 /**
1897  * pspp_sheet_view_column_get_width:
1898  * @tree_column: A #PsppSheetViewColumn.
1899  * 
1900  * Returns the current size of @tree_column in pixels.
1901  * 
1902  * Return value: The current width of @tree_column.
1903  **/
1904 gint
1905 pspp_sheet_view_column_get_width (PsppSheetViewColumn *tree_column)
1906 {
1907   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), 0);
1908
1909   return tree_column->width;
1910 }
1911
1912 /**
1913  * pspp_sheet_view_column_set_fixed_width:
1914  * @tree_column: A #PsppSheetViewColumn.
1915  * @fixed_width: The size to set @tree_column to. Must be greater than 0.
1916  * 
1917  * Sets the size of the column in pixels.  The size of the column is clamped to
1918  * the min/max width for the column.  Please note that the min/max width of the
1919  * column doesn't actually affect the "fixed_width" property of the widget, just
1920  * the actual size when displayed.
1921  **/
1922 void
1923 pspp_sheet_view_column_set_fixed_width (PsppSheetViewColumn *tree_column,
1924                                       gint               fixed_width)
1925 {
1926   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
1927   g_return_if_fail (fixed_width > 0);
1928
1929   tree_column->fixed_width = fixed_width;
1930   tree_column->use_resized_width = FALSE;
1931
1932   if (tree_column->tree_view &&
1933       gtk_widget_get_realized (tree_column->tree_view))
1934     {
1935       gtk_widget_queue_resize (tree_column->tree_view);
1936     }
1937
1938   g_object_notify (G_OBJECT (tree_column), "fixed-width");
1939 }
1940
1941 /**
1942  * pspp_sheet_view_column_get_fixed_width:
1943  * @tree_column: a #PsppSheetViewColumn
1944  * 
1945  * Gets the fixed width of the column.  This value is only meaning may not be
1946  * the actual width of the column on the screen, just what is requested.
1947  * 
1948  * Return value: the fixed width of the column
1949  **/
1950 gint
1951 pspp_sheet_view_column_get_fixed_width (PsppSheetViewColumn *tree_column)
1952 {
1953   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), 0);
1954
1955   return tree_column->fixed_width;
1956 }
1957
1958 /**
1959  * pspp_sheet_view_column_set_min_width:
1960  * @tree_column: A #PsppSheetViewColumn.
1961  * @min_width: The minimum width of the column in pixels, or -1.
1962  * 
1963  * Sets the minimum width of the @tree_column.  If @min_width is -1, then the
1964  * minimum width is unset.
1965  **/
1966 void
1967 pspp_sheet_view_column_set_min_width (PsppSheetViewColumn *tree_column,
1968                                     gint               min_width)
1969 {
1970   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
1971   g_return_if_fail (min_width >= -1);
1972
1973   if (min_width == tree_column->min_width)
1974     return;
1975
1976   if (tree_column->visible &&
1977       tree_column->tree_view != NULL &&
1978       gtk_widget_get_realized (tree_column->tree_view))
1979     {
1980       if (min_width > tree_column->width)
1981         gtk_widget_queue_resize (tree_column->tree_view);
1982     }
1983
1984   tree_column->min_width = min_width;
1985   g_object_freeze_notify (G_OBJECT (tree_column));
1986   if (tree_column->max_width != -1 && tree_column->max_width < min_width)
1987     {
1988       tree_column->max_width = min_width;
1989       g_object_notify (G_OBJECT (tree_column), "max-width");
1990     }
1991   g_object_notify (G_OBJECT (tree_column), "min-width");
1992   g_object_thaw_notify (G_OBJECT (tree_column));
1993 }
1994
1995 /**
1996  * pspp_sheet_view_column_get_min_width:
1997  * @tree_column: A #PsppSheetViewColumn.
1998  * 
1999  * Returns the minimum width in pixels of the @tree_column, or -1 if no minimum
2000  * width is set.
2001  * 
2002  * Return value: The minimum width of the @tree_column.
2003  **/
2004 gint
2005 pspp_sheet_view_column_get_min_width (PsppSheetViewColumn *tree_column)
2006 {
2007   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), -1);
2008
2009   return tree_column->min_width;
2010 }
2011
2012 /**
2013  * pspp_sheet_view_column_set_max_width:
2014  * @tree_column: A #PsppSheetViewColumn.
2015  * @max_width: The maximum width of the column in pixels, or -1.
2016  * 
2017  * Sets the maximum width of the @tree_column.  If @max_width is -1, then the
2018  * maximum width is unset.  Note, the column can actually be wider than max
2019  * width if it's the last column in a view.  In this case, the column expands to
2020  * fill any extra space.
2021  **/
2022 void
2023 pspp_sheet_view_column_set_max_width (PsppSheetViewColumn *tree_column,
2024                                     gint               max_width)
2025 {
2026   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
2027   g_return_if_fail (max_width >= -1);
2028
2029   if (max_width == tree_column->max_width)
2030     return;
2031
2032   if (tree_column->visible &&
2033       tree_column->tree_view != NULL &&
2034       gtk_widget_get_realized (tree_column->tree_view))
2035     {
2036       if (max_width != -1 && max_width < tree_column->width)
2037         gtk_widget_queue_resize (tree_column->tree_view);
2038     }
2039
2040   tree_column->max_width = max_width;
2041   g_object_freeze_notify (G_OBJECT (tree_column));
2042   if (max_width != -1 && max_width < tree_column->min_width)
2043     {
2044       tree_column->min_width = max_width;
2045       g_object_notify (G_OBJECT (tree_column), "min-width");
2046     }
2047   g_object_notify (G_OBJECT (tree_column), "max-width");
2048   g_object_thaw_notify (G_OBJECT (tree_column));
2049 }
2050
2051 /**
2052  * pspp_sheet_view_column_get_max_width:
2053  * @tree_column: A #PsppSheetViewColumn.
2054  * 
2055  * Returns the maximum width in pixels of the @tree_column, or -1 if no maximum
2056  * width is set.
2057  * 
2058  * Return value: The maximum width of the @tree_column.
2059  **/
2060 gint
2061 pspp_sheet_view_column_get_max_width (PsppSheetViewColumn *tree_column)
2062 {
2063   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), -1);
2064
2065   return tree_column->max_width;
2066 }
2067
2068 /**
2069  * pspp_sheet_view_column_clicked:
2070  * @tree_column: a #PsppSheetViewColumn
2071  * 
2072  * Emits the "clicked" signal on the column.  This function will only work if
2073  * @tree_column is clickable.
2074  **/
2075 void
2076 pspp_sheet_view_column_clicked (PsppSheetViewColumn *tree_column)
2077 {
2078   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
2079
2080   if (tree_column->visible &&
2081       tree_column->button &&
2082       tree_column->clickable)
2083     gtk_button_clicked (GTK_BUTTON (tree_column->button));
2084 }
2085
2086 /**
2087  * pspp_sheet_view_column_set_title:
2088  * @tree_column: A #PsppSheetViewColumn.
2089  * @title: The title of the @tree_column.
2090  * 
2091  * Sets the title of the @tree_column.  If a custom widget has been set, then
2092  * this value is ignored.
2093  **/
2094 void
2095 pspp_sheet_view_column_set_title (PsppSheetViewColumn *tree_column,
2096                                 const gchar       *title)
2097 {
2098   gchar *new_title;
2099   
2100   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
2101
2102   new_title = g_strdup (title);
2103   g_free (tree_column->title);
2104   tree_column->title = new_title;
2105
2106   pspp_sheet_view_column_update_button (tree_column);
2107   g_object_notify (G_OBJECT (tree_column), "title");
2108 }
2109
2110 /**
2111  * pspp_sheet_view_column_get_title:
2112  * @tree_column: A #PsppSheetViewColumn.
2113  * 
2114  * Returns the title of the widget.
2115  * 
2116  * Return value: the title of the column. This string should not be
2117  * modified or freed.
2118  **/
2119 G_CONST_RETURN gchar *
2120 pspp_sheet_view_column_get_title (PsppSheetViewColumn *tree_column)
2121 {
2122   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), NULL);
2123
2124   return tree_column->title;
2125 }
2126
2127 /**
2128  * pspp_sheet_view_column_set_expand:
2129  * @tree_column: A #PsppSheetViewColumn
2130  * @expand: %TRUE if the column should take available extra space, %FALSE if not
2131  * 
2132  * Sets the column to take available extra space.  This space is shared equally
2133  * amongst all columns that have the expand set to %TRUE.  If no column has this
2134  * option set, then the last column gets all extra space.  By default, every
2135  * column is created with this %FALSE.
2136  *
2137  * Since: 2.4
2138  **/
2139 void
2140 pspp_sheet_view_column_set_expand (PsppSheetViewColumn *tree_column,
2141                                  gboolean           expand)
2142 {
2143   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
2144
2145   expand = expand?TRUE:FALSE;
2146   if (tree_column->expand == expand)
2147     return;
2148   tree_column->expand = expand;
2149
2150   if (tree_column->visible &&
2151       tree_column->tree_view != NULL &&
2152       gtk_widget_get_realized (tree_column->tree_view))
2153     {
2154       /* We want to continue using the original width of the
2155        * column that includes additional space added by the user
2156        * resizing the columns and possibly extra (expanded) space, which
2157        * are not included in the resized width.
2158        */
2159       tree_column->use_resized_width = FALSE;
2160
2161       gtk_widget_queue_resize (tree_column->tree_view);
2162     }
2163
2164   g_object_notify (G_OBJECT (tree_column), "expand");
2165 }
2166
2167 /**
2168  * pspp_sheet_view_column_get_expand:
2169  * @tree_column: a #PsppSheetViewColumn
2170  * 
2171  * Return %TRUE if the column expands to take any available space.
2172  * 
2173  * Return value: %TRUE, if the column expands
2174  *
2175  * Since: 2.4
2176  **/
2177 gboolean
2178 pspp_sheet_view_column_get_expand (PsppSheetViewColumn *tree_column)
2179 {
2180   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), FALSE);
2181
2182   return tree_column->expand;
2183 }
2184
2185 /**
2186  * pspp_sheet_view_column_set_clickable:
2187  * @tree_column: A #PsppSheetViewColumn.
2188  * @clickable: %TRUE if the header is active.
2189  * 
2190  * Sets the header to be active if @active is %TRUE.  When the header is active,
2191  * then it can take keyboard focus, and can be clicked.
2192  **/
2193 void
2194 pspp_sheet_view_column_set_clickable (PsppSheetViewColumn *tree_column,
2195                                     gboolean           clickable)
2196 {
2197   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
2198
2199   clickable = !! clickable;
2200   if (tree_column->clickable == clickable)
2201     return;
2202
2203   tree_column->clickable = clickable;
2204   pspp_sheet_view_column_update_button (tree_column);
2205   g_object_notify (G_OBJECT (tree_column), "clickable");
2206 }
2207
2208 /**
2209  * pspp_sheet_view_column_get_clickable:
2210  * @tree_column: a #PsppSheetViewColumn
2211  * 
2212  * Returns %TRUE if the user can click on the header for the column.
2213  * 
2214  * Return value: %TRUE if user can click the column header.
2215  **/
2216 gboolean
2217 pspp_sheet_view_column_get_clickable (PsppSheetViewColumn *tree_column)
2218 {
2219   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), FALSE);
2220
2221   return tree_column->clickable;
2222 }
2223
2224 /**
2225  * pspp_sheet_view_column_set_widget:
2226  * @tree_column: A #PsppSheetViewColumn.
2227  * @widget: (allow-none): A child #GtkWidget, or %NULL.
2228  *
2229  * Sets the widget in the header to be @widget.  If widget is %NULL, then the
2230  * header button is set with a #GtkLabel set to the title of @tree_column.
2231  **/
2232 void
2233 pspp_sheet_view_column_set_widget (PsppSheetViewColumn *tree_column,
2234                                  GtkWidget         *widget)
2235 {
2236   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
2237   g_return_if_fail (widget == NULL || GTK_IS_WIDGET (widget));
2238
2239   if (widget)
2240     g_object_ref_sink (widget);
2241
2242   if (tree_column->child)      
2243     g_object_unref (tree_column->child);
2244
2245   tree_column->child = widget;
2246   pspp_sheet_view_column_update_button (tree_column);
2247   g_object_notify (G_OBJECT (tree_column), "widget");
2248 }
2249
2250 /**
2251  * pspp_sheet_view_column_get_widget:
2252  * @tree_column: A #PsppSheetViewColumn.
2253  * 
2254  * Returns the #GtkWidget in the button on the column header.  If a custom
2255  * widget has not been set then %NULL is returned.
2256  * 
2257  * Return value: The #GtkWidget in the column header, or %NULL
2258  **/
2259 GtkWidget *
2260 pspp_sheet_view_column_get_widget (PsppSheetViewColumn *tree_column)
2261 {
2262   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), NULL);
2263
2264   return tree_column->child;
2265 }
2266
2267 /**
2268  * pspp_sheet_view_column_set_alignment:
2269  * @tree_column: A #PsppSheetViewColumn.
2270  * @xalign: The alignment, which is between [0.0 and 1.0] inclusive.
2271  * 
2272  * Sets the alignment of the title or custom widget inside the column header.
2273  * The alignment determines its location inside the button -- 0.0 for left, 0.5
2274  * for center, 1.0 for right.
2275  **/
2276 void
2277 pspp_sheet_view_column_set_alignment (PsppSheetViewColumn *tree_column,
2278                                     gfloat             xalign)
2279 {
2280   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
2281
2282   xalign = CLAMP (xalign, 0.0, 1.0);
2283
2284   if (tree_column->xalign == xalign)
2285     return;
2286
2287   tree_column->xalign = xalign;
2288   pspp_sheet_view_column_update_button (tree_column);
2289   g_object_notify (G_OBJECT (tree_column), "alignment");
2290 }
2291
2292 /**
2293  * pspp_sheet_view_column_get_alignment:
2294  * @tree_column: A #PsppSheetViewColumn.
2295  * 
2296  * Returns the current x alignment of @tree_column.  This value can range
2297  * between 0.0 and 1.0.
2298  * 
2299  * Return value: The current alignent of @tree_column.
2300  **/
2301 gfloat
2302 pspp_sheet_view_column_get_alignment (PsppSheetViewColumn *tree_column)
2303 {
2304   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), 0.5);
2305
2306   return tree_column->xalign;
2307 }
2308
2309 /**
2310  * pspp_sheet_view_column_set_reorderable:
2311  * @tree_column: A #PsppSheetViewColumn
2312  * @reorderable: %TRUE, if the column can be reordered.
2313  * 
2314  * If @reorderable is %TRUE, then the column can be reordered by the end user
2315  * dragging the header.
2316  **/
2317 void
2318 pspp_sheet_view_column_set_reorderable (PsppSheetViewColumn *tree_column,
2319                                       gboolean           reorderable)
2320 {
2321   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
2322
2323   /*  if (reorderable)
2324       pspp_sheet_view_column_set_clickable (tree_column, TRUE);*/
2325
2326   if (tree_column->reorderable == (reorderable?TRUE:FALSE))
2327     return;
2328
2329   tree_column->reorderable = (reorderable?TRUE:FALSE);
2330   pspp_sheet_view_column_update_button (tree_column);
2331   g_object_notify (G_OBJECT (tree_column), "reorderable");
2332 }
2333
2334 /**
2335  * pspp_sheet_view_column_get_reorderable:
2336  * @tree_column: A #PsppSheetViewColumn
2337  * 
2338  * Returns %TRUE if the @tree_column can be reordered by the user.
2339  * 
2340  * Return value: %TRUE if the @tree_column can be reordered by the user.
2341  **/
2342 gboolean
2343 pspp_sheet_view_column_get_reorderable (PsppSheetViewColumn *tree_column)
2344 {
2345   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), FALSE);
2346
2347   return tree_column->reorderable;
2348 }
2349
2350 /**
2351  * pspp_sheet_view_column_set_quick_edit:
2352  * @tree_column: A #PsppSheetViewColumn
2353  * @quick_edit: If true, editing starts upon the first click in the column.  If
2354  * false, the first click selects the column and a second click is needed to
2355  * begin editing.  This has no effect on cells that are not editable.
2356  **/
2357 void
2358 pspp_sheet_view_column_set_quick_edit (PsppSheetViewColumn *tree_column,
2359                                       gboolean           quick_edit)
2360 {
2361   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
2362
2363   quick_edit = !!quick_edit;
2364   if (tree_column->quick_edit != quick_edit)
2365     {
2366       tree_column->quick_edit = (quick_edit?TRUE:FALSE);
2367       g_object_notify (G_OBJECT (tree_column), "quick-edit");
2368     }
2369 }
2370
2371 /**
2372  * pspp_sheet_view_column_get_quick_edit:
2373  * @tree_column: A #PsppSheetViewColumn
2374  *
2375  * Returns %TRUE if editing starts upon the first click in the column.  Returns
2376  * %FALSE, the first click selects the column and a second click is needed to
2377  * begin editing.  This is not meaningful for cells that are not editable.
2378  *
2379  * Return value: %TRUE if editing starts upon the first click.
2380  **/
2381 gboolean
2382 pspp_sheet_view_column_get_quick_edit (PsppSheetViewColumn *tree_column)
2383 {
2384   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), FALSE);
2385
2386   return tree_column->quick_edit;
2387 }
2388
2389
2390 /**
2391  * pspp_sheet_view_column_set_sort_column_id:
2392  * @tree_column: a #PsppSheetViewColumn
2393  * @sort_column_id: The @sort_column_id of the model to sort on.
2394  *
2395  * Sets the logical @sort_column_id that this column sorts on when this column 
2396  * is selected for sorting.  Doing so makes the column header clickable.
2397  **/
2398 void
2399 pspp_sheet_view_column_set_sort_column_id (PsppSheetViewColumn *tree_column,
2400                                          gint               sort_column_id)
2401 {
2402   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
2403   g_return_if_fail (sort_column_id >= -1);
2404
2405   if (tree_column->sort_column_id == sort_column_id)
2406     return;
2407
2408   tree_column->sort_column_id = sort_column_id;
2409
2410   /* Handle unsetting the id */
2411   if (sort_column_id == -1)
2412     {
2413       GtkTreeModel *model = pspp_sheet_view_get_model (PSPP_SHEET_VIEW (tree_column->tree_view));
2414
2415       if (tree_column->sort_clicked_signal)
2416         {
2417           g_signal_handler_disconnect (tree_column, tree_column->sort_clicked_signal);
2418           tree_column->sort_clicked_signal = 0;
2419         }
2420
2421       if (tree_column->sort_column_changed_signal)
2422         {
2423           g_signal_handler_disconnect (model, tree_column->sort_column_changed_signal);
2424           tree_column->sort_column_changed_signal = 0;
2425         }
2426
2427       pspp_sheet_view_column_set_sort_order (tree_column, GTK_SORT_ASCENDING);
2428       pspp_sheet_view_column_set_sort_indicator (tree_column, FALSE);
2429       pspp_sheet_view_column_set_clickable (tree_column, FALSE);
2430       g_object_notify (G_OBJECT (tree_column), "sort-column-id");
2431       return;
2432     }
2433
2434   pspp_sheet_view_column_set_clickable (tree_column, TRUE);
2435
2436   if (! tree_column->sort_clicked_signal)
2437     tree_column->sort_clicked_signal = g_signal_connect (tree_column,
2438                                                          "clicked",
2439                                                          G_CALLBACK (pspp_sheet_view_column_sort),
2440                                                          NULL);
2441
2442   pspp_sheet_view_column_setup_sort_column_id_callback (tree_column);
2443   g_object_notify (G_OBJECT (tree_column), "sort-column-id");
2444 }
2445
2446 /**
2447  * pspp_sheet_view_column_get_sort_column_id:
2448  * @tree_column: a #PsppSheetViewColumn
2449  *
2450  * Gets the logical @sort_column_id that the model sorts on when this
2451  * column is selected for sorting.
2452  * See pspp_sheet_view_column_set_sort_column_id().
2453  *
2454  * Return value: the current @sort_column_id for this column, or -1 if
2455  *               this column can't be used for sorting.
2456  **/
2457 gint
2458 pspp_sheet_view_column_get_sort_column_id (PsppSheetViewColumn *tree_column)
2459 {
2460   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), 0);
2461
2462   return tree_column->sort_column_id;
2463 }
2464
2465 /**
2466  * pspp_sheet_view_column_set_sort_indicator:
2467  * @tree_column: a #PsppSheetViewColumn
2468  * @setting: %TRUE to display an indicator that the column is sorted
2469  *
2470  * Call this function with a @setting of %TRUE to display an arrow in
2471  * the header button indicating the column is sorted. Call
2472  * pspp_sheet_view_column_set_sort_order() to change the direction of
2473  * the arrow.
2474  * 
2475  **/
2476 void
2477 pspp_sheet_view_column_set_sort_indicator (PsppSheetViewColumn     *tree_column,
2478                                          gboolean               setting)
2479 {
2480   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
2481
2482   setting = setting != FALSE;
2483
2484   if (setting == tree_column->show_sort_indicator)
2485     return;
2486
2487   tree_column->show_sort_indicator = setting;
2488   pspp_sheet_view_column_update_button (tree_column);
2489   g_object_notify (G_OBJECT (tree_column), "sort-indicator");
2490 }
2491
2492 /**
2493  * pspp_sheet_view_column_get_sort_indicator:
2494  * @tree_column: a #PsppSheetViewColumn
2495  * 
2496  * Gets the value set by pspp_sheet_view_column_set_sort_indicator().
2497  * 
2498  * Return value: whether the sort indicator arrow is displayed
2499  **/
2500 gboolean
2501 pspp_sheet_view_column_get_sort_indicator  (PsppSheetViewColumn     *tree_column)
2502 {
2503   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), FALSE);
2504
2505   return tree_column->show_sort_indicator;
2506 }
2507
2508 /**
2509  * pspp_sheet_view_column_set_sort_order:
2510  * @tree_column: a #PsppSheetViewColumn
2511  * @order: sort order that the sort indicator should indicate
2512  *
2513  * Changes the appearance of the sort indicator. 
2514  * 
2515  * This <emphasis>does not</emphasis> actually sort the model.  Use
2516  * pspp_sheet_view_column_set_sort_column_id() if you want automatic sorting
2517  * support.  This function is primarily for custom sorting behavior, and should
2518  * be used in conjunction with gtk_tree_sortable_set_sort_column() to do
2519  * that. For custom models, the mechanism will vary. 
2520  * 
2521  * The sort indicator changes direction to indicate normal sort or reverse sort.
2522  * Note that you must have the sort indicator enabled to see anything when 
2523  * calling this function; see pspp_sheet_view_column_set_sort_indicator().
2524  **/
2525 void
2526 pspp_sheet_view_column_set_sort_order      (PsppSheetViewColumn     *tree_column,
2527                                           GtkSortType            order)
2528 {
2529   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
2530
2531   if (order == tree_column->sort_order)
2532     return;
2533
2534   tree_column->sort_order = order;
2535   pspp_sheet_view_column_update_button (tree_column);
2536   g_object_notify (G_OBJECT (tree_column), "sort-order");
2537 }
2538
2539 /**
2540  * pspp_sheet_view_column_get_sort_order:
2541  * @tree_column: a #PsppSheetViewColumn
2542  * 
2543  * Gets the value set by pspp_sheet_view_column_set_sort_order().
2544  * 
2545  * Return value: the sort order the sort indicator is indicating
2546  **/
2547 GtkSortType
2548 pspp_sheet_view_column_get_sort_order      (PsppSheetViewColumn     *tree_column)
2549 {
2550   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), 0);
2551
2552   return tree_column->sort_order;
2553 }
2554
2555 /**
2556  * pspp_sheet_view_column_cell_set_cell_data:
2557  * @tree_column: A #PsppSheetViewColumn.
2558  * @tree_model: The #GtkTreeModel to to get the cell renderers attributes from.
2559  * @iter: The #GtkTreeIter to to get the cell renderer's attributes from.
2560  * 
2561  * Sets the cell renderer based on the @tree_model and @iter.  That is, for
2562  * every attribute mapping in @tree_column, it will get a value from the set
2563  * column on the @iter, and use that value to set the attribute on the cell
2564  * renderer.  This is used primarily by the #PsppSheetView.
2565  **/
2566 void
2567 pspp_sheet_view_column_cell_set_cell_data (PsppSheetViewColumn *tree_column,
2568                                          GtkTreeModel      *tree_model,
2569                                          GtkTreeIter       *iter)
2570 {
2571   GSList *list;
2572   GValue value = { 0, };
2573   GList *cell_list;
2574
2575   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
2576
2577   if (tree_model == NULL)
2578     return;
2579
2580   for (cell_list = tree_column->cell_list; cell_list; cell_list = cell_list->next)
2581     {
2582       PsppSheetViewColumnCellInfo *info = (PsppSheetViewColumnCellInfo *) cell_list->data;
2583       GObject *cell = (GObject *) info->cell;
2584
2585       list = info->attributes;
2586
2587       g_object_freeze_notify (cell);
2588
2589       while (list && list->next)
2590         {
2591           gtk_tree_model_get_value (tree_model, iter,
2592                                     GPOINTER_TO_INT (list->next->data),
2593                                     &value);
2594           g_object_set_property (cell, (gchar *) list->data, &value);
2595           g_value_unset (&value);
2596           list = list->next->next;
2597         }
2598
2599       if (info->func)
2600         (* info->func) (tree_column, info->cell, tree_model, iter, info->func_data);
2601       g_object_thaw_notify (G_OBJECT (info->cell));
2602     }
2603
2604 }
2605
2606 /**
2607  * pspp_sheet_view_column_cell_get_size:
2608  * @tree_column: A #PsppSheetViewColumn.
2609  * @cell_area: (allow-none): The area a cell in the column will be allocated, or %NULL
2610  * @x_offset: (allow-none): location to return x offset of a cell relative to @cell_area, or %NULL
2611  * @y_offset: (allow-none): location to return y offset of a cell relative to @cell_area, or %NULL
2612  * @width: (allow-none): location to return width needed to render a cell, or %NULL
2613  * @height: (allow-none): location to return height needed to render a cell, or %NULL
2614  * 
2615  * Obtains the width and height needed to render the column.  This is used
2616  * primarily by the #PsppSheetView.
2617  **/
2618 void
2619 pspp_sheet_view_column_cell_get_size (PsppSheetViewColumn  *tree_column,
2620                                     const GdkRectangle *cell_area,
2621                                     gint               *x_offset,
2622                                     gint               *y_offset,
2623                                     gint               *width,
2624                                     gint               *height)
2625 {
2626   GList *list;
2627   gboolean first_cell = TRUE;
2628   gint focus_line_width;
2629
2630   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
2631
2632   if (height)
2633     * height = 0;
2634   if (width)
2635     * width = 0;
2636
2637   gtk_widget_style_get (tree_column->tree_view, "focus-line-width", &focus_line_width, NULL);
2638   
2639   for (list = tree_column->cell_list; list; list = list->next)
2640     {
2641       PsppSheetViewColumnCellInfo *info = (PsppSheetViewColumnCellInfo *) list->data;
2642       gboolean visible;
2643       gint new_height = 0;
2644       gint new_width = 0;
2645       g_object_get (info->cell, "visible", &visible, NULL);
2646
2647       if (visible == FALSE)
2648         continue;
2649
2650       if (first_cell == FALSE && width)
2651         *width += tree_column->spacing;
2652
2653       gtk_cell_renderer_get_size (info->cell,
2654                                   tree_column->tree_view,
2655                                   cell_area,
2656                                   x_offset,
2657                                   y_offset,
2658                                   &new_width,
2659                                   &new_height);
2660
2661       if (height)
2662         * height = MAX (*height, new_height + focus_line_width * 2);
2663       info->requested_width = MAX (info->requested_width, new_width + focus_line_width * 2);
2664       if (width)
2665         * width += info->requested_width;
2666       first_cell = FALSE;
2667     }
2668 }
2669
2670 /* rendering, event handling and rendering focus are somewhat complicated, and
2671  * quite a bit of code.  Rather than duplicate them, we put them together to
2672  * keep the code in one place.
2673  *
2674  * To better understand what's going on, check out
2675  * docs/tree-column-sizing.png
2676  */
2677 enum {
2678   CELL_ACTION_RENDER,
2679   CELL_ACTION_FOCUS,
2680   CELL_ACTION_EVENT
2681 };
2682
2683 static gboolean
2684 pspp_sheet_view_column_cell_process_action (PsppSheetViewColumn  *tree_column,
2685                                           GdkWindow          *window,
2686                                           const GdkRectangle *background_area,
2687                                           const GdkRectangle *cell_area,
2688                                           guint               flags,
2689                                           gint                action,
2690                                           const GdkRectangle *expose_area,     /* RENDER */
2691                                           GdkRectangle       *focus_rectangle, /* FOCUS  */
2692                                           GtkCellEditable   **editable_widget, /* EVENT  */
2693                                           GdkEvent           *event,           /* EVENT  */
2694                                           gchar              *path_string)     /* EVENT  */
2695 {
2696   GList *list;
2697   GdkRectangle real_cell_area;
2698   GdkRectangle real_background_area;
2699   GdkRectangle real_expose_area = *cell_area;
2700   gint depth = 0;
2701   gint expand_cell_count = 0;
2702   gint full_requested_width = 0;
2703   gint extra_space;
2704   gint min_x, min_y, max_x, max_y;
2705   gint focus_line_width;
2706   gint special_cells;
2707   gint horizontal_separator;
2708   gboolean cursor_row = FALSE;
2709   gboolean first_cell = TRUE;
2710   gboolean rtl;
2711   /* If we have rtl text, we need to transform our areas */
2712   GdkRectangle rtl_cell_area;
2713   GdkRectangle rtl_background_area;
2714
2715   min_x = G_MAXINT;
2716   min_y = G_MAXINT;
2717   max_x = 0;
2718   max_y = 0;
2719
2720   rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_column->tree_view)) == GTK_TEXT_DIR_RTL);
2721   special_cells = _pspp_sheet_view_column_count_special_cells (tree_column);
2722
2723   if (special_cells > 1 && action == CELL_ACTION_FOCUS)
2724     {
2725       PsppSheetViewColumnCellInfo *info = NULL;
2726       gboolean found_has_focus = FALSE;
2727
2728       /* one should have focus */
2729       for (list = tree_column->cell_list; list; list = list->next)
2730         {
2731           info = list->data;
2732           if (info && info->has_focus)
2733             {
2734               found_has_focus = TRUE;
2735               break;
2736             }
2737         }
2738
2739       if (!found_has_focus)
2740         {
2741           /* give the first one focus */
2742           info = pspp_sheet_view_column_cell_first (tree_column)->data;
2743           info->has_focus = TRUE;
2744         }
2745     }
2746
2747   cursor_row = flags & GTK_CELL_RENDERER_FOCUSED;
2748
2749   gtk_widget_style_get (GTK_WIDGET (tree_column->tree_view),
2750                         "focus-line-width", &focus_line_width,
2751                         "horizontal-separator", &horizontal_separator,
2752                         NULL);
2753
2754   real_cell_area = *cell_area;
2755   real_background_area = *background_area;
2756
2757
2758   real_cell_area.x += focus_line_width;
2759   real_cell_area.y += focus_line_width;
2760   real_cell_area.height -= 2 * focus_line_width;
2761
2762   if (rtl)
2763     depth = real_background_area.width - real_cell_area.width;
2764   else
2765     depth = real_cell_area.x - real_background_area.x;
2766
2767   /* Find out how much extra space we have to allocate */
2768   for (list = tree_column->cell_list; list; list = list->next)
2769     {
2770       PsppSheetViewColumnCellInfo *info = (PsppSheetViewColumnCellInfo *)list->data;
2771
2772       if (! info->cell->visible)
2773         continue;
2774
2775       if (info->expand == TRUE)
2776         expand_cell_count ++;
2777       full_requested_width += info->requested_width;
2778
2779       if (!first_cell)
2780         full_requested_width += tree_column->spacing;
2781
2782       first_cell = FALSE;
2783     }
2784
2785   extra_space = cell_area->width - full_requested_width;
2786   if (extra_space < 0)
2787     extra_space = 0;
2788   else if (extra_space > 0 && expand_cell_count > 0)
2789     extra_space /= expand_cell_count;
2790
2791   /* iterate list for GTK_PACK_START cells */
2792   for (list = tree_column->cell_list; list; list = list->next)
2793     {
2794       PsppSheetViewColumnCellInfo *info = (PsppSheetViewColumnCellInfo *) list->data;
2795
2796       if (info->pack == GTK_PACK_END)
2797         continue;
2798
2799       if (! info->cell->visible)
2800         continue;
2801
2802       if ((info->has_focus || special_cells == 1) && cursor_row)
2803         flags |= GTK_CELL_RENDERER_FOCUSED;
2804       else
2805         flags &= ~GTK_CELL_RENDERER_FOCUSED;
2806
2807       info->real_width = info->requested_width + (info->expand?extra_space:0);
2808
2809       /* We constrain ourselves to only the width available */
2810       if (real_cell_area.x - focus_line_width + info->real_width > cell_area->x + cell_area->width)
2811         {
2812           info->real_width = cell_area->x + cell_area->width - real_cell_area.x;
2813         }   
2814
2815       if (real_cell_area.x > cell_area->x + cell_area->width)
2816         break;
2817
2818       real_cell_area.width = info->real_width;
2819       real_cell_area.width -= 2 * focus_line_width;
2820
2821       if (list->next)
2822         {
2823           real_background_area.width = info->real_width + depth;
2824         }
2825       else
2826         {
2827           /* fill the rest of background for the last cell */
2828           real_background_area.width = background_area->x + background_area->width - real_background_area.x;
2829         }
2830
2831       rtl_cell_area = real_cell_area;
2832       rtl_background_area = real_background_area;
2833       
2834       if (rtl)
2835         {
2836           rtl_cell_area.x = cell_area->x + cell_area->width - (real_cell_area.x - cell_area->x) - real_cell_area.width;
2837           rtl_background_area.x = background_area->x + background_area->width - (real_background_area.x - background_area->x) - real_background_area.width;
2838         }
2839
2840       /* RENDER */
2841       if (action == CELL_ACTION_RENDER)
2842         {
2843           gtk_cell_renderer_render (info->cell,
2844                                     window,
2845                                     tree_column->tree_view,
2846                                     &rtl_background_area,
2847                                     &rtl_cell_area,
2848                                     &real_expose_area, 
2849                                     flags);
2850         }
2851       /* FOCUS */
2852       else if (action == CELL_ACTION_FOCUS)
2853         {
2854           gint x_offset, y_offset, width, height;
2855
2856           gtk_cell_renderer_get_size (info->cell,
2857                                       tree_column->tree_view,
2858                                       &rtl_cell_area,
2859                                       &x_offset, &y_offset,
2860                                       &width, &height);
2861
2862           if (special_cells > 1)
2863             {
2864               if (info->has_focus)
2865                 {
2866                   min_x = rtl_cell_area.x + x_offset;
2867                   max_x = min_x + width;
2868                   min_y = rtl_cell_area.y + y_offset;
2869                   max_y = min_y + height;
2870                 }
2871             }
2872           else
2873             {
2874               if (min_x > (rtl_cell_area.x + x_offset))
2875                 min_x = rtl_cell_area.x + x_offset;
2876               if (max_x < rtl_cell_area.x + x_offset + width)
2877                 max_x = rtl_cell_area.x + x_offset + width;
2878               if (min_y > (rtl_cell_area.y + y_offset))
2879                 min_y = rtl_cell_area.y + y_offset;
2880               if (max_y < rtl_cell_area.y + y_offset + height)
2881                 max_y = rtl_cell_area.y + y_offset + height;
2882             }
2883         }
2884       /* EVENT */
2885       else if (action == CELL_ACTION_EVENT)
2886         {
2887           gboolean try_event = FALSE;
2888
2889           if (event)
2890             {
2891               if (special_cells == 1)
2892                 {
2893                   /* only 1 activatable cell -> whole column can activate */
2894                   if (cell_area->x <= ((GdkEventButton *)event)->x &&
2895                       cell_area->x + cell_area->width > ((GdkEventButton *)event)->x)
2896                     try_event = TRUE;
2897                 }
2898               else if (rtl_cell_area.x <= ((GdkEventButton *)event)->x &&
2899                   rtl_cell_area.x + rtl_cell_area.width > ((GdkEventButton *)event)->x)
2900                   /* only activate cell if the user clicked on an individual
2901                    * cell
2902                    */
2903                 try_event = TRUE;
2904             }
2905           else if (special_cells > 1 && info->has_focus)
2906             try_event = TRUE;
2907           else if (special_cells == 1)
2908             try_event = TRUE;
2909
2910           if (try_event)
2911             {
2912               gboolean visible, mode;
2913
2914               g_object_get (info->cell,
2915                             "visible", &visible,
2916                             "mode", &mode,
2917                             NULL);
2918               if (visible && mode == GTK_CELL_RENDERER_MODE_ACTIVATABLE)
2919                 {
2920                   if (gtk_cell_renderer_activate (info->cell,
2921                                                   event,
2922                                                   tree_column->tree_view,
2923                                                   path_string,
2924                                                   &rtl_background_area,
2925                                                   &rtl_cell_area,
2926                                                   flags))
2927                     {
2928                       flags &= ~GTK_CELL_RENDERER_FOCUSED;
2929                       return TRUE;
2930                     }
2931                 }
2932               else if (visible && mode == GTK_CELL_RENDERER_MODE_EDITABLE)
2933                 {
2934                   *editable_widget =
2935                     gtk_cell_renderer_start_editing (info->cell,
2936                                                      event,
2937                                                      tree_column->tree_view,
2938                                                      path_string,
2939                                                      &rtl_background_area,
2940                                                      &rtl_cell_area,
2941                                                      flags);
2942
2943                   if (*editable_widget != NULL)
2944                     {
2945                       g_return_val_if_fail (GTK_IS_CELL_EDITABLE (*editable_widget), FALSE);
2946                       info->in_editing_mode = TRUE;
2947                       pspp_sheet_view_column_focus_cell (tree_column, info->cell);
2948                       
2949                       flags &= ~GTK_CELL_RENDERER_FOCUSED;
2950
2951                       return TRUE;
2952                     }
2953                 }
2954             }
2955         }
2956
2957       flags &= ~GTK_CELL_RENDERER_FOCUSED;
2958
2959       real_cell_area.x += (real_cell_area.width + 2 * focus_line_width + tree_column->spacing);
2960       real_background_area.x += real_background_area.width + tree_column->spacing;
2961
2962       /* Only needed for first cell */
2963       depth = 0;
2964     }
2965
2966   /* iterate list for PACK_END cells */
2967   for (list = g_list_last (tree_column->cell_list); list; list = list->prev)
2968     {
2969       PsppSheetViewColumnCellInfo *info = (PsppSheetViewColumnCellInfo *) list->data;
2970
2971       if (info->pack == GTK_PACK_START)
2972         continue;
2973
2974       if (! info->cell->visible)
2975         continue;
2976
2977       if ((info->has_focus || special_cells == 1) && cursor_row)
2978         flags |= GTK_CELL_RENDERER_FOCUSED;
2979       else
2980         flags &= ~GTK_CELL_RENDERER_FOCUSED;
2981
2982       info->real_width = info->requested_width + (info->expand?extra_space:0);
2983
2984       /* We constrain ourselves to only the width available */
2985       if (real_cell_area.x - focus_line_width + info->real_width > cell_area->x + cell_area->width)
2986         {
2987           info->real_width = cell_area->x + cell_area->width - real_cell_area.x;
2988         }   
2989
2990       if (real_cell_area.x > cell_area->x + cell_area->width)
2991         break;
2992
2993       real_cell_area.width = info->real_width;
2994       real_cell_area.width -= 2 * focus_line_width;
2995       real_background_area.width = info->real_width + depth;
2996
2997       rtl_cell_area = real_cell_area;
2998       rtl_background_area = real_background_area;
2999       if (rtl)
3000         {
3001           rtl_cell_area.x = cell_area->x + cell_area->width - (real_cell_area.x - cell_area->x) - real_cell_area.width;
3002           rtl_background_area.x = background_area->x + background_area->width - (real_background_area.x - background_area->x) - real_background_area.width;
3003         }
3004
3005       /* RENDER */
3006       if (action == CELL_ACTION_RENDER)
3007         {
3008           gtk_cell_renderer_render (info->cell,
3009                                     window,
3010                                     tree_column->tree_view,
3011                                     &rtl_background_area,
3012                                     &rtl_cell_area,
3013                                     &real_expose_area,
3014                                     flags);
3015         }
3016       /* FOCUS */
3017       else if (action == CELL_ACTION_FOCUS)
3018         {
3019           gint x_offset, y_offset, width, height;
3020
3021           gtk_cell_renderer_get_size (info->cell,
3022                                       tree_column->tree_view,
3023                                       &rtl_cell_area,
3024                                       &x_offset, &y_offset,
3025                                       &width, &height);
3026
3027           if (special_cells > 1)
3028             {
3029               if (info->has_focus)
3030                 {
3031                   min_x = rtl_cell_area.x + x_offset;
3032                   max_x = min_x + width;
3033                   min_y = rtl_cell_area.y + y_offset;
3034                   max_y = min_y + height;
3035                 }
3036             }
3037           else
3038             {
3039               if (min_x > (rtl_cell_area.x + x_offset))
3040                 min_x = rtl_cell_area.x + x_offset;
3041               if (max_x < rtl_cell_area.x + x_offset + width)
3042                 max_x = rtl_cell_area.x + x_offset + width;
3043               if (min_y > (rtl_cell_area.y + y_offset))
3044                 min_y = rtl_cell_area.y + y_offset;
3045               if (max_y < rtl_cell_area.y + y_offset + height)
3046                 max_y = rtl_cell_area.y + y_offset + height;
3047             }
3048         }
3049       /* EVENT */
3050       else if (action == CELL_ACTION_EVENT)
3051         {
3052           gboolean try_event = FALSE;
3053
3054           if (event)
3055             {
3056               if (special_cells == 1)
3057                 {
3058                   /* only 1 activatable cell -> whole column can activate */
3059                   if (cell_area->x <= ((GdkEventButton *)event)->x &&
3060                       cell_area->x + cell_area->width > ((GdkEventButton *)event)->x)
3061                     try_event = TRUE;
3062                 }
3063               else if (rtl_cell_area.x <= ((GdkEventButton *)event)->x &&
3064                   rtl_cell_area.x + rtl_cell_area.width > ((GdkEventButton *)event)->x)
3065                 /* only activate cell if the user clicked on an individual
3066                  * cell
3067                  */
3068                 try_event = TRUE;
3069             }
3070           else if (special_cells > 1 && info->has_focus)
3071             try_event = TRUE;
3072           else if (special_cells == 1)
3073             try_event = TRUE;
3074
3075           if (try_event)
3076             {
3077               gboolean visible, mode;
3078
3079               g_object_get (info->cell,
3080                             "visible", &visible,
3081                             "mode", &mode,
3082                             NULL);
3083               if (visible && mode == GTK_CELL_RENDERER_MODE_ACTIVATABLE)
3084                 {
3085                   if (gtk_cell_renderer_activate (info->cell,
3086                                                   event,
3087                                                   tree_column->tree_view,
3088                                                   path_string,
3089                                                   &rtl_background_area,
3090                                                   &rtl_cell_area,
3091                                                   flags))
3092                     {
3093                       flags &= ~GTK_CELL_RENDERER_FOCUSED;
3094                       return TRUE;
3095                     }
3096                 }
3097               else if (visible && mode == GTK_CELL_RENDERER_MODE_EDITABLE)
3098                 {
3099                   *editable_widget =
3100                     gtk_cell_renderer_start_editing (info->cell,
3101                                                      event,
3102                                                      tree_column->tree_view,
3103                                                      path_string,
3104                                                      &rtl_background_area,
3105                                                      &rtl_cell_area,
3106                                                      flags);
3107
3108                   if (*editable_widget != NULL)
3109                     {
3110                       g_return_val_if_fail (GTK_IS_CELL_EDITABLE (*editable_widget), FALSE);
3111                       info->in_editing_mode = TRUE;
3112                       pspp_sheet_view_column_focus_cell (tree_column, info->cell);
3113
3114                       flags &= ~GTK_CELL_RENDERER_FOCUSED;
3115                       return TRUE;
3116                     }
3117                 }
3118             }
3119         }
3120
3121       flags &= ~GTK_CELL_RENDERER_FOCUSED;
3122
3123       real_cell_area.x += (real_cell_area.width + 2 * focus_line_width + tree_column->spacing);
3124       real_background_area.x += (real_background_area.width + tree_column->spacing);
3125
3126       /* Only needed for first cell */
3127       depth = 0;
3128     }
3129
3130   /* fill focus_rectangle when required */
3131   if (action == CELL_ACTION_FOCUS)
3132     {
3133       if (min_x >= max_x || min_y >= max_y)
3134         {
3135           *focus_rectangle = *cell_area;
3136           /* don't change the focus_rectangle, just draw it nicely inside
3137            * the cell area */
3138         }
3139       else
3140         {
3141           focus_rectangle->x = min_x - focus_line_width;
3142           focus_rectangle->y = min_y - focus_line_width;
3143           focus_rectangle->width = (max_x - min_x) + 2 * focus_line_width;
3144           focus_rectangle->height = (max_y - min_y) + 2 * focus_line_width;
3145         }
3146     }
3147
3148   return FALSE;
3149 }
3150
3151 /**
3152  * pspp_sheet_view_column_cell_render:
3153  * @tree_column: A #PsppSheetViewColumn.
3154  * @window: a #GdkDrawable to draw to
3155  * @background_area: entire cell area (including tree expanders and maybe padding on the sides)
3156  * @cell_area: area normally rendered by a cell renderer
3157  * @expose_area: area that actually needs updating
3158  * @flags: flags that affect rendering
3159  * 
3160  * Renders the cell contained by #tree_column. This is used primarily by the
3161  * #PsppSheetView.
3162  **/
3163 void
3164 _pspp_sheet_view_column_cell_render (PsppSheetViewColumn  *tree_column,
3165                                    GdkWindow          *window,
3166                                    const GdkRectangle *background_area,
3167                                    const GdkRectangle *cell_area,
3168                                    const GdkRectangle *expose_area,
3169                                    guint               flags)
3170 {
3171   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
3172   g_return_if_fail (background_area != NULL);
3173   g_return_if_fail (cell_area != NULL);
3174   g_return_if_fail (expose_area != NULL);
3175
3176   pspp_sheet_view_column_cell_process_action (tree_column,
3177                                             window,
3178                                             background_area,
3179                                             cell_area,
3180                                             flags,
3181                                             CELL_ACTION_RENDER,
3182                                             expose_area,
3183                                             NULL, NULL, NULL, NULL);
3184 }
3185
3186 gboolean
3187 _pspp_sheet_view_column_cell_event (PsppSheetViewColumn  *tree_column,
3188                                   GtkCellEditable   **editable_widget,
3189                                   GdkEvent           *event,
3190                                   gchar              *path_string,
3191                                   const GdkRectangle *background_area,
3192                                   const GdkRectangle *cell_area,
3193                                   guint               flags)
3194 {
3195   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), FALSE);
3196
3197   return pspp_sheet_view_column_cell_process_action (tree_column,
3198                                                    NULL,
3199                                                    background_area,
3200                                                    cell_area,
3201                                                    flags,
3202                                                    CELL_ACTION_EVENT,
3203                                                    NULL, NULL,
3204                                                    editable_widget,
3205                                                    event,
3206                                                    path_string);
3207 }
3208
3209 void
3210 _pspp_sheet_view_column_get_focus_area (PsppSheetViewColumn  *tree_column,
3211                                       const GdkRectangle *background_area,
3212                                       const GdkRectangle *cell_area,
3213                                       GdkRectangle       *focus_area)
3214 {
3215   pspp_sheet_view_column_cell_process_action (tree_column,
3216                                             NULL,
3217                                             background_area,
3218                                             cell_area,
3219                                             0,
3220                                             CELL_ACTION_FOCUS,
3221                                             NULL,
3222                                             focus_area,
3223                                             NULL, NULL, NULL);
3224 }
3225
3226
3227 /* cell list manipulation */
3228 static GList *
3229 pspp_sheet_view_column_cell_first (PsppSheetViewColumn *tree_column)
3230 {
3231   GList *list = tree_column->cell_list;
3232
3233   /* first GTK_PACK_START cell we find */
3234   for ( ; list; list = list->next)
3235     {
3236       PsppSheetViewColumnCellInfo *info = list->data;
3237       if (info->pack == GTK_PACK_START)
3238         return list;
3239     }
3240
3241   /* hmm, else the *last* GTK_PACK_END cell */
3242   list = g_list_last (tree_column->cell_list);
3243
3244   for ( ; list; list = list->prev)
3245     {
3246       PsppSheetViewColumnCellInfo *info = list->data;
3247       if (info->pack == GTK_PACK_END)
3248         return list;
3249     }
3250
3251   return NULL;
3252 }
3253
3254 static GList *
3255 pspp_sheet_view_column_cell_last (PsppSheetViewColumn *tree_column)
3256 {
3257   GList *list = tree_column->cell_list;
3258
3259   /* *first* GTK_PACK_END cell we find */
3260   for ( ; list ; list = list->next)
3261     {
3262       PsppSheetViewColumnCellInfo *info = list->data;
3263       if (info->pack == GTK_PACK_END)
3264         return list;
3265     }
3266
3267   /* hmm, else the last GTK_PACK_START cell */
3268   list = g_list_last (tree_column->cell_list);
3269
3270   for ( ; list; list = list->prev)
3271     {
3272       PsppSheetViewColumnCellInfo *info = list->data;
3273       if (info->pack == GTK_PACK_START)
3274         return list;
3275     }
3276
3277   return NULL;
3278 }
3279
3280 static GList *
3281 pspp_sheet_view_column_cell_next (PsppSheetViewColumn *tree_column,
3282                                 GList             *current)
3283 {
3284   GList *list;
3285   PsppSheetViewColumnCellInfo *info = current->data;
3286
3287   if (info->pack == GTK_PACK_START)
3288     {
3289       for (list = current->next; list; list = list->next)
3290         {
3291           PsppSheetViewColumnCellInfo *inf = list->data;
3292           if (inf->pack == GTK_PACK_START)
3293             return list;
3294         }
3295
3296       /* out of GTK_PACK_START cells, get *last* GTK_PACK_END one */
3297       list = g_list_last (tree_column->cell_list);
3298       for (; list; list = list->prev)
3299         {
3300           PsppSheetViewColumnCellInfo *inf = list->data;
3301           if (inf->pack == GTK_PACK_END)
3302             return list;
3303         }
3304     }
3305
3306   for (list = current->prev; list; list = list->prev)
3307     {
3308       PsppSheetViewColumnCellInfo *inf = list->data;
3309       if (inf->pack == GTK_PACK_END)
3310         return list;
3311     }
3312
3313   return NULL;
3314 }
3315
3316 static GList *
3317 pspp_sheet_view_column_cell_prev (PsppSheetViewColumn *tree_column,
3318                                 GList             *current)
3319 {
3320   GList *list;
3321   PsppSheetViewColumnCellInfo *info = current->data;
3322
3323   if (info->pack == GTK_PACK_END)
3324     {
3325       for (list = current->next; list; list = list->next)
3326         {
3327           PsppSheetViewColumnCellInfo *inf = list->data;
3328           if (inf->pack == GTK_PACK_END)
3329             return list;
3330         }
3331
3332       /* out of GTK_PACK_END, get last GTK_PACK_START one */
3333       list = g_list_last (tree_column->cell_list);
3334       for ( ; list; list = list->prev)
3335         {
3336           PsppSheetViewColumnCellInfo *inf = list->data;
3337           if (inf->pack == GTK_PACK_START)
3338             return list;
3339         }
3340     }
3341
3342   for (list = current->prev; list; list = list->prev)
3343     {
3344       PsppSheetViewColumnCellInfo *inf = list->data;
3345       if (inf->pack == GTK_PACK_START)
3346         return list;
3347     }
3348
3349   return NULL;
3350 }
3351
3352 gboolean
3353 _pspp_sheet_view_column_cell_focus (PsppSheetViewColumn *tree_column,
3354                                   gint               direction,
3355                                   gboolean           left,
3356                                   gboolean           right)
3357 {
3358   gint count;
3359   gboolean rtl;
3360
3361   count = _pspp_sheet_view_column_count_special_cells (tree_column);
3362   rtl = gtk_widget_get_direction (GTK_WIDGET (tree_column->tree_view)) == GTK_TEXT_DIR_RTL;
3363
3364   /* if we are the current focus column and have multiple editable cells,
3365    * try to select the next one, else move the focus to the next column
3366    */
3367   if (PSPP_SHEET_VIEW (tree_column->tree_view)->priv->focus_column == tree_column)
3368     {
3369       if (count > 1)
3370         {
3371           GList *next, *prev;
3372           GList *list = tree_column->cell_list;
3373           PsppSheetViewColumnCellInfo *info = NULL;
3374
3375           /* find current focussed cell */
3376           for ( ; list; list = list->next)
3377             {
3378               info = list->data;
3379               if (info->has_focus)
3380                 break;
3381             }
3382
3383           /* not a focussed cell in the focus column? */
3384           if (!list || !info || !info->has_focus)
3385             return FALSE;
3386
3387           if (rtl)
3388             {
3389               prev = pspp_sheet_view_column_cell_next (tree_column, list);
3390               next = pspp_sheet_view_column_cell_prev (tree_column, list);
3391             }
3392           else
3393             {
3394               next = pspp_sheet_view_column_cell_next (tree_column, list);
3395               prev = pspp_sheet_view_column_cell_prev (tree_column, list);
3396             }
3397
3398           info->has_focus = FALSE;
3399           if (direction > 0 && next)
3400             {
3401               info = next->data;
3402               info->has_focus = TRUE;
3403               return TRUE;
3404             }
3405           else if (direction > 0 && !next && !right)
3406             {
3407               /* keep focus on last cell */
3408               if (rtl)
3409                 info = pspp_sheet_view_column_cell_first (tree_column)->data;
3410               else
3411                 info = pspp_sheet_view_column_cell_last (tree_column)->data;
3412
3413               info->has_focus = TRUE;
3414               return TRUE;
3415             }
3416           else if (direction < 0 && prev)
3417             {
3418               info = prev->data;
3419               info->has_focus = TRUE;
3420               return TRUE;
3421             }
3422           else if (direction < 0 && !prev && !left)
3423             {
3424               /* keep focus on first cell */
3425               if (rtl)
3426                 info = pspp_sheet_view_column_cell_last (tree_column)->data;
3427               else
3428                 info = pspp_sheet_view_column_cell_first (tree_column)->data;
3429
3430               info->has_focus = TRUE;
3431               return TRUE;
3432             }
3433         }
3434       return FALSE;
3435     }
3436
3437   /* we get focus, if we have multiple editable cells, give the correct one
3438    * focus
3439    */
3440   if (count > 1)
3441     {
3442       GList *list = tree_column->cell_list;
3443
3444       /* clear focus first */
3445       for ( ; list ; list = list->next)
3446         {
3447           PsppSheetViewColumnCellInfo *info = list->data;
3448           if (info->has_focus)
3449             info->has_focus = FALSE;
3450         }
3451
3452       list = NULL;
3453       if (rtl)
3454         {
3455           if (direction > 0)
3456             list = pspp_sheet_view_column_cell_last (tree_column);
3457           else if (direction < 0)
3458             list = pspp_sheet_view_column_cell_first (tree_column);
3459         }
3460       else
3461         {
3462           if (direction > 0)
3463             list = pspp_sheet_view_column_cell_first (tree_column);
3464           else if (direction < 0)
3465             list = pspp_sheet_view_column_cell_last (tree_column);
3466         }
3467
3468       if (list)
3469         ((PsppSheetViewColumnCellInfo *) list->data)->has_focus = TRUE;
3470     }
3471
3472   return TRUE;
3473 }
3474
3475 void
3476 _pspp_sheet_view_column_cell_draw_focus (PsppSheetViewColumn  *tree_column,
3477                                        GdkWindow          *window,
3478                                        const GdkRectangle *background_area,
3479                                        const GdkRectangle *cell_area,
3480                                        const GdkRectangle *expose_area,
3481                                        guint               flags)
3482 {
3483   gint focus_line_width;
3484   GtkStateType cell_state;
3485   
3486   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
3487   gtk_widget_style_get (GTK_WIDGET (tree_column->tree_view),
3488                         "focus-line-width", &focus_line_width, NULL);
3489   if (tree_column->editable_widget)
3490     {
3491       /* This function is only called on the editable row when editing.
3492        */
3493 #if 0
3494       gtk_paint_focus (tree_column->tree_view->style,
3495                        window,
3496                        gtk_widget_get_state (tree_column->tree_view),
3497                        NULL,
3498                        tree_column->tree_view,
3499                        "treeview",
3500                        cell_area->x - focus_line_width,
3501                        cell_area->y - focus_line_width,
3502                        cell_area->width + 2 * focus_line_width,
3503                        cell_area->height + 2 * focus_line_width);
3504 #endif      
3505     }
3506   else
3507     {
3508       GdkRectangle focus_rectangle;
3509       pspp_sheet_view_column_cell_process_action (tree_column,
3510                                                 window,
3511                                                 background_area,
3512                                                 cell_area,
3513                                                 flags,
3514                                                 CELL_ACTION_FOCUS,
3515                                                 expose_area,
3516                                                 &focus_rectangle,
3517                                                 NULL, NULL, NULL);
3518
3519       cell_state = flags & GTK_CELL_RENDERER_SELECTED ? GTK_STATE_SELECTED :
3520               (flags & GTK_CELL_RENDERER_PRELIT ? GTK_STATE_PRELIGHT :
3521               (flags & GTK_CELL_RENDERER_INSENSITIVE ? GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL));
3522       gtk_paint_focus (tree_column->tree_view->style,
3523                        window,
3524                        cell_state,
3525                        cell_area,
3526                        tree_column->tree_view,
3527                        "treeview",
3528                        focus_rectangle.x,
3529                        focus_rectangle.y,
3530                        focus_rectangle.width,
3531                        focus_rectangle.height);
3532     }
3533 }
3534
3535 /**
3536  * pspp_sheet_view_column_cell_is_visible:
3537  * @tree_column: A #PsppSheetViewColumn
3538  * 
3539  * Returns %TRUE if any of the cells packed into the @tree_column are visible.
3540  * For this to be meaningful, you must first initialize the cells with
3541  * pspp_sheet_view_column_cell_set_cell_data()
3542  * 
3543  * Return value: %TRUE, if any of the cells packed into the @tree_column are currently visible
3544  **/
3545 gboolean
3546 pspp_sheet_view_column_cell_is_visible (PsppSheetViewColumn *tree_column)
3547 {
3548   GList *list;
3549
3550   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), FALSE);
3551
3552   for (list = tree_column->cell_list; list; list = list->next)
3553     {
3554       PsppSheetViewColumnCellInfo *info = (PsppSheetViewColumnCellInfo *) list->data;
3555
3556       if (info->cell->visible)
3557         return TRUE;
3558     }
3559
3560   return FALSE;
3561 }
3562
3563 /**
3564  * pspp_sheet_view_column_focus_cell:
3565  * @tree_column: A #PsppSheetViewColumn
3566  * @cell: A #GtkCellRenderer
3567  *
3568  * Sets the current keyboard focus to be at @cell, if the column contains
3569  * 2 or more editable and activatable cells.
3570  *
3571  * Since: 2.2
3572  **/
3573 void
3574 pspp_sheet_view_column_focus_cell (PsppSheetViewColumn *tree_column,
3575                                  GtkCellRenderer   *cell)
3576 {
3577   GList *list;
3578   gboolean found_cell = FALSE;
3579
3580   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
3581   g_return_if_fail (GTK_IS_CELL_RENDERER (cell));
3582
3583   if (_pspp_sheet_view_column_count_special_cells (tree_column) < 2)
3584     return;
3585
3586   for (list = tree_column->cell_list; list; list = list->next)
3587     {
3588       PsppSheetViewColumnCellInfo *info = list->data;
3589
3590       if (info->cell == cell)
3591         {
3592           info->has_focus = TRUE;
3593           found_cell = TRUE;
3594           break;
3595         }
3596     }
3597
3598   if (found_cell)
3599     {
3600       for (list = tree_column->cell_list; list; list = list->next)
3601         {
3602           PsppSheetViewColumnCellInfo *info = list->data;
3603
3604           if (info->cell != cell)
3605             info->has_focus = FALSE;
3606         }
3607
3608       /* FIXME: redraw? */
3609     }
3610 }
3611
3612 void
3613 _pspp_sheet_view_column_cell_set_dirty (PsppSheetViewColumn *tree_column)
3614 {
3615   GList *list;
3616
3617   for (list = tree_column->cell_list; list; list = list->next)
3618     {
3619       PsppSheetViewColumnCellInfo *info = (PsppSheetViewColumnCellInfo *) list->data;
3620
3621       info->requested_width = 0;
3622     }
3623   tree_column->dirty = TRUE;
3624   tree_column->requested_width = -1;
3625   tree_column->width = 0;
3626
3627   if (tree_column->tree_view &&
3628       gtk_widget_get_realized (tree_column->tree_view))
3629     {
3630       _pspp_sheet_view_install_mark_rows_col_dirty (PSPP_SHEET_VIEW (tree_column->tree_view));
3631       gtk_widget_queue_resize (tree_column->tree_view);
3632     }
3633 }
3634
3635 void
3636 _pspp_sheet_view_column_start_editing (PsppSheetViewColumn *tree_column,
3637                                      GtkCellEditable   *cell_editable)
3638 {
3639   g_return_if_fail (tree_column->editable_widget == NULL);
3640
3641   tree_column->editable_widget = cell_editable;
3642 }
3643
3644 void
3645 _pspp_sheet_view_column_stop_editing (PsppSheetViewColumn *tree_column)
3646 {
3647   GList *list;
3648
3649   g_return_if_fail (tree_column->editable_widget != NULL);
3650
3651   tree_column->editable_widget = NULL;
3652   for (list = tree_column->cell_list; list; list = list->next)
3653     ((PsppSheetViewColumnCellInfo *)list->data)->in_editing_mode = FALSE;
3654 }
3655
3656 void
3657 _pspp_sheet_view_column_get_neighbor_sizes (PsppSheetViewColumn *column,
3658                                           GtkCellRenderer   *cell,
3659                                           gint              *left,
3660                                           gint              *right)
3661 {
3662   GList *list;
3663   PsppSheetViewColumnCellInfo *info;
3664   gint l, r;
3665   gboolean rtl;
3666
3667   l = r = 0;
3668
3669   list = pspp_sheet_view_column_cell_first (column);  
3670
3671   while (list)
3672     {
3673       info = (PsppSheetViewColumnCellInfo *)list->data;
3674       
3675       list = pspp_sheet_view_column_cell_next (column, list);
3676
3677       if (info->cell == cell)
3678         break;
3679       
3680       if (info->cell->visible)
3681         l += info->real_width + column->spacing;
3682     }
3683
3684   while (list)
3685     {
3686       info = (PsppSheetViewColumnCellInfo *)list->data;
3687       
3688       list = pspp_sheet_view_column_cell_next (column, list);
3689
3690       if (info->cell->visible)
3691         r += info->real_width + column->spacing;
3692     }
3693
3694   rtl = (gtk_widget_get_direction (GTK_WIDGET (column->tree_view)) == GTK_TEXT_DIR_RTL);
3695   if (left)
3696     *left = rtl ? r : l;
3697
3698   if (right)
3699     *right = rtl ? l : r;
3700 }
3701
3702 /**
3703  * pspp_sheet_view_column_cell_get_position:
3704  * @tree_column: a #PsppSheetViewColumn
3705  * @cell_renderer: a #GtkCellRenderer
3706  * @start_pos: return location for the horizontal position of @cell within
3707  *            @tree_column, may be %NULL
3708  * @width: return location for the width of @cell, may be %NULL
3709  *
3710  * Obtains the horizontal position and size of a cell in a column. If the
3711  * cell is not found in the column, @start_pos and @width are not changed and
3712  * %FALSE is returned.
3713  * 
3714  * Return value: %TRUE if @cell belongs to @tree_column.
3715  */
3716 gboolean
3717 pspp_sheet_view_column_cell_get_position (PsppSheetViewColumn *tree_column,
3718                                         GtkCellRenderer   *cell_renderer,
3719                                         gint              *start_pos,
3720                                         gint              *width)
3721 {
3722   GList *list;
3723   gint current_x = 0;
3724   gboolean found_cell = FALSE;
3725   PsppSheetViewColumnCellInfo *cellinfo = NULL;
3726
3727   list = pspp_sheet_view_column_cell_first (tree_column);
3728   for (; list; list = pspp_sheet_view_column_cell_next (tree_column, list))
3729     {
3730       cellinfo = list->data;
3731       if (cellinfo->cell == cell_renderer)
3732         {
3733           found_cell = TRUE;
3734           break;
3735         }
3736
3737       if (cellinfo->cell->visible)
3738         current_x += cellinfo->real_width;
3739     }
3740
3741   if (found_cell)
3742     {
3743       if (start_pos)
3744         *start_pos = current_x;
3745       if (width)
3746         *width = cellinfo->real_width;
3747     }
3748
3749   return found_cell;
3750 }
3751
3752 /**
3753  * pspp_sheet_view_column_queue_resize:
3754  * @tree_column: A #PsppSheetViewColumn
3755  *
3756  * Flags the column, and the cell renderers added to this column, to have
3757  * their sizes renegotiated.
3758  *
3759  * Since: 2.8
3760  **/
3761 void
3762 pspp_sheet_view_column_queue_resize (PsppSheetViewColumn *tree_column)
3763 {
3764   g_return_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column));
3765
3766   if (tree_column->tree_view)
3767     _pspp_sheet_view_column_cell_set_dirty (tree_column);
3768 }
3769
3770 /**
3771  * pspp_sheet_view_column_get_tree_view:
3772  * @tree_column: A #PsppSheetViewColumn
3773  *
3774  * Returns the #PsppSheetView wherein @tree_column has been inserted.  If
3775  * @column is currently not inserted in any tree view, %NULL is
3776  * returned.
3777  *
3778  * Return value: The tree view wherein @column has been inserted if any,
3779  *               %NULL otherwise.
3780  *
3781  * Since: 2.12
3782  */
3783 GtkWidget *
3784 pspp_sheet_view_column_get_tree_view (PsppSheetViewColumn *tree_column)
3785 {
3786   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (tree_column), NULL);
3787
3788   return tree_column->tree_view;
3789 }
3790
3791 typedef struct {
3792   GtkCellLayout   *cell_layout;
3793   GtkCellRenderer *renderer;
3794   gchar           *attr_name;
3795 } AttributesSubParserData;
3796
3797 static void
3798 attributes_start_element (GMarkupParseContext *context,
3799                           const gchar         *element_name,
3800                           const gchar        **names,
3801                           const gchar        **values,
3802                           gpointer             user_data,
3803                           GError             **error)
3804 {
3805   AttributesSubParserData *parser_data = (AttributesSubParserData*)user_data;
3806   guint i;
3807
3808   if (strcmp (element_name, "attribute") == 0)
3809     {
3810       for (i = 0; names[i]; i++)
3811         if (strcmp (names[i], "name") == 0)
3812           parser_data->attr_name = g_strdup (values[i]);
3813     }
3814   else if (strcmp (element_name, "attributes") == 0)
3815     return;
3816   else
3817     g_warning ("Unsupported tag for GtkCellLayout: %s\n", element_name);
3818 }
3819
3820 static void
3821 attributes_text_element (GMarkupParseContext *context,
3822                          const gchar         *text,
3823                          gsize                text_len,
3824                          gpointer             user_data,
3825                          GError             **error)
3826 {
3827   AttributesSubParserData *parser_data = (AttributesSubParserData*)user_data;
3828   glong l;
3829   gchar *endptr;
3830   gchar *string;
3831   
3832   if (!parser_data->attr_name)
3833     return;
3834
3835   errno = 0;
3836   string = g_strndup (text, text_len);
3837   l = strtol (string, &endptr, 0);
3838   if (errno || endptr == string)
3839     {
3840       g_set_error (error, 
3841                    GTK_BUILDER_ERROR,
3842                    GTK_BUILDER_ERROR_INVALID_VALUE,
3843                    "Could not parse integer `%s'",
3844                    string);
3845       g_free (string);
3846       return;
3847     }
3848   g_free (string);
3849
3850   gtk_cell_layout_add_attribute (parser_data->cell_layout,
3851                                  parser_data->renderer,
3852                                  parser_data->attr_name, l);
3853   g_free (parser_data->attr_name);
3854   parser_data->attr_name = NULL;
3855 }
3856
3857 static const GMarkupParser attributes_parser =
3858   {
3859     attributes_start_element,
3860     NULL,
3861     attributes_text_element,
3862   };
3863
3864 gboolean
3865 _gtk_cell_layout_buildable_custom_tag_start (GtkBuildable  *buildable,
3866                                              GtkBuilder    *builder,
3867                                              GObject       *child,
3868                                              const gchar   *tagname,
3869                                              GMarkupParser *parser,
3870                                              gpointer      *data)
3871 {
3872   AttributesSubParserData *parser_data;
3873
3874   if (!child)
3875     return FALSE;
3876
3877   if (strcmp (tagname, "attributes") == 0)
3878     {
3879       parser_data = g_slice_new0 (AttributesSubParserData);
3880       parser_data->cell_layout = GTK_CELL_LAYOUT (buildable);
3881       parser_data->renderer = GTK_CELL_RENDERER (child);
3882       parser_data->attr_name = NULL;
3883
3884       *parser = attributes_parser;
3885       *data = parser_data;
3886       return TRUE;
3887     }
3888
3889   return FALSE;
3890 }
3891
3892 void
3893 _gtk_cell_layout_buildable_custom_tag_end (GtkBuildable *buildable,
3894                                            GtkBuilder   *builder,
3895                                            GObject      *child,
3896                                            const gchar  *tagname,
3897                                            gpointer     *data)
3898 {
3899   AttributesSubParserData *parser_data;
3900
3901   parser_data = (AttributesSubParserData*)data;
3902   g_assert (!parser_data->attr_name);
3903   g_slice_free (AttributesSubParserData, parser_data);
3904 }
3905
3906 void
3907 _gtk_cell_layout_buildable_add_child (GtkBuildable      *buildable,
3908                                       GtkBuilder        *builder,
3909                                       GObject           *child,
3910                                       const gchar       *type)
3911 {
3912   GtkCellLayoutIface *iface;
3913   
3914   g_return_if_fail (GTK_IS_CELL_LAYOUT (buildable));
3915   g_return_if_fail (GTK_IS_CELL_RENDERER (child));
3916
3917   iface = GTK_CELL_LAYOUT_GET_IFACE (buildable);
3918   g_return_if_fail (iface->pack_start != NULL);
3919   iface->pack_start (GTK_CELL_LAYOUT (buildable), GTK_CELL_RENDERER (child), FALSE);
3920 }