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