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