b836e3655354ec092e95f348c6117a5723592938
[pspp] / src / ui / gui / pspp-sheet-view.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 /* gtktreeview.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 <gtk/gtk.h>
41 #include <gdk/gdk.h>
42 #include <gdk/gdkkeysyms.h>
43 #include <string.h>
44
45 #include "ui/gui/psppire-marshal.h"
46 #include "ui/gui/pspp-sheet-selection.h"
47
48 #define P_(STRING) STRING
49 #define GTK_PARAM_READABLE G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
50 #define GTK_PARAM_READWRITE G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
51
52 /* Many keyboard shortcuts for Mac are the same as for X
53  * except they use Command key instead of Control (e.g. Cut,
54  * Copy, Paste). This symbol is for those simple cases. */
55 #ifndef GDK_WINDOWING_QUARTZ
56 #define GTK_DEFAULT_ACCEL_MOD_MASK GDK_CONTROL_MASK
57 #else
58 #define GTK_DEFAULT_ACCEL_MOD_MASK GDK_META_MASK
59 #endif
60
61 #define PSPP_SHEET_VIEW_PRIORITY_VALIDATE (GDK_PRIORITY_REDRAW + 5)
62 #define PSPP_SHEET_VIEW_PRIORITY_SCROLL_SYNC (PSPP_SHEET_VIEW_PRIORITY_VALIDATE + 2)
63 #define PSPP_SHEET_VIEW_TIME_MS_PER_IDLE 30
64 #define SCROLL_EDGE_SIZE 15
65 #define EXPANDER_EXTRA_PADDING 4
66 #define PSPP_SHEET_VIEW_SEARCH_DIALOG_TIMEOUT 5000
67
68 /* The "background" areas of all rows/cells add up to cover the entire tree.
69  * The background includes all inter-row and inter-cell spacing.
70  * The "cell" areas are the cell_area passed in to gtk_cell_renderer_render(),
71  * i.e. just the cells, no spacing.
72  */
73
74 #define BACKGROUND_HEIGHT(tree_view) (tree_view->priv->fixed_height)
75 #define CELL_HEIGHT(tree_view, separator) ((BACKGROUND_HEIGHT (tree_view)) - (separator))
76
77 /* Translate from bin_window coordinates to rbtree (tree coordinates) and
78  * vice versa.
79  */
80 #define TREE_WINDOW_Y_TO_RBTREE_Y(tree_view,y) ((y) + tree_view->priv->dy)
81 #define RBTREE_Y_TO_TREE_WINDOW_Y(tree_view,y) ((y) - tree_view->priv->dy)
82
83 /* This is in bin_window coordinates */
84 #define BACKGROUND_FIRST_PIXEL(tree_view,node) (RBTREE_Y_TO_TREE_WINDOW_Y (tree_view, pspp_sheet_view_node_find_offset (tree_view, (node))))
85 #define CELL_FIRST_PIXEL(tree_view,node,separator) (BACKGROUND_FIRST_PIXEL (tree_view,node) + separator/2)
86
87 #define ROW_HEIGHT(tree_view) \
88   ((tree_view->priv->fixed_height > 0) ? (tree_view->priv->fixed_height) : (tree_view)->priv->expander_size)
89
90
91 typedef struct _PsppSheetViewChild PsppSheetViewChild;
92 struct _PsppSheetViewChild
93 {
94   GtkWidget *widget;
95   gint x;
96   gint y;
97   gint width;
98   gint height;
99 };
100
101
102 typedef struct _TreeViewDragInfo TreeViewDragInfo;
103 struct _TreeViewDragInfo
104 {
105   GdkModifierType start_button_mask;
106   GtkTargetList *_unused_source_target_list;
107   GdkDragAction source_actions;
108
109   GtkTargetList *_unused_dest_target_list;
110
111   guint source_set : 1;
112   guint dest_set : 1;
113 };
114
115
116 /* Signals */
117 enum
118 {
119   ROW_ACTIVATED,
120   COLUMNS_CHANGED,
121   CURSOR_CHANGED,
122   MOVE_CURSOR,
123   SELECT_ALL,
124   UNSELECT_ALL,
125   SELECT_CURSOR_ROW,
126   TOGGLE_CURSOR_ROW,
127   START_INTERACTIVE_SEARCH,
128   LAST_SIGNAL
129 };
130
131 /* Properties */
132 enum {
133   PROP_0,
134   PROP_MODEL,
135   PROP_HADJUSTMENT,
136   PROP_VADJUSTMENT,
137   PROP_HEADERS_VISIBLE,
138   PROP_HEADERS_CLICKABLE,
139   PROP_REORDERABLE,
140   PROP_RULES_HINT,
141   PROP_ENABLE_SEARCH,
142   PROP_SEARCH_COLUMN,
143   PROP_HOVER_SELECTION,
144   PROP_RUBBER_BANDING,
145   PROP_ENABLE_GRID_LINES,
146   PROP_TOOLTIP_COLUMN
147 };
148
149 /* object signals */
150 static void     pspp_sheet_view_finalize             (GObject          *object);
151 static void     pspp_sheet_view_set_property         (GObject         *object,
152                                                     guint            prop_id,
153                                                     const GValue    *value,
154                                                     GParamSpec      *pspec);
155 static void     pspp_sheet_view_get_property         (GObject         *object,
156                                                     guint            prop_id,
157                                                     GValue          *value,
158                                                     GParamSpec      *pspec);
159
160 /* gtkobject signals */
161 static void     pspp_sheet_view_destroy              (GtkObject        *object);
162
163 /* gtkwidget signals */
164 static void     pspp_sheet_view_realize              (GtkWidget        *widget);
165 static void     pspp_sheet_view_unrealize            (GtkWidget        *widget);
166 static void     pspp_sheet_view_map                  (GtkWidget        *widget);
167 static void     pspp_sheet_view_size_request         (GtkWidget        *widget,
168                                                     GtkRequisition   *requisition);
169 static void     pspp_sheet_view_size_allocate        (GtkWidget        *widget,
170                                                     GtkAllocation    *allocation);
171 static gboolean pspp_sheet_view_expose               (GtkWidget        *widget,
172                                                     GdkEventExpose   *event);
173 static gboolean pspp_sheet_view_key_press            (GtkWidget        *widget,
174                                                     GdkEventKey      *event);
175 static gboolean pspp_sheet_view_key_release          (GtkWidget        *widget,
176                                                     GdkEventKey      *event);
177 static gboolean pspp_sheet_view_motion               (GtkWidget        *widget,
178                                                     GdkEventMotion   *event);
179 static gboolean pspp_sheet_view_enter_notify         (GtkWidget        *widget,
180                                                     GdkEventCrossing *event);
181 static gboolean pspp_sheet_view_leave_notify         (GtkWidget        *widget,
182                                                     GdkEventCrossing *event);
183 static gboolean pspp_sheet_view_button_press         (GtkWidget        *widget,
184                                                     GdkEventButton   *event);
185 static gboolean pspp_sheet_view_button_release       (GtkWidget        *widget,
186                                                     GdkEventButton   *event);
187 static gboolean pspp_sheet_view_grab_broken          (GtkWidget          *widget,
188                                                     GdkEventGrabBroken *event);
189 #if 0
190 static gboolean pspp_sheet_view_configure            (GtkWidget         *widget,
191                                                     GdkEventConfigure *event);
192 #endif
193
194 static void     pspp_sheet_view_set_focus_child      (GtkContainer     *container,
195                                                     GtkWidget        *child);
196 static gint     pspp_sheet_view_focus_out            (GtkWidget        *widget,
197                                                     GdkEventFocus    *event);
198 static gint     pspp_sheet_view_focus                (GtkWidget        *widget,
199                                                     GtkDirectionType  direction);
200 static void     pspp_sheet_view_grab_focus           (GtkWidget        *widget);
201 static void     pspp_sheet_view_style_set            (GtkWidget        *widget,
202                                                     GtkStyle         *previous_style);
203 static void     pspp_sheet_view_grab_notify          (GtkWidget        *widget,
204                                                     gboolean          was_grabbed);
205 static void     pspp_sheet_view_state_changed        (GtkWidget        *widget,
206                                                     GtkStateType      previous_state);
207
208 /* container signals */
209 static void     pspp_sheet_view_remove               (GtkContainer     *container,
210                                                     GtkWidget        *widget);
211 static void     pspp_sheet_view_forall               (GtkContainer     *container,
212                                                     gboolean          include_internals,
213                                                     GtkCallback       callback,
214                                                     gpointer          callback_data);
215
216 /* Source side drag signals */
217 static void pspp_sheet_view_drag_begin       (GtkWidget        *widget,
218                                             GdkDragContext   *context);
219 static void pspp_sheet_view_drag_end         (GtkWidget        *widget,
220                                             GdkDragContext   *context);
221 static void pspp_sheet_view_drag_data_get    (GtkWidget        *widget,
222                                             GdkDragContext   *context,
223                                             GtkSelectionData *selection_data,
224                                             guint             info,
225                                             guint             time);
226 static void pspp_sheet_view_drag_data_delete (GtkWidget        *widget,
227                                             GdkDragContext   *context);
228
229 /* Target side drag signals */
230 static void     pspp_sheet_view_drag_leave         (GtkWidget        *widget,
231                                                   GdkDragContext   *context,
232                                                   guint             time);
233 static gboolean pspp_sheet_view_drag_motion        (GtkWidget        *widget,
234                                                   GdkDragContext   *context,
235                                                   gint              x,
236                                                   gint              y,
237                                                   guint             time);
238 static gboolean pspp_sheet_view_drag_drop          (GtkWidget        *widget,
239                                                   GdkDragContext   *context,
240                                                   gint              x,
241                                                   gint              y,
242                                                   guint             time);
243 static void     pspp_sheet_view_drag_data_received (GtkWidget        *widget,
244                                                   GdkDragContext   *context,
245                                                   gint              x,
246                                                   gint              y,
247                                                   GtkSelectionData *selection_data,
248                                                   guint             info,
249                                                   guint             time);
250
251 /* tree_model signals */
252 static void pspp_sheet_view_set_adjustments                 (PsppSheetView     *tree_view,
253                                                            GtkAdjustment   *hadj,
254                                                            GtkAdjustment   *vadj);
255 static gboolean pspp_sheet_view_real_move_cursor            (PsppSheetView     *tree_view,
256                                                            GtkMovementStep  step,
257                                                            gint             count);
258 static gboolean pspp_sheet_view_real_select_all             (PsppSheetView     *tree_view);
259 static gboolean pspp_sheet_view_real_unselect_all           (PsppSheetView     *tree_view);
260 static gboolean pspp_sheet_view_real_select_cursor_row      (PsppSheetView     *tree_view,
261                                                            gboolean         start_editing);
262 static gboolean pspp_sheet_view_real_toggle_cursor_row      (PsppSheetView     *tree_view);
263 static void pspp_sheet_view_row_changed                     (GtkTreeModel    *model,
264                                                            GtkTreePath     *path,
265                                                            GtkTreeIter     *iter,
266                                                            gpointer         data);
267 static void pspp_sheet_view_row_inserted                    (GtkTreeModel    *model,
268                                                            GtkTreePath     *path,
269                                                            GtkTreeIter     *iter,
270                                                            gpointer         data);
271 static void pspp_sheet_view_row_deleted                     (GtkTreeModel    *model,
272                                                            GtkTreePath     *path,
273                                                            gpointer         data);
274 static void pspp_sheet_view_rows_reordered                  (GtkTreeModel    *model,
275                                                            GtkTreePath     *parent,
276                                                            GtkTreeIter     *iter,
277                                                            gint            *new_order,
278                                                            gpointer         data);
279
280 /* Incremental reflow */
281 static gint validate_row             (PsppSheetView *tree_view,
282                                           int node,
283                                           GtkTreeIter *iter,
284                                           GtkTreePath *path);
285 static void     validate_visible_area    (PsppSheetView *tree_view);
286 static gboolean validate_rows_handler    (PsppSheetView *tree_view);
287 static gboolean presize_handler_callback (gpointer     data);
288 static void     install_presize_handler  (PsppSheetView *tree_view);
289 static void     install_scroll_sync_handler (PsppSheetView *tree_view);
290 static void     pspp_sheet_view_set_top_row   (PsppSheetView *tree_view,
291                                              GtkTreePath *path,
292                                              gint         offset);
293 static void     pspp_sheet_view_dy_to_top_row (PsppSheetView *tree_view);
294 static void     pspp_sheet_view_top_row_to_dy (PsppSheetView *tree_view);
295 static void     invalidate_empty_focus      (PsppSheetView *tree_view);
296
297 /* Internal functions */
298 static void     pspp_sheet_view_add_move_binding               (GtkBindingSet      *binding_set,
299                                                               guint               keyval,
300                                                               guint               modmask,
301                                                               gboolean            add_shifted_binding,
302                                                               GtkMovementStep     step,
303                                                               gint                count);
304 static void     pspp_sheet_view_queue_draw_path                (PsppSheetView        *tree_view,
305                                                               GtkTreePath        *path,
306                                                               const GdkRectangle *clip_rect);
307 static gint     pspp_sheet_view_new_column_width               (PsppSheetView        *tree_view,
308                                                               gint                i,
309                                                               gint               *x);
310 static void     pspp_sheet_view_adjustment_changed             (GtkAdjustment      *adjustment,
311                                                               PsppSheetView        *tree_view);
312 static void     pspp_sheet_view_clamp_node_visible             (PsppSheetView        *tree_view,
313                                                               int node);
314 static void     pspp_sheet_view_clamp_column_visible           (PsppSheetView        *tree_view,
315                                                               PsppSheetViewColumn  *column,
316                                                               gboolean            focus_to_cell);
317 static gboolean pspp_sheet_view_maybe_begin_dragging_row       (PsppSheetView        *tree_view,
318                                                               GdkEventMotion     *event);
319 static void     pspp_sheet_view_focus_to_cursor                (PsppSheetView        *tree_view);
320 static void     pspp_sheet_view_move_cursor_up_down            (PsppSheetView        *tree_view,
321                                                               gint                count);
322 static void     pspp_sheet_view_move_cursor_page_up_down       (PsppSheetView        *tree_view,
323                                                               gint                count);
324 static void     pspp_sheet_view_move_cursor_left_right         (PsppSheetView        *tree_view,
325                                                               gint                count);
326 static void     pspp_sheet_view_move_cursor_start_end          (PsppSheetView        *tree_view,
327                                                               gint                count);
328 static void     pspp_sheet_view_real_set_cursor                (PsppSheetView        *tree_view,
329                                                               GtkTreePath        *path,
330                                                               gboolean            clear_and_select,
331                                                               gboolean            clamp_node);
332 static gboolean pspp_sheet_view_has_special_cell               (PsppSheetView        *tree_view);
333 static void     pspp_sheet_view_stop_rubber_band               (PsppSheetView        *tree_view);
334 static void     update_prelight                              (PsppSheetView        *tree_view,
335                                                               int                 x,
336                                                               int                 y);
337 static void initialize_fixed_height_mode (PsppSheetView *tree_view);
338
339 /* interactive search */
340 static void     pspp_sheet_view_ensure_interactive_directory (PsppSheetView *tree_view);
341 static void     pspp_sheet_view_search_dialog_hide     (GtkWidget        *search_dialog,
342                                                          PsppSheetView      *tree_view);
343 static void     pspp_sheet_view_search_position_func      (PsppSheetView      *tree_view,
344                                                          GtkWidget        *search_dialog,
345                                                          gpointer          user_data);
346 static void     pspp_sheet_view_search_disable_popdown    (GtkEntry         *entry,
347                                                          GtkMenu          *menu,
348                                                          gpointer          data);
349 static void     pspp_sheet_view_search_preedit_changed    (GtkIMContext     *im_context,
350                                                          PsppSheetView      *tree_view);
351 static void     pspp_sheet_view_search_activate           (GtkEntry         *entry,
352                                                          PsppSheetView      *tree_view);
353 static gboolean pspp_sheet_view_real_search_enable_popdown(gpointer          data);
354 static void     pspp_sheet_view_search_enable_popdown     (GtkWidget        *widget,
355                                                          gpointer          data);
356 static gboolean pspp_sheet_view_search_delete_event       (GtkWidget        *widget,
357                                                          GdkEventAny      *event,
358                                                          PsppSheetView      *tree_view);
359 static gboolean pspp_sheet_view_search_button_press_event (GtkWidget        *widget,
360                                                          GdkEventButton   *event,
361                                                          PsppSheetView      *tree_view);
362 static gboolean pspp_sheet_view_search_scroll_event       (GtkWidget        *entry,
363                                                          GdkEventScroll   *event,
364                                                          PsppSheetView      *tree_view);
365 static gboolean pspp_sheet_view_search_key_press_event    (GtkWidget        *entry,
366                                                          GdkEventKey      *event,
367                                                          PsppSheetView      *tree_view);
368 static gboolean pspp_sheet_view_search_move               (GtkWidget        *window,
369                                                          PsppSheetView      *tree_view,
370                                                          gboolean          up);
371 static gboolean pspp_sheet_view_search_equal_func         (GtkTreeModel     *model,
372                                                          gint              column,
373                                                          const gchar      *key,
374                                                          GtkTreeIter      *iter,
375                                                          gpointer          search_data);
376 static gboolean pspp_sheet_view_search_iter               (GtkTreeModel     *model,
377                                                          PsppSheetSelection *selection,
378                                                          GtkTreeIter      *iter,
379                                                          const gchar      *text,
380                                                          gint             *count,
381                                                          gint              n);
382 static void     pspp_sheet_view_search_init               (GtkWidget        *entry,
383                                                          PsppSheetView      *tree_view);
384 static void     pspp_sheet_view_put                       (PsppSheetView      *tree_view,
385                                                          GtkWidget        *child_widget,
386                                                          gint              x,
387                                                          gint              y,
388                                                          gint              width,
389                                                          gint              height);
390 static gboolean pspp_sheet_view_start_editing             (PsppSheetView      *tree_view,
391                                                          GtkTreePath      *cursor_path);
392 static void pspp_sheet_view_real_start_editing (PsppSheetView       *tree_view,
393                                               PsppSheetViewColumn *column,
394                                               GtkTreePath       *path,
395                                               GtkCellEditable   *cell_editable,
396                                               GdkRectangle      *cell_area,
397                                               GdkEvent          *event,
398                                               guint              flags);
399 static void pspp_sheet_view_stop_editing                  (PsppSheetView *tree_view,
400                                                          gboolean     cancel_editing);
401 static gboolean pspp_sheet_view_real_start_interactive_search (PsppSheetView *tree_view,
402                                                              gboolean     keybinding);
403 static gboolean pspp_sheet_view_start_interactive_search      (PsppSheetView *tree_view);
404 static PsppSheetViewColumn *pspp_sheet_view_get_drop_column (PsppSheetView       *tree_view,
405                                                          PsppSheetViewColumn *column,
406                                                          gint               drop_position);
407 static gint pspp_sheet_view_find_offset (PsppSheetView *tree_view,
408                                          gint height,
409                                          int *new_node);
410
411 /* GtkBuildable */
412 static void pspp_sheet_view_buildable_add_child (GtkBuildable *tree_view,
413                                                GtkBuilder  *builder,
414                                                GObject     *child,
415                                                const gchar *type);
416 static void pspp_sheet_view_buildable_init      (GtkBuildableIface *iface);
417
418
419 static gboolean scroll_row_timeout                   (gpointer     data);
420 static void     add_scroll_timeout                   (PsppSheetView *tree_view);
421 static void     remove_scroll_timeout                (PsppSheetView *tree_view);
422
423 static guint tree_view_signals [LAST_SIGNAL] = { 0 };
424
425 \f
426
427 /* GType Methods
428  */
429
430 G_DEFINE_TYPE_WITH_CODE (PsppSheetView, pspp_sheet_view, GTK_TYPE_CONTAINER,
431                          G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
432                                                 pspp_sheet_view_buildable_init))
433
434 static void
435 pspp_sheet_view_class_init (PsppSheetViewClass *class)
436 {
437   GObjectClass *o_class;
438   GtkObjectClass *object_class;
439   GtkWidgetClass *widget_class;
440   GtkContainerClass *container_class;
441   GtkBindingSet *binding_set;
442
443   binding_set = gtk_binding_set_by_class (class);
444
445   o_class = (GObjectClass *) class;
446   object_class = (GtkObjectClass *) class;
447   widget_class = (GtkWidgetClass *) class;
448   container_class = (GtkContainerClass *) class;
449
450   /* GObject signals */
451   o_class->set_property = pspp_sheet_view_set_property;
452   o_class->get_property = pspp_sheet_view_get_property;
453   o_class->finalize = pspp_sheet_view_finalize;
454
455   /* GtkObject signals */
456   object_class->destroy = pspp_sheet_view_destroy;
457
458   /* GtkWidget signals */
459   widget_class->map = pspp_sheet_view_map;
460   widget_class->realize = pspp_sheet_view_realize;
461   widget_class->unrealize = pspp_sheet_view_unrealize;
462   widget_class->size_request = pspp_sheet_view_size_request;
463   widget_class->size_allocate = pspp_sheet_view_size_allocate;
464   widget_class->button_press_event = pspp_sheet_view_button_press;
465   widget_class->button_release_event = pspp_sheet_view_button_release;
466   widget_class->grab_broken_event = pspp_sheet_view_grab_broken;
467   /*widget_class->configure_event = pspp_sheet_view_configure;*/
468   widget_class->motion_notify_event = pspp_sheet_view_motion;
469   widget_class->expose_event = pspp_sheet_view_expose;
470   widget_class->key_press_event = pspp_sheet_view_key_press;
471   widget_class->key_release_event = pspp_sheet_view_key_release;
472   widget_class->enter_notify_event = pspp_sheet_view_enter_notify;
473   widget_class->leave_notify_event = pspp_sheet_view_leave_notify;
474   widget_class->focus_out_event = pspp_sheet_view_focus_out;
475   widget_class->drag_begin = pspp_sheet_view_drag_begin;
476   widget_class->drag_end = pspp_sheet_view_drag_end;
477   widget_class->drag_data_get = pspp_sheet_view_drag_data_get;
478   widget_class->drag_data_delete = pspp_sheet_view_drag_data_delete;
479   widget_class->drag_leave = pspp_sheet_view_drag_leave;
480   widget_class->drag_motion = pspp_sheet_view_drag_motion;
481   widget_class->drag_drop = pspp_sheet_view_drag_drop;
482   widget_class->drag_data_received = pspp_sheet_view_drag_data_received;
483   widget_class->focus = pspp_sheet_view_focus;
484   widget_class->grab_focus = pspp_sheet_view_grab_focus;
485   widget_class->style_set = pspp_sheet_view_style_set;
486   widget_class->grab_notify = pspp_sheet_view_grab_notify;
487   widget_class->state_changed = pspp_sheet_view_state_changed;
488
489   /* GtkContainer signals */
490   container_class->remove = pspp_sheet_view_remove;
491   container_class->forall = pspp_sheet_view_forall;
492   container_class->set_focus_child = pspp_sheet_view_set_focus_child;
493
494   class->set_scroll_adjustments = pspp_sheet_view_set_adjustments;
495   class->move_cursor = pspp_sheet_view_real_move_cursor;
496   class->select_all = pspp_sheet_view_real_select_all;
497   class->unselect_all = pspp_sheet_view_real_unselect_all;
498   class->select_cursor_row = pspp_sheet_view_real_select_cursor_row;
499   class->toggle_cursor_row = pspp_sheet_view_real_toggle_cursor_row;
500   class->start_interactive_search = pspp_sheet_view_start_interactive_search;
501
502   /* Properties */
503
504   g_object_class_install_property (o_class,
505                                    PROP_MODEL,
506                                    g_param_spec_object ("model",
507                                                         P_("TreeView Model"),
508                                                         P_("The model for the tree view"),
509                                                         GTK_TYPE_TREE_MODEL,
510                                                         GTK_PARAM_READWRITE));
511
512   g_object_class_install_property (o_class,
513                                    PROP_HADJUSTMENT,
514                                    g_param_spec_object ("hadjustment",
515                                                         P_("Horizontal Adjustment"),
516                                                         P_("Horizontal Adjustment for the widget"),
517                                                         GTK_TYPE_ADJUSTMENT,
518                                                         GTK_PARAM_READWRITE));
519
520   g_object_class_install_property (o_class,
521                                    PROP_VADJUSTMENT,
522                                    g_param_spec_object ("vadjustment",
523                                                         P_("Vertical Adjustment"),
524                                                         P_("Vertical Adjustment for the widget"),
525                                                         GTK_TYPE_ADJUSTMENT,
526                                                         GTK_PARAM_READWRITE));
527
528   g_object_class_install_property (o_class,
529                                    PROP_HEADERS_VISIBLE,
530                                    g_param_spec_boolean ("headers-visible",
531                                                          P_("Headers Visible"),
532                                                          P_("Show the column header buttons"),
533                                                          TRUE,
534                                                          GTK_PARAM_READWRITE));
535
536   g_object_class_install_property (o_class,
537                                    PROP_HEADERS_CLICKABLE,
538                                    g_param_spec_boolean ("headers-clickable",
539                                                          P_("Headers Clickable"),
540                                                          P_("Column headers respond to click events"),
541                                                          TRUE,
542                                                          GTK_PARAM_READWRITE));
543
544   g_object_class_install_property (o_class,
545                                    PROP_REORDERABLE,
546                                    g_param_spec_boolean ("reorderable",
547                                                          P_("Reorderable"),
548                                                          P_("View is reorderable"),
549                                                          FALSE,
550                                                          GTK_PARAM_READWRITE));
551
552   g_object_class_install_property (o_class,
553                                    PROP_RULES_HINT,
554                                    g_param_spec_boolean ("rules-hint",
555                                                          P_("Rules Hint"),
556                                                          P_("Set a hint to the theme engine to draw rows in alternating colors"),
557                                                          FALSE,
558                                                          GTK_PARAM_READWRITE));
559
560     g_object_class_install_property (o_class,
561                                      PROP_ENABLE_SEARCH,
562                                      g_param_spec_boolean ("enable-search",
563                                                            P_("Enable Search"),
564                                                            P_("View allows user to search through columns interactively"),
565                                                            TRUE,
566                                                            GTK_PARAM_READWRITE));
567
568     g_object_class_install_property (o_class,
569                                      PROP_SEARCH_COLUMN,
570                                      g_param_spec_int ("search-column",
571                                                        P_("Search Column"),
572                                                        P_("Model column to search through during interactive search"),
573                                                        -1,
574                                                        G_MAXINT,
575                                                        -1,
576                                                        GTK_PARAM_READWRITE));
577
578     /**
579      * PsppSheetView:hover-selection:
580      * 
581      * Enables of disables the hover selection mode of @tree_view.
582      * Hover selection makes the selected row follow the pointer.
583      * Currently, this works only for the selection modes 
584      * %GTK_SELECTION_SINGLE and %GTK_SELECTION_BROWSE.
585      *
586      * This mode is primarily intended for treeviews in popups, e.g.
587      * in #GtkComboBox or #GtkEntryCompletion.
588      *
589      * Since: 2.6
590      */
591     g_object_class_install_property (o_class,
592                                      PROP_HOVER_SELECTION,
593                                      g_param_spec_boolean ("hover-selection",
594                                                            P_("Hover Selection"),
595                                                            P_("Whether the selection should follow the pointer"),
596                                                            FALSE,
597                                                            GTK_PARAM_READWRITE));
598
599     g_object_class_install_property (o_class,
600                                      PROP_RUBBER_BANDING,
601                                      g_param_spec_boolean ("rubber-banding",
602                                                            P_("Rubber Banding"),
603                                                            P_("Whether to enable selection of multiple items by dragging the mouse pointer"),
604                                                            FALSE,
605                                                            GTK_PARAM_READWRITE));
606
607     g_object_class_install_property (o_class,
608                                      PROP_ENABLE_GRID_LINES,
609                                      g_param_spec_enum ("enable-grid-lines",
610                                                         P_("Enable Grid Lines"),
611                                                         P_("Whether grid lines should be drawn in the tree view"),
612                                                         PSPP_TYPE_SHEET_VIEW_GRID_LINES,
613                                                         PSPP_SHEET_VIEW_GRID_LINES_NONE,
614                                                         GTK_PARAM_READWRITE));
615
616     g_object_class_install_property (o_class,
617                                      PROP_TOOLTIP_COLUMN,
618                                      g_param_spec_int ("tooltip-column",
619                                                        P_("Tooltip Column"),
620                                                        P_("The column in the model containing the tooltip texts for the rows"),
621                                                        -1,
622                                                        G_MAXINT,
623                                                        -1,
624                                                        GTK_PARAM_READWRITE));
625
626   /* Style properties */
627 #define _TREE_VIEW_EXPANDER_SIZE 12
628 #define _TREE_VIEW_VERTICAL_SEPARATOR 2
629 #define _TREE_VIEW_HORIZONTAL_SEPARATOR 2
630
631   gtk_widget_class_install_style_property (widget_class,
632                                            g_param_spec_int ("expander-size",
633                                                              P_("Expander Size"),
634                                                              P_("Size of the expander arrow"),
635                                                              0,
636                                                              G_MAXINT,
637                                                              _TREE_VIEW_EXPANDER_SIZE,
638                                                              GTK_PARAM_READABLE));
639
640   gtk_widget_class_install_style_property (widget_class,
641                                            g_param_spec_int ("vertical-separator",
642                                                              P_("Vertical Separator Width"),
643                                                              P_("Vertical space between cells.  Must be an even number"),
644                                                              0,
645                                                              G_MAXINT,
646                                                              _TREE_VIEW_VERTICAL_SEPARATOR,
647                                                              GTK_PARAM_READABLE));
648
649   gtk_widget_class_install_style_property (widget_class,
650                                            g_param_spec_int ("horizontal-separator",
651                                                              P_("Horizontal Separator Width"),
652                                                              P_("Horizontal space between cells.  Must be an even number"),
653                                                              0,
654                                                              G_MAXINT,
655                                                              _TREE_VIEW_HORIZONTAL_SEPARATOR,
656                                                              GTK_PARAM_READABLE));
657
658   gtk_widget_class_install_style_property (widget_class,
659                                            g_param_spec_boolean ("allow-rules",
660                                                                  P_("Allow Rules"),
661                                                                  P_("Allow drawing of alternating color rows"),
662                                                                  TRUE,
663                                                                  GTK_PARAM_READABLE));
664
665   gtk_widget_class_install_style_property (widget_class,
666                                            g_param_spec_boxed ("even-row-color",
667                                                                P_("Even Row Color"),
668                                                                P_("Color to use for even rows"),
669                                                                GDK_TYPE_COLOR,
670                                                                GTK_PARAM_READABLE));
671
672   gtk_widget_class_install_style_property (widget_class,
673                                            g_param_spec_boxed ("odd-row-color",
674                                                                P_("Odd Row Color"),
675                                                                P_("Color to use for odd rows"),
676                                                                GDK_TYPE_COLOR,
677                                                                GTK_PARAM_READABLE));
678
679   gtk_widget_class_install_style_property (widget_class,
680                                            g_param_spec_boolean ("row-ending-details",
681                                                                  P_("Row Ending details"),
682                                                                  P_("Enable extended row background theming"),
683                                                                  FALSE,
684                                                                  GTK_PARAM_READABLE));
685
686   gtk_widget_class_install_style_property (widget_class,
687                                            g_param_spec_int ("grid-line-width",
688                                                              P_("Grid line width"),
689                                                              P_("Width, in pixels, of the tree view grid lines"),
690                                                              0, G_MAXINT, 1,
691                                                              GTK_PARAM_READABLE));
692
693   gtk_widget_class_install_style_property (widget_class,
694                                            g_param_spec_int ("tree-line-width",
695                                                              P_("Tree line width"),
696                                                              P_("Width, in pixels, of the tree view lines"),
697                                                              0, G_MAXINT, 1,
698                                                              GTK_PARAM_READABLE));
699
700   gtk_widget_class_install_style_property (widget_class,
701                                            g_param_spec_string ("grid-line-pattern",
702                                                                 P_("Grid line pattern"),
703                                                                 P_("Dash pattern used to draw the tree view grid lines"),
704                                                                 "\1\1",
705                                                                 GTK_PARAM_READABLE));
706
707   gtk_widget_class_install_style_property (widget_class,
708                                            g_param_spec_string ("tree-line-pattern",
709                                                                 P_("Tree line pattern"),
710                                                                 P_("Dash pattern used to draw the tree view lines"),
711                                                                 "\1\1",
712                                                                 GTK_PARAM_READABLE));
713
714   /* Signals */
715   /**
716    * PsppSheetView::set-scroll-adjustments
717    * @horizontal: the horizontal #GtkAdjustment
718    * @vertical: the vertical #GtkAdjustment
719    *
720    * Set the scroll adjustments for the tree view. Usually scrolled containers
721    * like #GtkScrolledWindow will emit this signal to connect two instances
722    * of #GtkScrollbar to the scroll directions of the #PsppSheetView.
723    */
724   widget_class->set_scroll_adjustments_signal =
725     g_signal_new ("set-scroll-adjustments",
726                   G_TYPE_FROM_CLASS (o_class),
727                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
728                   G_STRUCT_OFFSET (PsppSheetViewClass, set_scroll_adjustments),
729                   NULL, NULL,
730                   psppire_marshal_VOID__OBJECT_OBJECT,
731                   G_TYPE_NONE, 2,
732                   GTK_TYPE_ADJUSTMENT,
733                   GTK_TYPE_ADJUSTMENT);
734
735   /**
736    * PsppSheetView::row-activated:
737    * @tree_view: the object on which the signal is emitted
738    * @path: the #GtkTreePath for the activated row
739    * @column: the #PsppSheetViewColumn in which the activation occurred
740    *
741    * The "row-activated" signal is emitted when the method
742    * pspp_sheet_view_row_activated() is called or the user double clicks 
743    * a treeview row. It is also emitted when a non-editable row is 
744    * selected and one of the keys: Space, Shift+Space, Return or 
745    * Enter is pressed.
746    * 
747    * For selection handling refer to the <link linkend="TreeWidget">tree 
748    * widget conceptual overview</link> as well as #PsppSheetSelection.
749    */
750   tree_view_signals[ROW_ACTIVATED] =
751     g_signal_new ("row-activated",
752                   G_TYPE_FROM_CLASS (o_class),
753                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
754                   G_STRUCT_OFFSET (PsppSheetViewClass, row_activated),
755                   NULL, NULL,
756                   psppire_marshal_VOID__BOXED_OBJECT,
757                   G_TYPE_NONE, 2,
758                   GTK_TYPE_TREE_PATH,
759                   PSPP_TYPE_SHEET_VIEW_COLUMN);
760
761   /**
762    * PsppSheetView::columns-changed:
763    * @tree_view: the object on which the signal is emitted 
764    * 
765    * The number of columns of the treeview has changed.
766    */
767   tree_view_signals[COLUMNS_CHANGED] =
768     g_signal_new ("columns-changed",
769                   G_TYPE_FROM_CLASS (o_class),
770                   G_SIGNAL_RUN_LAST,
771                   G_STRUCT_OFFSET (PsppSheetViewClass, columns_changed),
772                   NULL, NULL,
773                   g_cclosure_marshal_VOID__VOID,
774                   G_TYPE_NONE, 0);
775
776   /**
777    * PsppSheetView::cursor-changed:
778    * @tree_view: the object on which the signal is emitted
779    * 
780    * The position of the cursor (focused cell) has changed.
781    */
782   tree_view_signals[CURSOR_CHANGED] =
783     g_signal_new ("cursor-changed",
784                   G_TYPE_FROM_CLASS (o_class),
785                   G_SIGNAL_RUN_LAST,
786                   G_STRUCT_OFFSET (PsppSheetViewClass, cursor_changed),
787                   NULL, NULL,
788                   g_cclosure_marshal_VOID__VOID,
789                   G_TYPE_NONE, 0);
790
791   tree_view_signals[MOVE_CURSOR] =
792     g_signal_new ("move-cursor",
793                   G_TYPE_FROM_CLASS (object_class),
794                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
795                   G_STRUCT_OFFSET (PsppSheetViewClass, move_cursor),
796                   NULL, NULL,
797                   psppire_marshal_BOOLEAN__ENUM_INT,
798                   G_TYPE_BOOLEAN, 2,
799                   GTK_TYPE_MOVEMENT_STEP,
800                   G_TYPE_INT);
801
802   tree_view_signals[SELECT_ALL] =
803     g_signal_new ("select-all",
804                   G_TYPE_FROM_CLASS (object_class),
805                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
806                   G_STRUCT_OFFSET (PsppSheetViewClass, select_all),
807                   NULL, NULL,
808                   psppire_marshal_BOOLEAN__VOID,
809                   G_TYPE_BOOLEAN, 0);
810
811   tree_view_signals[UNSELECT_ALL] =
812     g_signal_new ("unselect-all",
813                   G_TYPE_FROM_CLASS (object_class),
814                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
815                   G_STRUCT_OFFSET (PsppSheetViewClass, unselect_all),
816                   NULL, NULL,
817                   psppire_marshal_BOOLEAN__VOID,
818                   G_TYPE_BOOLEAN, 0);
819
820   tree_view_signals[SELECT_CURSOR_ROW] =
821     g_signal_new ("select-cursor-row",
822                   G_TYPE_FROM_CLASS (object_class),
823                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
824                   G_STRUCT_OFFSET (PsppSheetViewClass, select_cursor_row),
825                   NULL, NULL,
826                   psppire_marshal_BOOLEAN__BOOLEAN,
827                   G_TYPE_BOOLEAN, 1,
828                   G_TYPE_BOOLEAN);
829
830   tree_view_signals[TOGGLE_CURSOR_ROW] =
831     g_signal_new ("toggle-cursor-row",
832                   G_TYPE_FROM_CLASS (object_class),
833                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
834                   G_STRUCT_OFFSET (PsppSheetViewClass, toggle_cursor_row),
835                   NULL, NULL,
836                   psppire_marshal_BOOLEAN__VOID,
837                   G_TYPE_BOOLEAN, 0);
838
839   tree_view_signals[START_INTERACTIVE_SEARCH] =
840     g_signal_new ("start-interactive-search",
841                   G_TYPE_FROM_CLASS (object_class),
842                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
843                   G_STRUCT_OFFSET (PsppSheetViewClass, start_interactive_search),
844                   NULL, NULL,
845                   psppire_marshal_BOOLEAN__VOID,
846                   G_TYPE_BOOLEAN, 0);
847
848   /* Key bindings */
849   pspp_sheet_view_add_move_binding (binding_set, GDK_Up, 0, TRUE,
850                                   GTK_MOVEMENT_DISPLAY_LINES, -1);
851   pspp_sheet_view_add_move_binding (binding_set, GDK_KP_Up, 0, TRUE,
852                                   GTK_MOVEMENT_DISPLAY_LINES, -1);
853
854   pspp_sheet_view_add_move_binding (binding_set, GDK_Down, 0, TRUE,
855                                   GTK_MOVEMENT_DISPLAY_LINES, 1);
856   pspp_sheet_view_add_move_binding (binding_set, GDK_KP_Down, 0, TRUE,
857                                   GTK_MOVEMENT_DISPLAY_LINES, 1);
858
859   pspp_sheet_view_add_move_binding (binding_set, GDK_p, GDK_CONTROL_MASK, FALSE,
860                                   GTK_MOVEMENT_DISPLAY_LINES, -1);
861
862   pspp_sheet_view_add_move_binding (binding_set, GDK_n, GDK_CONTROL_MASK, FALSE,
863                                   GTK_MOVEMENT_DISPLAY_LINES, 1);
864
865   pspp_sheet_view_add_move_binding (binding_set, GDK_Home, 0, TRUE,
866                                   GTK_MOVEMENT_BUFFER_ENDS, -1);
867   pspp_sheet_view_add_move_binding (binding_set, GDK_KP_Home, 0, TRUE,
868                                   GTK_MOVEMENT_BUFFER_ENDS, -1);
869
870   pspp_sheet_view_add_move_binding (binding_set, GDK_End, 0, TRUE,
871                                   GTK_MOVEMENT_BUFFER_ENDS, 1);
872   pspp_sheet_view_add_move_binding (binding_set, GDK_KP_End, 0, TRUE,
873                                   GTK_MOVEMENT_BUFFER_ENDS, 1);
874
875   pspp_sheet_view_add_move_binding (binding_set, GDK_Page_Up, 0, TRUE,
876                                   GTK_MOVEMENT_PAGES, -1);
877   pspp_sheet_view_add_move_binding (binding_set, GDK_KP_Page_Up, 0, TRUE,
878                                   GTK_MOVEMENT_PAGES, -1);
879
880   pspp_sheet_view_add_move_binding (binding_set, GDK_Page_Down, 0, TRUE,
881                                   GTK_MOVEMENT_PAGES, 1);
882   pspp_sheet_view_add_move_binding (binding_set, GDK_KP_Page_Down, 0, TRUE,
883                                   GTK_MOVEMENT_PAGES, 1);
884
885
886   gtk_binding_entry_add_signal (binding_set, GDK_Right, 0, "move-cursor", 2,
887                                 G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
888                                 G_TYPE_INT, 1);
889
890   gtk_binding_entry_add_signal (binding_set, GDK_Left, 0, "move-cursor", 2,
891                                 G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
892                                 G_TYPE_INT, -1);
893
894   gtk_binding_entry_add_signal (binding_set, GDK_KP_Right, 0, "move-cursor", 2,
895                                 G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
896                                 G_TYPE_INT, 1);
897
898   gtk_binding_entry_add_signal (binding_set, GDK_KP_Left, 0, "move-cursor", 2,
899                                 G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
900                                 G_TYPE_INT, -1);
901
902   gtk_binding_entry_add_signal (binding_set, GDK_Right, GDK_CONTROL_MASK,
903                                 "move-cursor", 2,
904                                 G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
905                                 G_TYPE_INT, 1);
906
907   gtk_binding_entry_add_signal (binding_set, GDK_Left, GDK_CONTROL_MASK,
908                                 "move-cursor", 2,
909                                 G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
910                                 G_TYPE_INT, -1);
911
912   gtk_binding_entry_add_signal (binding_set, GDK_KP_Right, GDK_CONTROL_MASK,
913                                 "move-cursor", 2,
914                                 G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
915                                 G_TYPE_INT, 1);
916
917   gtk_binding_entry_add_signal (binding_set, GDK_KP_Left, GDK_CONTROL_MASK,
918                                 "move-cursor", 2,
919                                 G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
920                                 G_TYPE_INT, -1);
921
922   gtk_binding_entry_add_signal (binding_set, GDK_space, GDK_CONTROL_MASK, "toggle-cursor-row", 0);
923   gtk_binding_entry_add_signal (binding_set, GDK_KP_Space, GDK_CONTROL_MASK, "toggle-cursor-row", 0);
924
925   gtk_binding_entry_add_signal (binding_set, GDK_a, GDK_CONTROL_MASK, "select-all", 0);
926   gtk_binding_entry_add_signal (binding_set, GDK_slash, GDK_CONTROL_MASK, "select-all", 0);
927
928   gtk_binding_entry_add_signal (binding_set, GDK_A, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "unselect-all", 0);
929   gtk_binding_entry_add_signal (binding_set, GDK_backslash, GDK_CONTROL_MASK, "unselect-all", 0);
930
931   gtk_binding_entry_add_signal (binding_set, GDK_space, GDK_SHIFT_MASK, "select-cursor-row", 1,
932                                 G_TYPE_BOOLEAN, TRUE);
933   gtk_binding_entry_add_signal (binding_set, GDK_KP_Space, GDK_SHIFT_MASK, "select-cursor-row", 1,
934                                 G_TYPE_BOOLEAN, TRUE);
935
936   gtk_binding_entry_add_signal (binding_set, GDK_space, 0, "select-cursor-row", 1,
937                                 G_TYPE_BOOLEAN, TRUE);
938   gtk_binding_entry_add_signal (binding_set, GDK_KP_Space, 0, "select-cursor-row", 1,
939                                 G_TYPE_BOOLEAN, TRUE);
940   gtk_binding_entry_add_signal (binding_set, GDK_Return, 0, "select-cursor-row", 1,
941                                 G_TYPE_BOOLEAN, TRUE);
942   gtk_binding_entry_add_signal (binding_set, GDK_ISO_Enter, 0, "select-cursor-row", 1,
943                                 G_TYPE_BOOLEAN, TRUE);
944   gtk_binding_entry_add_signal (binding_set, GDK_KP_Enter, 0, "select-cursor-row", 1,
945                                 G_TYPE_BOOLEAN, TRUE);
946
947   gtk_binding_entry_add_signal (binding_set, GDK_BackSpace, 0, "select-cursor-parent", 0);
948   gtk_binding_entry_add_signal (binding_set, GDK_BackSpace, GDK_CONTROL_MASK, "select-cursor-parent", 0);
949
950   gtk_binding_entry_add_signal (binding_set, GDK_f, GDK_CONTROL_MASK, "start-interactive-search", 0);
951
952   gtk_binding_entry_add_signal (binding_set, GDK_F, GDK_CONTROL_MASK, "start-interactive-search", 0);
953
954   g_type_class_add_private (o_class, sizeof (PsppSheetViewPrivate));
955 }
956
957 static void
958 pspp_sheet_view_buildable_init (GtkBuildableIface *iface)
959 {
960   iface->add_child = pspp_sheet_view_buildable_add_child;
961 }
962
963 static void
964 pspp_sheet_view_init (PsppSheetView *tree_view)
965 {
966   tree_view->priv = G_TYPE_INSTANCE_GET_PRIVATE (tree_view, PSPP_TYPE_SHEET_VIEW, PsppSheetViewPrivate);
967
968   gtk_widget_set_can_focus (GTK_WIDGET (tree_view), TRUE);
969   gtk_widget_set_redraw_on_allocate (GTK_WIDGET (tree_view), FALSE);
970
971   tree_view->priv->flags =  PSPP_SHEET_VIEW_DRAW_KEYFOCUS
972                             | PSPP_SHEET_VIEW_HEADERS_VISIBLE;
973
974   /* We need some padding */
975   tree_view->priv->selected = range_tower_create ();
976   tree_view->priv->dy = 0;
977   tree_view->priv->cursor_offset = 0;
978   tree_view->priv->n_columns = 0;
979   tree_view->priv->header_height = 1;
980   tree_view->priv->x_drag = 0;
981   tree_view->priv->drag_pos = -1;
982   tree_view->priv->header_has_focus = FALSE;
983   tree_view->priv->pressed_button = -1;
984   tree_view->priv->press_start_x = -1;
985   tree_view->priv->press_start_y = -1;
986   tree_view->priv->reorderable = FALSE;
987   tree_view->priv->presize_handler_timer = 0;
988   tree_view->priv->scroll_sync_timer = 0;
989   tree_view->priv->fixed_height = -1;
990   pspp_sheet_view_set_adjustments (tree_view, NULL, NULL);
991   tree_view->priv->selection = _pspp_sheet_selection_new_with_tree_view (tree_view);
992   tree_view->priv->enable_search = TRUE;
993   tree_view->priv->search_column = -1;
994   tree_view->priv->search_position_func = pspp_sheet_view_search_position_func;
995   tree_view->priv->search_equal_func = pspp_sheet_view_search_equal_func;
996   tree_view->priv->search_custom_entry_set = FALSE;
997   tree_view->priv->typeselect_flush_timeout = 0;
998   tree_view->priv->init_hadjust_value = TRUE;    
999   tree_view->priv->width = 0;
1000           
1001   tree_view->priv->hover_selection = FALSE;
1002
1003   tree_view->priv->rubber_banding_enable = FALSE;
1004
1005   tree_view->priv->grid_lines = PSPP_SHEET_VIEW_GRID_LINES_NONE;
1006
1007   tree_view->priv->tooltip_column = -1;
1008
1009   tree_view->priv->post_validation_flag = FALSE;
1010
1011   tree_view->priv->last_button_x = -1;
1012   tree_view->priv->last_button_y = -1;
1013
1014   tree_view->priv->event_last_x = -10000;
1015   tree_view->priv->event_last_y = -10000;
1016
1017   tree_view->priv->prelight_node = -1;
1018   tree_view->priv->rubber_band_start_node = -1;
1019   tree_view->priv->rubber_band_end_node = -1;
1020 }
1021
1022 \f
1023
1024 /* GObject Methods
1025  */
1026
1027 static void
1028 pspp_sheet_view_set_property (GObject         *object,
1029                             guint            prop_id,
1030                             const GValue    *value,
1031                             GParamSpec      *pspec)
1032 {
1033   PsppSheetView *tree_view;
1034
1035   tree_view = PSPP_SHEET_VIEW (object);
1036
1037   switch (prop_id)
1038     {
1039     case PROP_MODEL:
1040       pspp_sheet_view_set_model (tree_view, g_value_get_object (value));
1041       break;
1042     case PROP_HADJUSTMENT:
1043       pspp_sheet_view_set_hadjustment (tree_view, g_value_get_object (value));
1044       break;
1045     case PROP_VADJUSTMENT:
1046       pspp_sheet_view_set_vadjustment (tree_view, g_value_get_object (value));
1047       break;
1048     case PROP_HEADERS_VISIBLE:
1049       pspp_sheet_view_set_headers_visible (tree_view, g_value_get_boolean (value));
1050       break;
1051     case PROP_HEADERS_CLICKABLE:
1052       pspp_sheet_view_set_headers_clickable (tree_view, g_value_get_boolean (value));
1053       break;
1054     case PROP_REORDERABLE:
1055       pspp_sheet_view_set_reorderable (tree_view, g_value_get_boolean (value));
1056       break;
1057     case PROP_RULES_HINT:
1058       pspp_sheet_view_set_rules_hint (tree_view, g_value_get_boolean (value));
1059       break;
1060     case PROP_ENABLE_SEARCH:
1061       pspp_sheet_view_set_enable_search (tree_view, g_value_get_boolean (value));
1062       break;
1063     case PROP_SEARCH_COLUMN:
1064       pspp_sheet_view_set_search_column (tree_view, g_value_get_int (value));
1065       break;
1066     case PROP_HOVER_SELECTION:
1067       tree_view->priv->hover_selection = g_value_get_boolean (value);
1068       break;
1069     case PROP_RUBBER_BANDING:
1070       tree_view->priv->rubber_banding_enable = g_value_get_boolean (value);
1071       break;
1072     case PROP_ENABLE_GRID_LINES:
1073       pspp_sheet_view_set_grid_lines (tree_view, g_value_get_enum (value));
1074       break;
1075     case PROP_TOOLTIP_COLUMN:
1076       pspp_sheet_view_set_tooltip_column (tree_view, g_value_get_int (value));
1077       break;
1078     default:
1079       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1080       break;
1081     }
1082 }
1083
1084 static void
1085 pspp_sheet_view_get_property (GObject    *object,
1086                             guint       prop_id,
1087                             GValue     *value,
1088                             GParamSpec *pspec)
1089 {
1090   PsppSheetView *tree_view;
1091
1092   tree_view = PSPP_SHEET_VIEW (object);
1093
1094   switch (prop_id)
1095     {
1096     case PROP_MODEL:
1097       g_value_set_object (value, tree_view->priv->model);
1098       break;
1099     case PROP_HADJUSTMENT:
1100       g_value_set_object (value, tree_view->priv->hadjustment);
1101       break;
1102     case PROP_VADJUSTMENT:
1103       g_value_set_object (value, tree_view->priv->vadjustment);
1104       break;
1105     case PROP_HEADERS_VISIBLE:
1106       g_value_set_boolean (value, pspp_sheet_view_get_headers_visible (tree_view));
1107       break;
1108     case PROP_HEADERS_CLICKABLE:
1109       g_value_set_boolean (value, pspp_sheet_view_get_headers_clickable (tree_view));
1110       break;
1111     case PROP_REORDERABLE:
1112       g_value_set_boolean (value, tree_view->priv->reorderable);
1113       break;
1114     case PROP_RULES_HINT:
1115       g_value_set_boolean (value, tree_view->priv->has_rules);
1116       break;
1117     case PROP_ENABLE_SEARCH:
1118       g_value_set_boolean (value, tree_view->priv->enable_search);
1119       break;
1120     case PROP_SEARCH_COLUMN:
1121       g_value_set_int (value, tree_view->priv->search_column);
1122       break;
1123     case PROP_HOVER_SELECTION:
1124       g_value_set_boolean (value, tree_view->priv->hover_selection);
1125       break;
1126     case PROP_RUBBER_BANDING:
1127       g_value_set_boolean (value, tree_view->priv->rubber_banding_enable);
1128       break;
1129     case PROP_ENABLE_GRID_LINES:
1130       g_value_set_enum (value, tree_view->priv->grid_lines);
1131       break;
1132     case PROP_TOOLTIP_COLUMN:
1133       g_value_set_int (value, tree_view->priv->tooltip_column);
1134       break;
1135     default:
1136       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1137       break;
1138     }
1139 }
1140
1141 static void
1142 pspp_sheet_view_finalize (GObject *object)
1143 {
1144   G_OBJECT_CLASS (pspp_sheet_view_parent_class)->finalize (object);
1145 }
1146
1147 \f
1148
1149 static void
1150 pspp_sheet_view_buildable_add_child (GtkBuildable *tree_view,
1151                                    GtkBuilder  *builder,
1152                                    GObject     *child,
1153                                    const gchar *type)
1154 {
1155   pspp_sheet_view_append_column (PSPP_SHEET_VIEW (tree_view), PSPP_SHEET_VIEW_COLUMN (child));
1156 }
1157
1158 /* GtkObject Methods
1159  */
1160
1161 static void
1162 pspp_sheet_view_destroy (GtkObject *object)
1163 {
1164   PsppSheetView *tree_view = PSPP_SHEET_VIEW (object);
1165   GList *list;
1166
1167   pspp_sheet_view_stop_editing (tree_view, TRUE);
1168
1169   if (tree_view->priv->selected != NULL)
1170     {
1171       range_tower_destroy (tree_view->priv->selected);
1172       tree_view->priv->selected = NULL;
1173     }
1174
1175   if (tree_view->priv->columns != NULL)
1176     {
1177       list = tree_view->priv->columns;
1178       while (list)
1179         {
1180           PsppSheetViewColumn *column;
1181           column = PSPP_SHEET_VIEW_COLUMN (list->data);
1182           list = list->next;
1183           pspp_sheet_view_remove_column (tree_view, column);
1184         }
1185       tree_view->priv->columns = NULL;
1186     }
1187
1188   tree_view->priv->prelight_node = -1;
1189
1190   if (tree_view->priv->selection != NULL)
1191     {
1192       _pspp_sheet_selection_set_tree_view (tree_view->priv->selection, NULL);
1193       g_object_unref (tree_view->priv->selection);
1194       tree_view->priv->selection = NULL;
1195     }
1196
1197   if (tree_view->priv->scroll_to_path != NULL)
1198     {
1199       gtk_tree_row_reference_free (tree_view->priv->scroll_to_path);
1200       tree_view->priv->scroll_to_path = NULL;
1201     }
1202
1203   if (tree_view->priv->drag_dest_row != NULL)
1204     {
1205       gtk_tree_row_reference_free (tree_view->priv->drag_dest_row);
1206       tree_view->priv->drag_dest_row = NULL;
1207     }
1208
1209   if (tree_view->priv->top_row != NULL)
1210     {
1211       gtk_tree_row_reference_free (tree_view->priv->top_row);
1212       tree_view->priv->top_row = NULL;
1213     }
1214
1215   if (tree_view->priv->column_drop_func_data &&
1216       tree_view->priv->column_drop_func_data_destroy)
1217     {
1218       tree_view->priv->column_drop_func_data_destroy (tree_view->priv->column_drop_func_data);
1219       tree_view->priv->column_drop_func_data = NULL;
1220     }
1221
1222   if (tree_view->priv->destroy_count_destroy &&
1223       tree_view->priv->destroy_count_data)
1224     {
1225       tree_view->priv->destroy_count_destroy (tree_view->priv->destroy_count_data);
1226       tree_view->priv->destroy_count_data = NULL;
1227     }
1228
1229   gtk_tree_row_reference_free (tree_view->priv->cursor);
1230   tree_view->priv->cursor = NULL;
1231
1232   gtk_tree_row_reference_free (tree_view->priv->anchor);
1233   tree_view->priv->anchor = NULL;
1234
1235   /* destroy interactive search dialog */
1236   if (tree_view->priv->search_window)
1237     {
1238       gtk_widget_destroy (tree_view->priv->search_window);
1239       tree_view->priv->search_window = NULL;
1240       tree_view->priv->search_entry = NULL;
1241       if (tree_view->priv->typeselect_flush_timeout)
1242         {
1243           g_source_remove (tree_view->priv->typeselect_flush_timeout);
1244           tree_view->priv->typeselect_flush_timeout = 0;
1245         }
1246     }
1247
1248   if (tree_view->priv->search_destroy && tree_view->priv->search_user_data)
1249     {
1250       tree_view->priv->search_destroy (tree_view->priv->search_user_data);
1251       tree_view->priv->search_user_data = NULL;
1252     }
1253
1254   if (tree_view->priv->search_position_destroy && tree_view->priv->search_position_user_data)
1255     {
1256       tree_view->priv->search_position_destroy (tree_view->priv->search_position_user_data);
1257       tree_view->priv->search_position_user_data = NULL;
1258     }
1259
1260   pspp_sheet_view_set_model (tree_view, NULL);
1261
1262   if (tree_view->priv->hadjustment)
1263     {
1264       g_object_unref (tree_view->priv->hadjustment);
1265       tree_view->priv->hadjustment = NULL;
1266     }
1267   if (tree_view->priv->vadjustment)
1268     {
1269       g_object_unref (tree_view->priv->vadjustment);
1270       tree_view->priv->vadjustment = NULL;
1271     }
1272
1273   GTK_OBJECT_CLASS (pspp_sheet_view_parent_class)->destroy (object);
1274 }
1275
1276 \f
1277
1278 /* GtkWidget Methods
1279  */
1280
1281 /* GtkWidget::map helper */
1282 static void
1283 pspp_sheet_view_map_buttons (PsppSheetView *tree_view)
1284 {
1285   GList *list;
1286
1287   g_return_if_fail (gtk_widget_get_mapped (GTK_WIDGET (tree_view)));
1288
1289   if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_HEADERS_VISIBLE))
1290     {
1291       PsppSheetViewColumn *column;
1292
1293       for (list = tree_view->priv->columns; list; list = list->next)
1294         {
1295           column = list->data;
1296           if (gtk_widget_get_visible (column->button) &&
1297               !gtk_widget_get_mapped (column->button))
1298             gtk_widget_map (column->button);
1299         }
1300       for (list = tree_view->priv->columns; list; list = list->next)
1301         {
1302           column = list->data;
1303           if (column->visible == FALSE)
1304             continue;
1305           if (column->resizable)
1306             {
1307               gdk_window_raise (column->window);
1308               gdk_window_show (column->window);
1309             }
1310           else
1311             gdk_window_hide (column->window);
1312         }
1313       gdk_window_show (tree_view->priv->header_window);
1314     }
1315 }
1316
1317 static void
1318 pspp_sheet_view_map (GtkWidget *widget)
1319 {
1320   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
1321   GList *tmp_list;
1322
1323   gtk_widget_set_mapped (widget, TRUE);
1324
1325   tmp_list = tree_view->priv->children;
1326   while (tmp_list)
1327     {
1328       PsppSheetViewChild *child = tmp_list->data;
1329       tmp_list = tmp_list->next;
1330
1331       if (gtk_widget_get_visible (child->widget))
1332         {
1333           if (!gtk_widget_get_mapped (child->widget))
1334             gtk_widget_map (child->widget);
1335         }
1336     }
1337   gdk_window_show (tree_view->priv->bin_window);
1338
1339   pspp_sheet_view_map_buttons (tree_view);
1340
1341   gdk_window_show (widget->window);
1342 }
1343
1344 static void
1345 pspp_sheet_view_realize (GtkWidget *widget)
1346 {
1347   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
1348   GList *tmp_list;
1349   GdkWindowAttr attributes;
1350   gint attributes_mask;
1351
1352   gtk_widget_set_realized (widget, TRUE);
1353
1354   /* Make the main, clipping window */
1355   attributes.window_type = GDK_WINDOW_CHILD;
1356   attributes.x = widget->allocation.x;
1357   attributes.y = widget->allocation.y;
1358   attributes.width = widget->allocation.width;
1359   attributes.height = widget->allocation.height;
1360   attributes.wclass = GDK_INPUT_OUTPUT;
1361   attributes.visual = gtk_widget_get_visual (widget);
1362   attributes.colormap = gtk_widget_get_colormap (widget);
1363   attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK;
1364
1365   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
1366
1367   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
1368                                    &attributes, attributes_mask);
1369   gdk_window_set_user_data (widget->window, widget);
1370
1371   /* Make the window for the tree */
1372   attributes.x = 0;
1373   attributes.y = TREE_VIEW_HEADER_HEIGHT (tree_view);
1374   attributes.width = MAX (tree_view->priv->width, widget->allocation.width);
1375   attributes.height = widget->allocation.height;
1376   attributes.event_mask = (GDK_EXPOSURE_MASK |
1377                            GDK_SCROLL_MASK |
1378                            GDK_POINTER_MOTION_MASK |
1379                            GDK_ENTER_NOTIFY_MASK |
1380                            GDK_LEAVE_NOTIFY_MASK |
1381                            GDK_BUTTON_PRESS_MASK |
1382                            GDK_BUTTON_RELEASE_MASK |
1383                            gtk_widget_get_events (widget));
1384
1385   tree_view->priv->bin_window = gdk_window_new (widget->window,
1386                                                 &attributes, attributes_mask);
1387   gdk_window_set_user_data (tree_view->priv->bin_window, widget);
1388
1389   /* Make the column header window */
1390   attributes.x = 0;
1391   attributes.y = 0;
1392   attributes.width = MAX (tree_view->priv->width, widget->allocation.width);
1393   attributes.height = tree_view->priv->header_height;
1394   attributes.event_mask = (GDK_EXPOSURE_MASK |
1395                            GDK_SCROLL_MASK |
1396                            GDK_BUTTON_PRESS_MASK |
1397                            GDK_BUTTON_RELEASE_MASK |
1398                            GDK_KEY_PRESS_MASK |
1399                            GDK_KEY_RELEASE_MASK |
1400                            gtk_widget_get_events (widget));
1401
1402   tree_view->priv->header_window = gdk_window_new (widget->window,
1403                                                    &attributes, attributes_mask);
1404   gdk_window_set_user_data (tree_view->priv->header_window, widget);
1405
1406   /* Add them all up. */
1407   widget->style = gtk_style_attach (widget->style, widget->window);
1408   gdk_window_set_back_pixmap (widget->window, NULL, FALSE);
1409   gdk_window_set_background (tree_view->priv->bin_window, &widget->style->base[widget->state]);
1410   gtk_style_set_background (widget->style, tree_view->priv->header_window, GTK_STATE_NORMAL);
1411
1412   tmp_list = tree_view->priv->children;
1413   while (tmp_list)
1414     {
1415       PsppSheetViewChild *child = tmp_list->data;
1416       tmp_list = tmp_list->next;
1417
1418       gtk_widget_set_parent_window (child->widget, tree_view->priv->bin_window);
1419     }
1420
1421   for (tmp_list = tree_view->priv->columns; tmp_list; tmp_list = tmp_list->next)
1422     _pspp_sheet_view_column_realize_button (PSPP_SHEET_VIEW_COLUMN (tmp_list->data));
1423
1424   /* Need to call those here, since they create GCs */
1425   pspp_sheet_view_set_grid_lines (tree_view, tree_view->priv->grid_lines);
1426
1427   install_presize_handler (tree_view); 
1428 }
1429
1430 static void
1431 pspp_sheet_view_unrealize (GtkWidget *widget)
1432 {
1433   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
1434   PsppSheetViewPrivate *priv = tree_view->priv;
1435   GList *list;
1436
1437   if (priv->scroll_timeout != 0)
1438     {
1439       g_source_remove (priv->scroll_timeout);
1440       priv->scroll_timeout = 0;
1441     }
1442
1443   if (priv->open_dest_timeout != 0)
1444     {
1445       g_source_remove (priv->open_dest_timeout);
1446       priv->open_dest_timeout = 0;
1447     }
1448
1449   if (priv->presize_handler_timer != 0)
1450     {
1451       g_source_remove (priv->presize_handler_timer);
1452       priv->presize_handler_timer = 0;
1453     }
1454
1455   if (priv->validate_rows_timer != 0)
1456     {
1457       g_source_remove (priv->validate_rows_timer);
1458       priv->validate_rows_timer = 0;
1459     }
1460
1461   if (priv->scroll_sync_timer != 0)
1462     {
1463       g_source_remove (priv->scroll_sync_timer);
1464       priv->scroll_sync_timer = 0;
1465     }
1466
1467   if (priv->typeselect_flush_timeout)
1468     {
1469       g_source_remove (priv->typeselect_flush_timeout);
1470       priv->typeselect_flush_timeout = 0;
1471     }
1472   
1473   for (list = priv->columns; list; list = list->next)
1474     _pspp_sheet_view_column_unrealize_button (PSPP_SHEET_VIEW_COLUMN (list->data));
1475
1476   gdk_window_set_user_data (priv->bin_window, NULL);
1477   gdk_window_destroy (priv->bin_window);
1478   priv->bin_window = NULL;
1479
1480   gdk_window_set_user_data (priv->header_window, NULL);
1481   gdk_window_destroy (priv->header_window);
1482   priv->header_window = NULL;
1483
1484   if (priv->drag_window)
1485     {
1486       gdk_window_set_user_data (priv->drag_window, NULL);
1487       gdk_window_destroy (priv->drag_window);
1488       priv->drag_window = NULL;
1489     }
1490
1491   if (priv->drag_highlight_window)
1492     {
1493       gdk_window_set_user_data (priv->drag_highlight_window, NULL);
1494       gdk_window_destroy (priv->drag_highlight_window);
1495       priv->drag_highlight_window = NULL;
1496     }
1497
1498   if (priv->grid_line_gc)
1499     {
1500       g_object_unref (priv->grid_line_gc);
1501       priv->grid_line_gc = NULL;
1502     }
1503
1504   GTK_WIDGET_CLASS (pspp_sheet_view_parent_class)->unrealize (widget);
1505 }
1506
1507 /* GtkWidget::size_request helper */
1508 static void
1509 pspp_sheet_view_size_request_columns (PsppSheetView *tree_view)
1510 {
1511   GList *list;
1512
1513   tree_view->priv->header_height = 0;
1514
1515   if (tree_view->priv->model)
1516     {
1517       for (list = tree_view->priv->columns; list; list = list->next)
1518         {
1519           GtkRequisition requisition;
1520           PsppSheetViewColumn *column = list->data;
1521
1522           if (column->button == NULL)
1523             continue;
1524
1525           column = list->data;
1526           
1527           gtk_widget_size_request (column->button, &requisition);
1528           column->button_request = requisition.width;
1529           tree_view->priv->header_height = MAX (tree_view->priv->header_height, requisition.height);
1530         }
1531     }
1532 }
1533
1534
1535 /* Called only by ::size_request */
1536 static void
1537 pspp_sheet_view_update_size (PsppSheetView *tree_view)
1538 {
1539   GList *list;
1540   PsppSheetViewColumn *column;
1541   gint i;
1542
1543   if (tree_view->priv->model == NULL)
1544     {
1545       tree_view->priv->width = 0;
1546       tree_view->priv->prev_width = 0;                   
1547       tree_view->priv->height = 0;
1548       return;
1549     }
1550
1551   tree_view->priv->prev_width = tree_view->priv->width;  
1552   tree_view->priv->width = 0;
1553
1554   /* keep this in sync with size_allocate below */
1555   for (list = tree_view->priv->columns, i = 0; list; list = list->next, i++)
1556     {
1557       gint real_requested_width = 0;
1558       column = list->data;
1559       if (!column->visible)
1560         continue;
1561
1562       if (column->use_resized_width)
1563         {
1564           real_requested_width = column->resized_width;
1565         }
1566       else
1567         {
1568           real_requested_width = column->fixed_width;
1569         }
1570
1571       if (column->min_width != -1)
1572         real_requested_width = MAX (real_requested_width, column->min_width);
1573       if (column->max_width != -1)
1574         real_requested_width = MIN (real_requested_width, column->max_width);
1575
1576       tree_view->priv->width += real_requested_width;
1577     }
1578
1579   tree_view->priv->height = tree_view->priv->fixed_height * tree_view->priv->row_count;
1580 }
1581
1582 static void
1583 pspp_sheet_view_size_request (GtkWidget      *widget,
1584                             GtkRequisition *requisition)
1585 {
1586   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
1587   GList *tmp_list;
1588
1589   /* we validate some rows initially just to make sure we have some size. 
1590    * In practice, with a lot of static lists, this should get a good width.
1591    */
1592   initialize_fixed_height_mode (tree_view);
1593   pspp_sheet_view_size_request_columns (tree_view);
1594   pspp_sheet_view_update_size (PSPP_SHEET_VIEW (widget));
1595
1596   requisition->width = tree_view->priv->width;
1597   requisition->height = tree_view->priv->height + TREE_VIEW_HEADER_HEIGHT (tree_view);
1598
1599   tmp_list = tree_view->priv->children;
1600
1601   while (tmp_list)
1602     {
1603       PsppSheetViewChild *child = tmp_list->data;
1604       GtkRequisition child_requisition;
1605
1606       tmp_list = tmp_list->next;
1607
1608       if (gtk_widget_get_visible (child->widget))
1609         gtk_widget_size_request (child->widget, &child_requisition);
1610     }
1611 }
1612
1613 static void
1614 invalidate_column (PsppSheetView       *tree_view,
1615                    PsppSheetViewColumn *column)
1616 {
1617   gint column_offset = 0;
1618   GList *list;
1619   GtkWidget *widget = GTK_WIDGET (tree_view);
1620   gboolean rtl;
1621
1622   if (!gtk_widget_get_realized (widget))
1623     return;
1624
1625   rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
1626   for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns));
1627        list;
1628        list = (rtl ? list->prev : list->next))
1629     {
1630       PsppSheetViewColumn *tmpcolumn = list->data;
1631       if (tmpcolumn == column)
1632         {
1633           GdkRectangle invalid_rect;
1634           
1635           invalid_rect.x = column_offset;
1636           invalid_rect.y = 0;
1637           invalid_rect.width = column->width;
1638           invalid_rect.height = widget->allocation.height;
1639           
1640           gdk_window_invalidate_rect (widget->window, &invalid_rect, TRUE);
1641           break;
1642         }
1643       
1644       column_offset += tmpcolumn->width;
1645     }
1646 }
1647
1648 static void
1649 invalidate_last_column (PsppSheetView *tree_view)
1650 {
1651   GList *last_column;
1652   gboolean rtl;
1653
1654   rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
1655
1656   for (last_column = (rtl ? g_list_first (tree_view->priv->columns) : g_list_last (tree_view->priv->columns));
1657        last_column;
1658        last_column = (rtl ? last_column->next : last_column->prev))
1659     {
1660       if (PSPP_SHEET_VIEW_COLUMN (last_column->data)->visible)
1661         {
1662           invalidate_column (tree_view, last_column->data);
1663           return;
1664         }
1665     }
1666 }
1667
1668 static gint
1669 pspp_sheet_view_get_real_requested_width_from_column (PsppSheetView       *tree_view,
1670                                                     PsppSheetViewColumn *column)
1671 {
1672   gint real_requested_width;
1673
1674   if (column->use_resized_width)
1675     {
1676       real_requested_width = column->resized_width;
1677     }
1678   else
1679     {
1680       real_requested_width = column->fixed_width;
1681     }
1682
1683   if (column->min_width != -1)
1684     real_requested_width = MAX (real_requested_width, column->min_width);
1685   if (column->max_width != -1)
1686     real_requested_width = MIN (real_requested_width, column->max_width);
1687
1688   return real_requested_width;
1689 }
1690
1691 /* GtkWidget::size_allocate helper */
1692 static void
1693 pspp_sheet_view_size_allocate_columns (GtkWidget *widget,
1694                                      gboolean  *width_changed)
1695 {
1696   PsppSheetView *tree_view;
1697   GList *list, *first_column, *last_column;
1698   PsppSheetViewColumn *column;
1699   GtkAllocation allocation;
1700   gint width = 0;
1701   gint extra, extra_per_column, extra_for_last;
1702   gint full_requested_width = 0;
1703   gint number_of_expand_columns = 0;
1704   gboolean column_changed = FALSE;
1705   gboolean rtl;
1706   gboolean update_expand;
1707   
1708   tree_view = PSPP_SHEET_VIEW (widget);
1709
1710   for (last_column = g_list_last (tree_view->priv->columns);
1711        last_column && !(PSPP_SHEET_VIEW_COLUMN (last_column->data)->visible);
1712        last_column = last_column->prev)
1713     ;
1714   if (last_column == NULL)
1715     return;
1716
1717   for (first_column = g_list_first (tree_view->priv->columns);
1718        first_column && !(PSPP_SHEET_VIEW_COLUMN (first_column->data)->visible);
1719        first_column = first_column->next)
1720     ;
1721
1722   allocation.y = 0;
1723   allocation.height = tree_view->priv->header_height;
1724
1725   rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
1726
1727   /* find out how many extra space and expandable columns we have */
1728   for (list = tree_view->priv->columns; list != last_column->next; list = list->next)
1729     {
1730       column = (PsppSheetViewColumn *)list->data;
1731
1732       if (!column->visible)
1733         continue;
1734
1735       full_requested_width += pspp_sheet_view_get_real_requested_width_from_column (tree_view, column);
1736
1737       if (column->expand)
1738         number_of_expand_columns++;
1739     }
1740
1741   /* Only update the expand value if the width of the widget has changed,
1742    * or the number of expand columns has changed, or if there are no expand
1743    * columns, or if we didn't have an size-allocation yet after the
1744    * last validated node.
1745    */
1746   update_expand = (width_changed && *width_changed == TRUE)
1747       || number_of_expand_columns != tree_view->priv->last_number_of_expand_columns
1748       || number_of_expand_columns == 0
1749       || tree_view->priv->post_validation_flag == TRUE;
1750
1751   tree_view->priv->post_validation_flag = FALSE;
1752
1753   if (!update_expand)
1754     {
1755       extra = tree_view->priv->last_extra_space;
1756       extra_for_last = MAX (widget->allocation.width - full_requested_width - extra, 0);
1757     }
1758   else
1759     {
1760       extra = MAX (widget->allocation.width - full_requested_width, 0);
1761       extra_for_last = 0;
1762
1763       tree_view->priv->last_extra_space = extra;
1764     }
1765
1766   if (number_of_expand_columns > 0)
1767     extra_per_column = extra/number_of_expand_columns;
1768   else
1769     extra_per_column = 0;
1770
1771   if (update_expand)
1772     {
1773       tree_view->priv->last_extra_space_per_column = extra_per_column;
1774       tree_view->priv->last_number_of_expand_columns = number_of_expand_columns;
1775     }
1776
1777   for (list = (rtl ? last_column : first_column); 
1778        list != (rtl ? first_column->prev : last_column->next);
1779        list = (rtl ? list->prev : list->next)) 
1780     {
1781       gint real_requested_width = 0;
1782       gint old_width;
1783
1784       column = list->data;
1785       old_width = column->width;
1786
1787       if (!column->visible)
1788         continue;
1789
1790       /* We need to handle the dragged button specially.
1791        */
1792       if (column == tree_view->priv->drag_column)
1793         {
1794           GtkAllocation drag_allocation;
1795           gdk_drawable_get_size (tree_view->priv->drag_window,
1796                                  &(drag_allocation.width),
1797                                  &(drag_allocation.height));
1798           drag_allocation.x = 0;
1799           drag_allocation.y = 0;
1800           gtk_widget_size_allocate (tree_view->priv->drag_column->button,
1801                                     &drag_allocation);
1802           width += drag_allocation.width;
1803           continue;
1804         }
1805
1806       real_requested_width = pspp_sheet_view_get_real_requested_width_from_column (tree_view, column);
1807
1808       allocation.x = width;
1809       column->width = real_requested_width;
1810
1811       if (column->expand)
1812         {
1813           if (number_of_expand_columns == 1)
1814             {
1815               /* We add the remander to the last column as
1816                * */
1817               column->width += extra;
1818             }
1819           else
1820             {
1821               column->width += extra_per_column;
1822               extra -= extra_per_column;
1823               number_of_expand_columns --;
1824             }
1825         }
1826       else if (number_of_expand_columns == 0 &&
1827                list == last_column)
1828         {
1829           column->width += extra;
1830         }
1831
1832       /* In addition to expand, the last column can get even more
1833        * extra space so all available space is filled up.
1834        */
1835       if (extra_for_last > 0 && list == last_column)
1836         column->width += extra_for_last;
1837
1838       g_object_notify (G_OBJECT (column), "width");
1839
1840       allocation.width = column->width;
1841       width += column->width;
1842
1843       if (column->width > old_width)
1844         column_changed = TRUE;
1845
1846       gtk_widget_size_allocate (column->button, &allocation);
1847
1848       if (column->window)
1849         gdk_window_move_resize (column->window,
1850                                 allocation.x + (rtl ? 0 : allocation.width) - TREE_VIEW_DRAG_WIDTH/2,
1851                                 allocation.y,
1852                                 TREE_VIEW_DRAG_WIDTH, allocation.height);
1853     }
1854
1855   /* We change the width here.  The user might have been resizing columns,
1856    * so the total width of the tree view changes.
1857    */
1858   tree_view->priv->width = width;
1859   if (width_changed)
1860     *width_changed = TRUE;
1861
1862   if (column_changed)
1863     gtk_widget_queue_draw (GTK_WIDGET (tree_view));
1864 }
1865
1866
1867 static void
1868 pspp_sheet_view_size_allocate (GtkWidget     *widget,
1869                              GtkAllocation *allocation)
1870 {
1871   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
1872   GList *tmp_list;
1873   gboolean width_changed = FALSE;
1874   gint old_width = widget->allocation.width;
1875
1876   if (allocation->width != widget->allocation.width)
1877     width_changed = TRUE;
1878
1879   widget->allocation = *allocation;
1880
1881   tmp_list = tree_view->priv->children;
1882
1883   while (tmp_list)
1884     {
1885       GtkAllocation allocation;
1886
1887       PsppSheetViewChild *child = tmp_list->data;
1888       tmp_list = tmp_list->next;
1889
1890       /* totally ignore our child's requisition */
1891       allocation.x = child->x;
1892       allocation.y = child->y;
1893       allocation.width = child->width;
1894       allocation.height = child->height;
1895       gtk_widget_size_allocate (child->widget, &allocation);
1896     }
1897
1898   /* We size-allocate the columns first because the width of the
1899    * tree view (used in updating the adjustments below) might change.
1900    */
1901   pspp_sheet_view_size_allocate_columns (widget, &width_changed);
1902
1903   tree_view->priv->hadjustment->page_size = allocation->width;
1904   tree_view->priv->hadjustment->page_increment = allocation->width * 0.9;
1905   tree_view->priv->hadjustment->step_increment = allocation->width * 0.1;
1906   tree_view->priv->hadjustment->lower = 0;
1907   tree_view->priv->hadjustment->upper = MAX (tree_view->priv->hadjustment->page_size, tree_view->priv->width);
1908
1909   if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL)   
1910     {
1911       if (allocation->width < tree_view->priv->width)
1912         {
1913           if (tree_view->priv->init_hadjust_value)
1914             {
1915               tree_view->priv->hadjustment->value = MAX (tree_view->priv->width - allocation->width, 0);
1916               tree_view->priv->init_hadjust_value = FALSE;
1917             }
1918           else if (allocation->width != old_width)
1919             {
1920               tree_view->priv->hadjustment->value = CLAMP (tree_view->priv->hadjustment->value - allocation->width + old_width, 0, tree_view->priv->width - allocation->width);
1921             }
1922           else
1923             tree_view->priv->hadjustment->value = CLAMP (tree_view->priv->width - (tree_view->priv->prev_width - tree_view->priv->hadjustment->value), 0, tree_view->priv->width - allocation->width);
1924         }
1925       else
1926         {
1927           tree_view->priv->hadjustment->value = 0;
1928           tree_view->priv->init_hadjust_value = TRUE;
1929         }
1930     }
1931   else
1932     if (tree_view->priv->hadjustment->value + allocation->width > tree_view->priv->width)
1933       tree_view->priv->hadjustment->value = MAX (tree_view->priv->width - allocation->width, 0);
1934
1935   gtk_adjustment_changed (tree_view->priv->hadjustment);
1936
1937   tree_view->priv->vadjustment->page_size = allocation->height - TREE_VIEW_HEADER_HEIGHT (tree_view);
1938   tree_view->priv->vadjustment->step_increment = tree_view->priv->vadjustment->page_size * 0.1;
1939   tree_view->priv->vadjustment->page_increment = tree_view->priv->vadjustment->page_size * 0.9;
1940   tree_view->priv->vadjustment->lower = 0;
1941   tree_view->priv->vadjustment->upper = MAX (tree_view->priv->vadjustment->page_size, tree_view->priv->height);
1942
1943   gtk_adjustment_changed (tree_view->priv->vadjustment);
1944
1945   /* now the adjustments and window sizes are in sync, we can sync toprow/dy again */
1946   if (tree_view->priv->height <= tree_view->priv->vadjustment->page_size)
1947     gtk_adjustment_set_value (GTK_ADJUSTMENT (tree_view->priv->vadjustment), 0);
1948   else if (tree_view->priv->vadjustment->value + tree_view->priv->vadjustment->page_size > tree_view->priv->height)
1949     gtk_adjustment_set_value (GTK_ADJUSTMENT (tree_view->priv->vadjustment),
1950                               tree_view->priv->height - tree_view->priv->vadjustment->page_size);
1951   else if (gtk_tree_row_reference_valid (tree_view->priv->top_row))
1952     pspp_sheet_view_top_row_to_dy (tree_view);
1953   else
1954     pspp_sheet_view_dy_to_top_row (tree_view);
1955   
1956   if (gtk_widget_get_realized (widget))
1957     {
1958       gdk_window_move_resize (widget->window,
1959                               allocation->x, allocation->y,
1960                               allocation->width, allocation->height);
1961       gdk_window_move_resize (tree_view->priv->header_window,
1962                               - (gint) tree_view->priv->hadjustment->value,
1963                               0,
1964                               MAX (tree_view->priv->width, allocation->width),
1965                               tree_view->priv->header_height);
1966       gdk_window_move_resize (tree_view->priv->bin_window,
1967                               - (gint) tree_view->priv->hadjustment->value,
1968                               TREE_VIEW_HEADER_HEIGHT (tree_view),
1969                               MAX (tree_view->priv->width, allocation->width),
1970                               allocation->height - TREE_VIEW_HEADER_HEIGHT (tree_view));
1971     }
1972
1973   if (tree_view->priv->row_count == 0)
1974     invalidate_empty_focus (tree_view);
1975
1976   if (gtk_widget_get_realized (widget))
1977     {
1978       gboolean has_expand_column = FALSE;
1979       for (tmp_list = tree_view->priv->columns; tmp_list; tmp_list = tmp_list->next)
1980         {
1981           if (pspp_sheet_view_column_get_expand (PSPP_SHEET_VIEW_COLUMN (tmp_list->data)))
1982             {
1983               has_expand_column = TRUE;
1984               break;
1985             }
1986         }
1987
1988       /* This little hack only works if we have an LTR locale, and no column has the  */
1989       if (width_changed)
1990         {
1991           if (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_LTR &&
1992               ! has_expand_column)
1993             invalidate_last_column (tree_view);
1994           else
1995             gtk_widget_queue_draw (widget);
1996         }
1997     }
1998 }
1999
2000 /* Grabs the focus and unsets the PSPP_SHEET_VIEW_DRAW_KEYFOCUS flag */
2001 static void
2002 grab_focus_and_unset_draw_keyfocus (PsppSheetView *tree_view)
2003 {
2004   GtkWidget *widget = GTK_WIDGET (tree_view);
2005
2006   if (gtk_widget_get_can_focus (widget) && !gtk_widget_has_focus (widget))
2007     gtk_widget_grab_focus (widget);
2008   PSPP_SHEET_VIEW_UNSET_FLAG (tree_view, PSPP_SHEET_VIEW_DRAW_KEYFOCUS);
2009 }
2010
2011 gboolean
2012 pspp_sheet_view_node_is_selected (PsppSheetView *tree_view,
2013                                   int node)
2014 {
2015   return node >= 0 && range_tower_contains (tree_view->priv->selected, node);
2016 }
2017
2018 void
2019 pspp_sheet_view_node_select (PsppSheetView *tree_view,
2020                              int node)
2021 {
2022   range_tower_set1 (tree_view->priv->selected, node, 1);
2023 }
2024
2025 void
2026 pspp_sheet_view_node_unselect (PsppSheetView *tree_view,
2027                                int node)
2028 {
2029   range_tower_set0 (tree_view->priv->selected, node, 1);
2030 }
2031
2032 gint
2033 pspp_sheet_view_node_next (PsppSheetView *tree_view,
2034                            gint node)
2035 {
2036   return node + 1 < tree_view->priv->row_count ? node + 1 : -1;
2037 }
2038
2039 gint
2040 pspp_sheet_view_node_prev (PsppSheetView *tree_view,
2041                            gint node)
2042 {
2043   return node > 0 ? node - 1 : -1;
2044 }
2045
2046 static gboolean
2047 pspp_sheet_view_button_press (GtkWidget      *widget,
2048                             GdkEventButton *event)
2049 {
2050   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
2051   GList *list;
2052   PsppSheetViewColumn *column = NULL;
2053   gint i;
2054   GdkRectangle background_area;
2055   GdkRectangle cell_area;
2056   gint vertical_separator;
2057   gint horizontal_separator;
2058   gboolean rtl;
2059
2060   rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
2061   pspp_sheet_view_stop_editing (tree_view, FALSE);
2062   gtk_widget_style_get (widget,
2063                         "vertical-separator", &vertical_separator,
2064                         "horizontal-separator", &horizontal_separator,
2065                         NULL);
2066
2067
2068   /* Because grab_focus can cause reentrancy, we delay grab_focus until after
2069    * we're done handling the button press.
2070    */
2071
2072   if (event->window == tree_view->priv->bin_window)
2073     {
2074       int node;
2075       GtkTreePath *path;
2076       gchar *path_string;
2077       gint depth;
2078       gint new_y;
2079       gint y_offset;
2080       gint dval;
2081       gint pre_val, aft_val;
2082       PsppSheetViewColumn *column = NULL;
2083       GtkCellRenderer *focus_cell = NULL;
2084       gint column_handled_click = FALSE;
2085       gboolean row_double_click = FALSE;
2086       gboolean rtl;
2087       gboolean node_selected;
2088
2089       /* Empty tree? */
2090       if (tree_view->priv->row_count == 0)
2091         {
2092           grab_focus_and_unset_draw_keyfocus (tree_view);
2093           return TRUE;
2094         }
2095
2096       /* find the node that was clicked */
2097       new_y = TREE_WINDOW_Y_TO_RBTREE_Y(tree_view, event->y);
2098       if (new_y < 0)
2099         new_y = 0;
2100       y_offset = -pspp_sheet_view_find_offset (tree_view, new_y, &node);
2101
2102       if (node < 0)
2103         {
2104           /* We clicked in dead space */
2105           grab_focus_and_unset_draw_keyfocus (tree_view);
2106           return TRUE;
2107         }
2108
2109       /* Get the path and the node */
2110       path = _pspp_sheet_view_find_path (tree_view, node);
2111
2112       depth = gtk_tree_path_get_depth (path);
2113       background_area.y = y_offset + event->y;
2114       background_area.height = ROW_HEIGHT (tree_view);
2115       background_area.x = 0;
2116
2117
2118       /* Let the column have a chance at selecting it. */
2119       rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
2120       for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns));
2121            list; list = (rtl ? list->prev : list->next))
2122         {
2123           PsppSheetViewColumn *candidate = list->data;
2124
2125           if (!candidate->visible)
2126             continue;
2127
2128           background_area.width = candidate->width;
2129           if ((background_area.x > (gint) event->x) ||
2130               (background_area.x + background_area.width <= (gint) event->x))
2131             {
2132               background_area.x += background_area.width;
2133               continue;
2134             }
2135
2136           /* we found the focus column */
2137           column = candidate;
2138           cell_area = background_area;
2139           cell_area.width -= horizontal_separator;
2140           cell_area.height -= vertical_separator;
2141           cell_area.x += horizontal_separator/2;
2142           cell_area.y += vertical_separator/2;
2143           break;
2144         }
2145
2146       if (column == NULL)
2147         {
2148           gtk_tree_path_free (path);
2149           grab_focus_and_unset_draw_keyfocus (tree_view);
2150           return FALSE;
2151         }
2152
2153       tree_view->priv->focus_column = column;
2154
2155       /* decide if we edit */
2156       if (event->type == GDK_BUTTON_PRESS && event->button == 1 &&
2157           !(event->state & gtk_accelerator_get_default_mod_mask ()))
2158         {
2159           GtkTreePath *anchor;
2160           GtkTreeIter iter;
2161
2162           gtk_tree_model_get_iter (tree_view->priv->model, &iter, path);
2163           pspp_sheet_view_column_cell_set_cell_data (column,
2164                                                    tree_view->priv->model,
2165                                                    &iter);
2166
2167           if (tree_view->priv->anchor)
2168             anchor = gtk_tree_row_reference_get_path (tree_view->priv->anchor);
2169           else
2170             anchor = NULL;
2171
2172           if ((anchor && !gtk_tree_path_compare (anchor, path))
2173               || !_pspp_sheet_view_column_has_editable_cell (column))
2174             {
2175               GtkCellEditable *cell_editable = NULL;
2176
2177               /* FIXME: get the right flags */
2178               guint flags = 0;
2179
2180               path_string = gtk_tree_path_to_string (path);
2181
2182               if (_pspp_sheet_view_column_cell_event (column,
2183                                                     &cell_editable,
2184                                                     (GdkEvent *)event,
2185                                                     path_string,
2186                                                     &background_area,
2187                                                     &cell_area, flags))
2188                 {
2189                   if (cell_editable != NULL)
2190                     {
2191                       gint left, right;
2192                       GdkRectangle area;
2193
2194                       area = cell_area;
2195                       _pspp_sheet_view_column_get_neighbor_sizes (column,       _pspp_sheet_view_column_get_edited_cell (column), &left, &right);
2196
2197                       area.x += left;
2198                       area.width -= right + left;
2199
2200                       pspp_sheet_view_real_start_editing (tree_view,
2201                                                         column,
2202                                                         path,
2203                                                         cell_editable,
2204                                                         &area,
2205                                                         (GdkEvent *)event,
2206                                                         flags);
2207                       g_free (path_string);
2208                       gtk_tree_path_free (path);
2209                       gtk_tree_path_free (anchor);
2210                       return TRUE;
2211                     }
2212                   column_handled_click = TRUE;
2213                 }
2214               g_free (path_string);
2215             }
2216           if (anchor)
2217             gtk_tree_path_free (anchor);
2218         }
2219
2220       /* select */
2221       node_selected = pspp_sheet_view_node_is_selected (tree_view, node);
2222       pre_val = tree_view->priv->vadjustment->value;
2223
2224       /* we only handle selection modifications on the first button press
2225        */
2226       if (event->type == GDK_BUTTON_PRESS)
2227         {
2228           if ((event->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
2229             tree_view->priv->ctrl_pressed = TRUE;
2230           if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK)
2231             tree_view->priv->shift_pressed = TRUE;
2232
2233           focus_cell = _pspp_sheet_view_column_get_cell_at_pos (column, event->x - background_area.x);
2234           if (focus_cell)
2235             pspp_sheet_view_column_focus_cell (column, focus_cell);
2236
2237           if (event->state & GDK_CONTROL_MASK)
2238             {
2239               pspp_sheet_view_real_set_cursor (tree_view, path, FALSE, TRUE);
2240               pspp_sheet_view_real_toggle_cursor_row (tree_view);
2241             }
2242           else if (event->state & GDK_SHIFT_MASK)
2243             {
2244               pspp_sheet_view_real_set_cursor (tree_view, path, FALSE, TRUE);
2245               pspp_sheet_view_real_select_cursor_row (tree_view, FALSE);
2246             }
2247           else
2248             {
2249               pspp_sheet_view_real_set_cursor (tree_view, path, TRUE, TRUE);
2250             }
2251
2252           tree_view->priv->ctrl_pressed = FALSE;
2253           tree_view->priv->shift_pressed = FALSE;
2254         }
2255
2256       /* the treeview may have been scrolled because of _set_cursor,
2257        * correct here
2258        */
2259
2260       aft_val = tree_view->priv->vadjustment->value;
2261       dval = pre_val - aft_val;
2262
2263       cell_area.y += dval;
2264       background_area.y += dval;
2265
2266       /* Save press to possibly begin a drag
2267        */
2268       if (!column_handled_click &&
2269           !tree_view->priv->in_grab &&
2270           tree_view->priv->pressed_button < 0)
2271         {
2272           tree_view->priv->pressed_button = event->button;
2273           tree_view->priv->press_start_x = event->x;
2274           tree_view->priv->press_start_y = event->y;
2275
2276           if (tree_view->priv->rubber_banding_enable
2277               && !node_selected
2278               && tree_view->priv->selection->type == GTK_SELECTION_MULTIPLE)
2279             {
2280               tree_view->priv->press_start_y += tree_view->priv->dy;
2281               tree_view->priv->rubber_band_x = event->x;
2282               tree_view->priv->rubber_band_y = event->y + tree_view->priv->dy;
2283               tree_view->priv->rubber_band_status = RUBBER_BAND_MAYBE_START;
2284
2285               if ((event->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
2286                 tree_view->priv->rubber_band_ctrl = TRUE;
2287               if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK)
2288                 tree_view->priv->rubber_band_shift = TRUE;
2289             }
2290         }
2291
2292       /* Test if a double click happened on the same row. */
2293       if (event->button == 1 && event->type == GDK_BUTTON_PRESS)
2294         {
2295           int double_click_time, double_click_distance;
2296
2297           g_object_get (gtk_settings_get_for_screen (
2298                           gtk_widget_get_screen (widget)),
2299                         "gtk-double-click-time", &double_click_time,
2300                         "gtk-double-click-distance", &double_click_distance,
2301                         NULL);
2302
2303           /* Same conditions as _gdk_event_button_generate */
2304           if (tree_view->priv->last_button_x != -1 &&
2305               (event->time < tree_view->priv->last_button_time + double_click_time) &&
2306               (ABS (event->x - tree_view->priv->last_button_x) <= double_click_distance) &&
2307               (ABS (event->y - tree_view->priv->last_button_y) <= double_click_distance))
2308             {
2309               /* We do no longer compare paths of this row and the
2310                * row clicked previously.  We use the double click
2311                * distance to decide whether this is a valid click,
2312                * allowing the mouse to slightly move over another row.
2313                */
2314               row_double_click = TRUE;
2315
2316               tree_view->priv->last_button_time = 0;
2317               tree_view->priv->last_button_x = -1;
2318               tree_view->priv->last_button_y = -1;
2319             }
2320           else
2321             {
2322               tree_view->priv->last_button_time = event->time;
2323               tree_view->priv->last_button_x = event->x;
2324               tree_view->priv->last_button_y = event->y;
2325             }
2326         }
2327
2328       if (row_double_click)
2329         {
2330           gtk_grab_remove (widget);
2331           pspp_sheet_view_row_activated (tree_view, path, column);
2332
2333           if (tree_view->priv->pressed_button == event->button)
2334             tree_view->priv->pressed_button = -1;
2335         }
2336
2337       gtk_tree_path_free (path);
2338
2339       /* If we activated the row through a double click we don't want to grab
2340        * focus back, as moving focus to another widget is pretty common.
2341        */
2342       if (!row_double_click)
2343         grab_focus_and_unset_draw_keyfocus (tree_view);
2344
2345       return TRUE;
2346     }
2347
2348   /* We didn't click in the window.  Let's check to see if we clicked on a column resize window.
2349    */
2350   for (i = 0, list = tree_view->priv->columns; list; list = list->next, i++)
2351     {
2352       column = list->data;
2353       if (event->window == column->window &&
2354           column->resizable &&
2355           column->window)
2356         {
2357           gpointer drag_data;
2358
2359           if (gdk_pointer_grab (column->window, FALSE,
2360                                 GDK_POINTER_MOTION_HINT_MASK |
2361                                 GDK_BUTTON1_MOTION_MASK |
2362                                 GDK_BUTTON_RELEASE_MASK,
2363                                 NULL, NULL, event->time))
2364             return FALSE;
2365
2366           gtk_grab_add (widget);
2367           PSPP_SHEET_VIEW_SET_FLAG (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_RESIZE);
2368           column->resized_width = column->width - tree_view->priv->last_extra_space_per_column;
2369
2370           /* block attached dnd signal handler */
2371           drag_data = g_object_get_data (G_OBJECT (widget), "gtk-site-data");
2372           if (drag_data)
2373             g_signal_handlers_block_matched (widget,
2374                                              G_SIGNAL_MATCH_DATA,
2375                                              0, 0, NULL, NULL,
2376                                              drag_data);
2377
2378           tree_view->priv->drag_pos = i;
2379           tree_view->priv->x_drag = column->button->allocation.x + (rtl ? 0 : column->button->allocation.width);
2380
2381           if (!gtk_widget_has_focus (widget))
2382             gtk_widget_grab_focus (widget);
2383
2384           return TRUE;
2385         }
2386     }
2387   return FALSE;
2388 }
2389
2390 /* GtkWidget::button_release_event helper */
2391 static gboolean
2392 pspp_sheet_view_button_release_drag_column (GtkWidget      *widget,
2393                                           GdkEventButton *event)
2394 {
2395   PsppSheetView *tree_view;
2396   GList *l;
2397   gboolean rtl;
2398
2399   tree_view = PSPP_SHEET_VIEW (widget);
2400
2401   rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
2402   gdk_display_pointer_ungrab (gtk_widget_get_display (widget), GDK_CURRENT_TIME);
2403   gdk_display_keyboard_ungrab (gtk_widget_get_display (widget), GDK_CURRENT_TIME);
2404
2405   /* Move the button back */
2406   g_object_ref (tree_view->priv->drag_column->button);
2407   gtk_container_remove (GTK_CONTAINER (tree_view), tree_view->priv->drag_column->button);
2408   gtk_widget_set_parent_window (tree_view->priv->drag_column->button, tree_view->priv->header_window);
2409   gtk_widget_set_parent (tree_view->priv->drag_column->button, GTK_WIDGET (tree_view));
2410   g_object_unref (tree_view->priv->drag_column->button);
2411   gtk_widget_queue_resize (widget);
2412   if (tree_view->priv->drag_column->resizable)
2413     {
2414       gdk_window_raise (tree_view->priv->drag_column->window);
2415       gdk_window_show (tree_view->priv->drag_column->window);
2416     }
2417   else
2418     gdk_window_hide (tree_view->priv->drag_column->window);
2419
2420   gtk_widget_grab_focus (tree_view->priv->drag_column->button);
2421
2422   if (rtl)
2423     {
2424       if (tree_view->priv->cur_reorder &&
2425           tree_view->priv->cur_reorder->right_column != tree_view->priv->drag_column)
2426         pspp_sheet_view_move_column_after (tree_view, tree_view->priv->drag_column,
2427                                          tree_view->priv->cur_reorder->right_column);
2428     }
2429   else
2430     {
2431       if (tree_view->priv->cur_reorder &&
2432           tree_view->priv->cur_reorder->left_column != tree_view->priv->drag_column)
2433         pspp_sheet_view_move_column_after (tree_view, tree_view->priv->drag_column,
2434                                          tree_view->priv->cur_reorder->left_column);
2435     }
2436   tree_view->priv->drag_column = NULL;
2437   gdk_window_hide (tree_view->priv->drag_window);
2438
2439   for (l = tree_view->priv->column_drag_info; l != NULL; l = l->next)
2440     g_slice_free (PsppSheetViewColumnReorder, l->data);
2441   g_list_free (tree_view->priv->column_drag_info);
2442   tree_view->priv->column_drag_info = NULL;
2443   tree_view->priv->cur_reorder = NULL;
2444
2445   if (tree_view->priv->drag_highlight_window)
2446     gdk_window_hide (tree_view->priv->drag_highlight_window);
2447
2448   /* Reset our flags */
2449   tree_view->priv->drag_column_window_state = DRAG_COLUMN_WINDOW_STATE_UNSET;
2450   PSPP_SHEET_VIEW_UNSET_FLAG (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_DRAG);
2451
2452   return TRUE;
2453 }
2454
2455 /* GtkWidget::button_release_event helper */
2456 static gboolean
2457 pspp_sheet_view_button_release_column_resize (GtkWidget      *widget,
2458                                             GdkEventButton *event)
2459 {
2460   PsppSheetView *tree_view;
2461   gpointer drag_data;
2462
2463   tree_view = PSPP_SHEET_VIEW (widget);
2464
2465   tree_view->priv->drag_pos = -1;
2466
2467   /* unblock attached dnd signal handler */
2468   drag_data = g_object_get_data (G_OBJECT (widget), "gtk-site-data");
2469   if (drag_data)
2470     g_signal_handlers_unblock_matched (widget,
2471                                        G_SIGNAL_MATCH_DATA,
2472                                        0, 0, NULL, NULL,
2473                                        drag_data);
2474
2475   PSPP_SHEET_VIEW_UNSET_FLAG (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_RESIZE);
2476   gtk_grab_remove (widget);
2477   gdk_display_pointer_ungrab (gdk_drawable_get_display (event->window),
2478                               event->time);
2479   return TRUE;
2480 }
2481
2482 static gboolean
2483 pspp_sheet_view_button_release (GtkWidget      *widget,
2484                               GdkEventButton *event)
2485 {
2486   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
2487
2488   if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_DRAG))
2489     return pspp_sheet_view_button_release_drag_column (widget, event);
2490
2491   if (tree_view->priv->rubber_band_status)
2492     pspp_sheet_view_stop_rubber_band (tree_view);
2493
2494   if (tree_view->priv->pressed_button == event->button)
2495     tree_view->priv->pressed_button = -1;
2496
2497   if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_RESIZE))
2498     return pspp_sheet_view_button_release_column_resize (widget, event);
2499
2500   return FALSE;
2501 }
2502
2503 static gboolean
2504 pspp_sheet_view_grab_broken (GtkWidget          *widget,
2505                            GdkEventGrabBroken *event)
2506 {
2507   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
2508
2509   if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_DRAG))
2510     pspp_sheet_view_button_release_drag_column (widget, (GdkEventButton *)event);
2511
2512   if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_RESIZE))
2513     pspp_sheet_view_button_release_column_resize (widget, (GdkEventButton *)event);
2514
2515   return TRUE;
2516 }
2517
2518 #if 0
2519 static gboolean
2520 pspp_sheet_view_configure (GtkWidget *widget,
2521                          GdkEventConfigure *event)
2522 {
2523   PsppSheetView *tree_view;
2524
2525   tree_view = PSPP_SHEET_VIEW (widget);
2526   tree_view->priv->search_position_func (tree_view, tree_view->priv->search_window);
2527
2528   return FALSE;
2529 }
2530 #endif
2531
2532 /* GtkWidget::motion_event function set.
2533  */
2534
2535 static void
2536 do_prelight (PsppSheetView *tree_view,
2537              int node,
2538              /* these are in bin_window coords */
2539              gint         x,
2540              gint         y)
2541 {
2542   int prev_node = tree_view->priv->prelight_node;
2543
2544   if (prev_node != node)
2545     {
2546       tree_view->priv->prelight_node = node;
2547
2548       if (prev_node >= 0)
2549         _pspp_sheet_view_queue_draw_node (tree_view, prev_node, NULL);
2550
2551       if (node >= 0)
2552         _pspp_sheet_view_queue_draw_node (tree_view, node, NULL);
2553     }
2554 }
2555
2556 static void
2557 prelight_or_select (PsppSheetView *tree_view,
2558                     int node,
2559                     /* these are in bin_window coords */
2560                     gint         x,
2561                     gint         y)
2562 {
2563   GtkSelectionMode mode = pspp_sheet_selection_get_mode (tree_view->priv->selection);
2564   
2565   if (tree_view->priv->hover_selection &&
2566       (mode == GTK_SELECTION_SINGLE || mode == GTK_SELECTION_BROWSE) &&
2567       !(tree_view->priv->edited_column &&
2568         tree_view->priv->edited_column->editable_widget))
2569     {
2570       if (node >= 0)
2571         {
2572           if (!pspp_sheet_view_node_is_selected (tree_view, node))
2573             {
2574               GtkTreePath *path;
2575               
2576               path = _pspp_sheet_view_find_path (tree_view, node);
2577               pspp_sheet_selection_select_path (tree_view->priv->selection, path);
2578               if (pspp_sheet_view_node_is_selected (tree_view, node))
2579                 {
2580                   PSPP_SHEET_VIEW_UNSET_FLAG (tree_view, PSPP_SHEET_VIEW_DRAW_KEYFOCUS);
2581                   pspp_sheet_view_real_set_cursor (tree_view, path, FALSE, FALSE);
2582                 }
2583               gtk_tree_path_free (path);
2584             }
2585         }
2586
2587       else if (mode == GTK_SELECTION_SINGLE)
2588         pspp_sheet_selection_unselect_all (tree_view->priv->selection);
2589     }
2590
2591     do_prelight (tree_view, node, x, y);
2592 }
2593
2594 static void
2595 ensure_unprelighted (PsppSheetView *tree_view)
2596 {
2597   do_prelight (tree_view,
2598                -1,
2599                -1000, -1000); /* coords not possibly over an arrow */
2600
2601   g_assert (tree_view->priv->prelight_node < 0);
2602 }
2603
2604 static void
2605 update_prelight (PsppSheetView *tree_view,
2606                  gint         x,
2607                  gint         y)
2608 {
2609   int new_y;
2610   int node;
2611
2612   if (tree_view->priv->row_count == 0)
2613     return;
2614
2615   if (x == -10000)
2616     {
2617       ensure_unprelighted (tree_view);
2618       return;
2619     }
2620
2621   new_y = TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, y);
2622   if (new_y < 0)
2623     new_y = 0;
2624
2625   pspp_sheet_view_find_offset (tree_view, new_y, &node);
2626
2627   if (node >= 0)
2628     prelight_or_select (tree_view, node, x, y);
2629 }
2630
2631
2632
2633
2634 /* Our motion arrow is either a box (in the case of the original spot)
2635  * or an arrow.  It is expander_size wide.
2636  */
2637 /*
2638  * 11111111111111
2639  * 01111111111110
2640  * 00111111111100
2641  * 00011111111000
2642  * 00001111110000
2643  * 00000111100000
2644  * 00000111100000
2645  * 00000111100000
2646  * ~ ~ ~ ~ ~ ~ ~
2647  * 00000111100000
2648  * 00000111100000
2649  * 00000111100000
2650  * 00001111110000
2651  * 00011111111000
2652  * 00111111111100
2653  * 01111111111110
2654  * 11111111111111
2655  */
2656
2657 static void
2658 pspp_sheet_view_motion_draw_column_motion_arrow (PsppSheetView *tree_view)
2659 {
2660   PsppSheetViewColumnReorder *reorder = tree_view->priv->cur_reorder;
2661   GtkWidget *widget = GTK_WIDGET (tree_view);
2662   GdkBitmap *mask = NULL;
2663   gint x;
2664   gint y;
2665   gint width;
2666   gint height;
2667   gint arrow_type = DRAG_COLUMN_WINDOW_STATE_UNSET;
2668   GdkWindowAttr attributes;
2669   guint attributes_mask;
2670
2671   if (!reorder ||
2672       reorder->left_column == tree_view->priv->drag_column ||
2673       reorder->right_column == tree_view->priv->drag_column)
2674     arrow_type = DRAG_COLUMN_WINDOW_STATE_ORIGINAL;
2675   else if (reorder->left_column || reorder->right_column)
2676     {
2677       GdkRectangle visible_rect;
2678       pspp_sheet_view_get_visible_rect (tree_view, &visible_rect);
2679       if (reorder->left_column)
2680         x = reorder->left_column->button->allocation.x + reorder->left_column->button->allocation.width;
2681       else
2682         x = reorder->right_column->button->allocation.x;
2683
2684       if (x < visible_rect.x)
2685         arrow_type = DRAG_COLUMN_WINDOW_STATE_ARROW_LEFT;
2686       else if (x > visible_rect.x + visible_rect.width)
2687         arrow_type = DRAG_COLUMN_WINDOW_STATE_ARROW_RIGHT;
2688       else
2689         arrow_type = DRAG_COLUMN_WINDOW_STATE_ARROW;
2690     }
2691
2692   /* We want to draw the rectangle over the initial location. */
2693   if (arrow_type == DRAG_COLUMN_WINDOW_STATE_ORIGINAL)
2694     {
2695       GdkGC *gc;
2696       GdkColor col;
2697
2698       if (tree_view->priv->drag_column_window_state != DRAG_COLUMN_WINDOW_STATE_ORIGINAL)
2699         {
2700           if (tree_view->priv->drag_highlight_window)
2701             {
2702               gdk_window_set_user_data (tree_view->priv->drag_highlight_window,
2703                                         NULL);
2704               gdk_window_destroy (tree_view->priv->drag_highlight_window);
2705             }
2706
2707           attributes.window_type = GDK_WINDOW_CHILD;
2708           attributes.wclass = GDK_INPUT_OUTPUT;
2709           attributes.x = tree_view->priv->drag_column_x;
2710           attributes.y = 0;
2711           width = attributes.width = tree_view->priv->drag_column->button->allocation.width;
2712           height = attributes.height = tree_view->priv->drag_column->button->allocation.height;
2713           attributes.visual = gtk_widget_get_visual (GTK_WIDGET (tree_view));
2714           attributes.colormap = gtk_widget_get_colormap (GTK_WIDGET (tree_view));
2715           attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK;
2716           attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
2717           tree_view->priv->drag_highlight_window = gdk_window_new (tree_view->priv->header_window, &attributes, attributes_mask);
2718           gdk_window_set_user_data (tree_view->priv->drag_highlight_window, GTK_WIDGET (tree_view));
2719
2720           mask = gdk_pixmap_new (tree_view->priv->drag_highlight_window, width, height, 1);
2721           gc = gdk_gc_new (mask);
2722           col.pixel = 1;
2723           gdk_gc_set_foreground (gc, &col);
2724           gdk_draw_rectangle (mask, gc, TRUE, 0, 0, width, height);
2725           col.pixel = 0;
2726           gdk_gc_set_foreground(gc, &col);
2727           gdk_draw_rectangle (mask, gc, TRUE, 2, 2, width - 4, height - 4);
2728           g_object_unref (gc);
2729
2730           gdk_window_shape_combine_mask (tree_view->priv->drag_highlight_window,
2731                                          mask, 0, 0);
2732           if (mask) g_object_unref (mask);
2733           tree_view->priv->drag_column_window_state = DRAG_COLUMN_WINDOW_STATE_ORIGINAL;
2734         }
2735     }
2736   else if (arrow_type == DRAG_COLUMN_WINDOW_STATE_ARROW)
2737     {
2738       gint i, j = 1;
2739       GdkGC *gc;
2740       GdkColor col;
2741
2742       width = tree_view->priv->expander_size;
2743
2744       /* Get x, y, width, height of arrow */
2745       gdk_window_get_origin (tree_view->priv->header_window, &x, &y);
2746       if (reorder->left_column)
2747         {
2748           x += reorder->left_column->button->allocation.x + reorder->left_column->button->allocation.width - width/2;
2749           height = reorder->left_column->button->allocation.height;
2750         }
2751       else
2752         {
2753           x += reorder->right_column->button->allocation.x - width/2;
2754           height = reorder->right_column->button->allocation.height;
2755         }
2756       y -= tree_view->priv->expander_size/2; /* The arrow takes up only half the space */
2757       height += tree_view->priv->expander_size;
2758
2759       /* Create the new window */
2760       if (tree_view->priv->drag_column_window_state != DRAG_COLUMN_WINDOW_STATE_ARROW)
2761         {
2762           if (tree_view->priv->drag_highlight_window)
2763             {
2764               gdk_window_set_user_data (tree_view->priv->drag_highlight_window,
2765                                         NULL);
2766               gdk_window_destroy (tree_view->priv->drag_highlight_window);
2767             }
2768
2769           attributes.window_type = GDK_WINDOW_TEMP;
2770           attributes.wclass = GDK_INPUT_OUTPUT;
2771           attributes.visual = gtk_widget_get_visual (GTK_WIDGET (tree_view));
2772           attributes.colormap = gtk_widget_get_colormap (GTK_WIDGET (tree_view));
2773           attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK;
2774           attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
2775           attributes.x = x;
2776           attributes.y = y;
2777           attributes.width = width;
2778           attributes.height = height;
2779           tree_view->priv->drag_highlight_window = gdk_window_new (gtk_widget_get_root_window (widget),
2780                                                                    &attributes, attributes_mask);
2781           gdk_window_set_user_data (tree_view->priv->drag_highlight_window, GTK_WIDGET (tree_view));
2782
2783           mask = gdk_pixmap_new (tree_view->priv->drag_highlight_window, width, height, 1);
2784           gc = gdk_gc_new (mask);
2785           col.pixel = 1;
2786           gdk_gc_set_foreground (gc, &col);
2787           gdk_draw_rectangle (mask, gc, TRUE, 0, 0, width, height);
2788
2789           /* Draw the 2 arrows as per above */
2790           col.pixel = 0;
2791           gdk_gc_set_foreground (gc, &col);
2792           for (i = 0; i < width; i ++)
2793             {
2794               if (i == (width/2 - 1))
2795                 continue;
2796               gdk_draw_line (mask, gc, i, j, i, height - j);
2797               if (i < (width/2 - 1))
2798                 j++;
2799               else
2800                 j--;
2801             }
2802           g_object_unref (gc);
2803           gdk_window_shape_combine_mask (tree_view->priv->drag_highlight_window,
2804                                          mask, 0, 0);
2805           if (mask) g_object_unref (mask);
2806         }
2807
2808       tree_view->priv->drag_column_window_state = DRAG_COLUMN_WINDOW_STATE_ARROW;
2809       gdk_window_move (tree_view->priv->drag_highlight_window, x, y);
2810     }
2811   else if (arrow_type == DRAG_COLUMN_WINDOW_STATE_ARROW_LEFT ||
2812            arrow_type == DRAG_COLUMN_WINDOW_STATE_ARROW_RIGHT)
2813     {
2814       gint i, j = 1;
2815       GdkGC *gc;
2816       GdkColor col;
2817
2818       width = tree_view->priv->expander_size;
2819
2820       /* Get x, y, width, height of arrow */
2821       width = width/2; /* remember, the arrow only takes half the available width */
2822       gdk_window_get_origin (widget->window, &x, &y);
2823       if (arrow_type == DRAG_COLUMN_WINDOW_STATE_ARROW_RIGHT)
2824         x += widget->allocation.width - width;
2825
2826       if (reorder->left_column)
2827         height = reorder->left_column->button->allocation.height;
2828       else
2829         height = reorder->right_column->button->allocation.height;
2830
2831       y -= tree_view->priv->expander_size;
2832       height += 2*tree_view->priv->expander_size;
2833
2834       /* Create the new window */
2835       if (tree_view->priv->drag_column_window_state != DRAG_COLUMN_WINDOW_STATE_ARROW_LEFT &&
2836           tree_view->priv->drag_column_window_state != DRAG_COLUMN_WINDOW_STATE_ARROW_RIGHT)
2837         {
2838           if (tree_view->priv->drag_highlight_window)
2839             {
2840               gdk_window_set_user_data (tree_view->priv->drag_highlight_window,
2841                                         NULL);
2842               gdk_window_destroy (tree_view->priv->drag_highlight_window);
2843             }
2844
2845           attributes.window_type = GDK_WINDOW_TEMP;
2846           attributes.wclass = GDK_INPUT_OUTPUT;
2847           attributes.visual = gtk_widget_get_visual (GTK_WIDGET (tree_view));
2848           attributes.colormap = gtk_widget_get_colormap (GTK_WIDGET (tree_view));
2849           attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK;
2850           attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
2851           attributes.x = x;
2852           attributes.y = y;
2853           attributes.width = width;
2854           attributes.height = height;
2855           tree_view->priv->drag_highlight_window = gdk_window_new (NULL, &attributes, attributes_mask);
2856           gdk_window_set_user_data (tree_view->priv->drag_highlight_window, GTK_WIDGET (tree_view));
2857
2858           mask = gdk_pixmap_new (tree_view->priv->drag_highlight_window, width, height, 1);
2859           gc = gdk_gc_new (mask);
2860           col.pixel = 1;
2861           gdk_gc_set_foreground (gc, &col);
2862           gdk_draw_rectangle (mask, gc, TRUE, 0, 0, width, height);
2863
2864           /* Draw the 2 arrows as per above */
2865           col.pixel = 0;
2866           gdk_gc_set_foreground (gc, &col);
2867           j = tree_view->priv->expander_size;
2868           for (i = 0; i < width; i ++)
2869             {
2870               gint k;
2871               if (arrow_type == DRAG_COLUMN_WINDOW_STATE_ARROW_LEFT)
2872                 k = width - i - 1;
2873               else
2874                 k = i;
2875               gdk_draw_line (mask, gc, k, j, k, height - j);
2876               gdk_draw_line (mask, gc, k, 0, k, tree_view->priv->expander_size - j);
2877               gdk_draw_line (mask, gc, k, height, k, height - tree_view->priv->expander_size + j);
2878               j--;
2879             }
2880           g_object_unref (gc);
2881           gdk_window_shape_combine_mask (tree_view->priv->drag_highlight_window,
2882                                          mask, 0, 0);
2883           if (mask) g_object_unref (mask);
2884         }
2885
2886       tree_view->priv->drag_column_window_state = arrow_type;
2887       gdk_window_move (tree_view->priv->drag_highlight_window, x, y);
2888    }
2889   else
2890     {
2891       g_warning (G_STRLOC"Invalid PsppSheetViewColumnReorder struct");
2892       gdk_window_hide (tree_view->priv->drag_highlight_window);
2893       return;
2894     }
2895
2896   gdk_window_show (tree_view->priv->drag_highlight_window);
2897   gdk_window_raise (tree_view->priv->drag_highlight_window);
2898 }
2899
2900 static gboolean
2901 pspp_sheet_view_motion_resize_column (GtkWidget      *widget,
2902                                     GdkEventMotion *event)
2903 {
2904   gint x;
2905   gint new_width;
2906   PsppSheetViewColumn *column;
2907   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
2908
2909   column = pspp_sheet_view_get_column (tree_view, tree_view->priv->drag_pos);
2910
2911   if (event->is_hint || event->window != widget->window)
2912     gtk_widget_get_pointer (widget, &x, NULL);
2913   else
2914     x = event->x;
2915
2916   if (tree_view->priv->hadjustment)
2917     x += tree_view->priv->hadjustment->value;
2918
2919   new_width = pspp_sheet_view_new_column_width (tree_view,
2920                                               tree_view->priv->drag_pos, &x);
2921   if (x != tree_view->priv->x_drag &&
2922       (new_width != column->fixed_width))
2923     {
2924       column->use_resized_width = TRUE;
2925       column->resized_width = new_width;
2926       if (column->expand)
2927         column->resized_width -= tree_view->priv->last_extra_space_per_column;
2928       gtk_widget_queue_resize (widget);
2929     }
2930
2931   return FALSE;
2932 }
2933
2934
2935 static void
2936 pspp_sheet_view_update_current_reorder (PsppSheetView *tree_view)
2937 {
2938   PsppSheetViewColumnReorder *reorder = NULL;
2939   GList *list;
2940   gint mouse_x;
2941
2942   gdk_window_get_pointer (tree_view->priv->header_window, &mouse_x, NULL, NULL);
2943   for (list = tree_view->priv->column_drag_info; list; list = list->next)
2944     {
2945       reorder = (PsppSheetViewColumnReorder *) list->data;
2946       if (mouse_x >= reorder->left_align && mouse_x < reorder->right_align)
2947         break;
2948       reorder = NULL;
2949     }
2950
2951   /*  if (reorder && reorder == tree_view->priv->cur_reorder)
2952       return;*/
2953
2954   tree_view->priv->cur_reorder = reorder;
2955   pspp_sheet_view_motion_draw_column_motion_arrow (tree_view);
2956 }
2957
2958 static void
2959 pspp_sheet_view_vertical_autoscroll (PsppSheetView *tree_view)
2960 {
2961   GdkRectangle visible_rect;
2962   gint y;
2963   gint offset;
2964   gfloat value;
2965
2966   gdk_window_get_pointer (tree_view->priv->bin_window, NULL, &y, NULL);
2967   y += tree_view->priv->dy;
2968
2969   pspp_sheet_view_get_visible_rect (tree_view, &visible_rect);
2970
2971   /* see if we are near the edge. */
2972   offset = y - (visible_rect.y + 2 * SCROLL_EDGE_SIZE);
2973   if (offset > 0)
2974     {
2975       offset = y - (visible_rect.y + visible_rect.height - 2 * SCROLL_EDGE_SIZE);
2976       if (offset < 0)
2977         return;
2978     }
2979
2980   value = CLAMP (tree_view->priv->vadjustment->value + offset, 0.0,
2981                  tree_view->priv->vadjustment->upper - tree_view->priv->vadjustment->page_size);
2982   gtk_adjustment_set_value (tree_view->priv->vadjustment, value);
2983 }
2984
2985 static gboolean
2986 pspp_sheet_view_horizontal_autoscroll (PsppSheetView *tree_view)
2987 {
2988   GdkRectangle visible_rect;
2989   gint x;
2990   gint offset;
2991   gfloat value;
2992
2993   gdk_window_get_pointer (tree_view->priv->bin_window, &x, NULL, NULL);
2994
2995   pspp_sheet_view_get_visible_rect (tree_view, &visible_rect);
2996
2997   /* See if we are near the edge. */
2998   offset = x - (visible_rect.x + SCROLL_EDGE_SIZE);
2999   if (offset > 0)
3000     {
3001       offset = x - (visible_rect.x + visible_rect.width - SCROLL_EDGE_SIZE);
3002       if (offset < 0)
3003         return TRUE;
3004     }
3005   offset = offset/3;
3006
3007   value = CLAMP (tree_view->priv->hadjustment->value + offset,
3008                  0.0, tree_view->priv->hadjustment->upper - tree_view->priv->hadjustment->page_size);
3009   gtk_adjustment_set_value (tree_view->priv->hadjustment, value);
3010
3011   return TRUE;
3012
3013 }
3014
3015 static gboolean
3016 pspp_sheet_view_motion_drag_column (GtkWidget      *widget,
3017                                   GdkEventMotion *event)
3018 {
3019   PsppSheetView *tree_view = (PsppSheetView *) widget;
3020   PsppSheetViewColumn *column = tree_view->priv->drag_column;
3021   gint x, y;
3022
3023   /* Sanity Check */
3024   if ((column == NULL) ||
3025       (event->window != tree_view->priv->drag_window))
3026     return FALSE;
3027
3028   /* Handle moving the header */
3029   gdk_window_get_position (tree_view->priv->drag_window, &x, &y);
3030   x = CLAMP (x + (gint)event->x - column->drag_x, 0,
3031              MAX (tree_view->priv->width, GTK_WIDGET (tree_view)->allocation.width) - column->button->allocation.width);
3032   gdk_window_move (tree_view->priv->drag_window, x, y);
3033   
3034   /* autoscroll, if needed */
3035   pspp_sheet_view_horizontal_autoscroll (tree_view);
3036   /* Update the current reorder position and arrow; */
3037   pspp_sheet_view_update_current_reorder (tree_view);
3038
3039   return TRUE;
3040 }
3041
3042 static void
3043 pspp_sheet_view_stop_rubber_band (PsppSheetView *tree_view)
3044 {
3045   remove_scroll_timeout (tree_view);
3046   gtk_grab_remove (GTK_WIDGET (tree_view));
3047
3048   if (tree_view->priv->rubber_band_status == RUBBER_BAND_ACTIVE)
3049     {
3050       GtkTreePath *tmp_path;
3051
3052       gtk_widget_queue_draw (GTK_WIDGET (tree_view));
3053
3054       /* The anchor path should be set to the start path */
3055       tmp_path = _pspp_sheet_view_find_path (tree_view,
3056                                            tree_view->priv->rubber_band_start_node);
3057
3058       if (tree_view->priv->anchor)
3059         gtk_tree_row_reference_free (tree_view->priv->anchor);
3060
3061       tree_view->priv->anchor =
3062         gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view),
3063                                           tree_view->priv->model,
3064                                           tmp_path);
3065
3066       gtk_tree_path_free (tmp_path);
3067
3068       /* ... and the cursor to the end path */
3069       tmp_path = _pspp_sheet_view_find_path (tree_view,
3070                                            tree_view->priv->rubber_band_end_node);
3071       pspp_sheet_view_real_set_cursor (PSPP_SHEET_VIEW (tree_view), tmp_path, FALSE, FALSE);
3072       gtk_tree_path_free (tmp_path);
3073
3074       _pspp_sheet_selection_emit_changed (tree_view->priv->selection);
3075     }
3076
3077   /* Clear status variables */
3078   tree_view->priv->rubber_band_status = RUBBER_BAND_OFF;
3079   tree_view->priv->rubber_band_shift = 0;
3080   tree_view->priv->rubber_band_ctrl = 0;
3081
3082   tree_view->priv->rubber_band_start_node = -1;
3083   tree_view->priv->rubber_band_end_node = -1;
3084 }
3085
3086 static void
3087 pspp_sheet_view_update_rubber_band_selection_range (PsppSheetView *tree_view,
3088                                                  int start_node,
3089                                                  int end_node,
3090                                                  gboolean     select,
3091                                                  gboolean     skip_start,
3092                                                  gboolean     skip_end)
3093 {
3094   if (start_node == end_node)
3095     return;
3096
3097   /* We skip the first node and jump inside the loop */
3098   if (skip_start)
3099     goto skip_first;
3100
3101   do
3102     {
3103       /* Small optimization by assuming insensitive nodes are never
3104        * selected.
3105        */
3106       if (select)
3107         {
3108           if (tree_view->priv->rubber_band_shift)
3109             pspp_sheet_view_node_select (tree_view, start_node);
3110           else if (tree_view->priv->rubber_band_ctrl)
3111             {
3112               /* Toggle the selection state */
3113               if (pspp_sheet_view_node_is_selected (tree_view, start_node))
3114                 pspp_sheet_view_node_unselect (tree_view, start_node);
3115               else
3116                 pspp_sheet_view_node_select (tree_view, start_node);
3117             }
3118           else
3119             pspp_sheet_view_node_select (tree_view, start_node);
3120         }
3121       else
3122         {
3123           /* Mirror the above */
3124           if (tree_view->priv->rubber_band_shift)
3125                 pspp_sheet_view_node_unselect (tree_view, start_node);
3126           else if (tree_view->priv->rubber_band_ctrl)
3127             {
3128               /* Toggle the selection state */
3129               if (pspp_sheet_view_node_is_selected (tree_view, start_node))
3130                 pspp_sheet_view_node_unselect (tree_view, start_node);
3131               else
3132                 pspp_sheet_view_node_select (tree_view, start_node);
3133             }
3134           else
3135             pspp_sheet_view_node_unselect (tree_view, start_node);
3136         }
3137
3138       _pspp_sheet_view_queue_draw_node (tree_view, start_node, NULL);
3139
3140       if (start_node == end_node)
3141         break;
3142
3143 skip_first:
3144
3145       start_node = pspp_sheet_view_node_next (tree_view, start_node);
3146
3147       if (start_node < 0)
3148         /* Ran out of tree */
3149         break;
3150
3151       if (skip_end && start_node == end_node)
3152         break;
3153     }
3154   while (TRUE);
3155 }
3156
3157 static gint
3158 pspp_sheet_view_node_find_offset (PsppSheetView *tree_view,
3159                                   int node)
3160 {
3161   return node * tree_view->priv->fixed_height;
3162 }
3163
3164 static gint
3165 pspp_sheet_view_find_offset (PsppSheetView *tree_view,
3166                              gint height,
3167                              int *new_node)
3168 {
3169   int fixed_height = tree_view->priv->fixed_height;
3170   if (fixed_height <= 0
3171       || height < 0
3172       || height >= tree_view->priv->row_count * fixed_height)
3173     {
3174       *new_node = -1;
3175       return 0;
3176     }
3177   else
3178     {
3179       *new_node = height / fixed_height;
3180       return height % fixed_height;
3181     }
3182 }
3183
3184 static void
3185 pspp_sheet_view_update_rubber_band_selection (PsppSheetView *tree_view)
3186 {
3187   int start_node;
3188   int end_node;
3189
3190   pspp_sheet_view_find_offset (tree_view, MIN (tree_view->priv->press_start_y, tree_view->priv->rubber_band_y), &start_node);
3191   pspp_sheet_view_find_offset (tree_view, MAX (tree_view->priv->press_start_y, tree_view->priv->rubber_band_y), &end_node);
3192
3193   /* Handle the start area first */
3194   if (tree_view->priv->rubber_band_start_node < 0)
3195     {
3196       pspp_sheet_view_update_rubber_band_selection_range (tree_view,
3197                                                        start_node,
3198                                                        end_node,
3199                                                        TRUE,
3200                                                        FALSE,
3201                                                        FALSE);
3202     }
3203   else if (start_node < tree_view->priv->rubber_band_start_node)
3204     {
3205       /* New node is above the old one; selection became bigger */
3206       pspp_sheet_view_update_rubber_band_selection_range (tree_view,
3207                                                        start_node,
3208                                                        tree_view->priv->rubber_band_start_node,
3209                                                        TRUE,
3210                                                        FALSE,
3211                                                        TRUE);
3212     }
3213   else if (start_node > tree_view->priv->rubber_band_start_node)
3214     {
3215       /* New node is below the old one; selection became smaller */
3216       pspp_sheet_view_update_rubber_band_selection_range (tree_view,
3217                                                        tree_view->priv->rubber_band_start_node,
3218                                                        start_node,
3219                                                        FALSE,
3220                                                        FALSE,
3221                                                        TRUE);
3222     }
3223
3224   tree_view->priv->rubber_band_start_node = start_node;
3225
3226   /* Next, handle the end area */
3227   if (tree_view->priv->rubber_band_end_node < 0)
3228     {
3229       /* In the event this happens, start_node was also -1; this case is
3230        * handled above.
3231        */
3232     }
3233   else if (end_node < 0)
3234     {
3235       /* Find the last node in the tree */
3236       pspp_sheet_view_find_offset (tree_view, tree_view->priv->height - 1,
3237                                &end_node);
3238
3239       /* Selection reached end of the tree */
3240       pspp_sheet_view_update_rubber_band_selection_range (tree_view,
3241                                                        tree_view->priv->rubber_band_end_node,
3242                                                        end_node,
3243                                                        TRUE,
3244                                                        TRUE,
3245                                                        FALSE);
3246     }
3247   else if (end_node > tree_view->priv->rubber_band_end_node)
3248     {
3249       /* New node is below the old one; selection became bigger */
3250       pspp_sheet_view_update_rubber_band_selection_range (tree_view,
3251                                                        tree_view->priv->rubber_band_end_node,
3252                                                        end_node,
3253                                                        TRUE,
3254                                                        TRUE,
3255                                                        FALSE);
3256     }
3257   else if (end_node < tree_view->priv->rubber_band_end_node)
3258     {
3259       /* New node is above the old one; selection became smaller */
3260       pspp_sheet_view_update_rubber_band_selection_range (tree_view,
3261                                                        end_node,
3262                                                        tree_view->priv->rubber_band_end_node,
3263                                                        FALSE,
3264                                                        TRUE,
3265                                                        FALSE);
3266     }
3267
3268   tree_view->priv->rubber_band_end_node = end_node;
3269 }
3270
3271 static void
3272 pspp_sheet_view_update_rubber_band (PsppSheetView *tree_view)
3273 {
3274   gint x, y;
3275   GdkRectangle old_area;
3276   GdkRectangle new_area;
3277   GdkRectangle common;
3278   GdkRegion *invalid_region;
3279
3280   old_area.x = MIN (tree_view->priv->press_start_x, tree_view->priv->rubber_band_x);
3281   old_area.y = MIN (tree_view->priv->press_start_y, tree_view->priv->rubber_band_y) - tree_view->priv->dy;
3282   old_area.width = ABS (tree_view->priv->rubber_band_x - tree_view->priv->press_start_x) + 1;
3283   old_area.height = ABS (tree_view->priv->rubber_band_y - tree_view->priv->press_start_y) + 1;
3284
3285   gdk_window_get_pointer (tree_view->priv->bin_window, &x, &y, NULL);
3286
3287   x = MAX (x, 0);
3288   y = MAX (y, 0) + tree_view->priv->dy;
3289
3290   new_area.x = MIN (tree_view->priv->press_start_x, x);
3291   new_area.y = MIN (tree_view->priv->press_start_y, y) - tree_view->priv->dy;
3292   new_area.width = ABS (x - tree_view->priv->press_start_x) + 1;
3293   new_area.height = ABS (y - tree_view->priv->press_start_y) + 1;
3294
3295   invalid_region = gdk_region_rectangle (&old_area);
3296   gdk_region_union_with_rect (invalid_region, &new_area);
3297
3298   gdk_rectangle_intersect (&old_area, &new_area, &common);
3299   if (common.width > 2 && common.height > 2)
3300     {
3301       GdkRegion *common_region;
3302
3303       /* make sure the border is invalidated */
3304       common.x += 1;
3305       common.y += 1;
3306       common.width -= 2;
3307       common.height -= 2;
3308
3309       common_region = gdk_region_rectangle (&common);
3310
3311       gdk_region_subtract (invalid_region, common_region);
3312       gdk_region_destroy (common_region);
3313     }
3314
3315   gdk_window_invalidate_region (tree_view->priv->bin_window, invalid_region, TRUE);
3316
3317   gdk_region_destroy (invalid_region);
3318
3319   tree_view->priv->rubber_band_x = x;
3320   tree_view->priv->rubber_band_y = y;
3321
3322   pspp_sheet_view_update_rubber_band_selection (tree_view);
3323 }
3324
3325 static void
3326 pspp_sheet_view_paint_rubber_band (PsppSheetView  *tree_view,
3327                                 GdkRectangle *area)
3328 {
3329   cairo_t *cr;
3330   GdkRectangle rect;
3331   GdkRectangle rubber_rect;
3332
3333   rubber_rect.x = MIN (tree_view->priv->press_start_x, tree_view->priv->rubber_band_x);
3334   rubber_rect.y = MIN (tree_view->priv->press_start_y, tree_view->priv->rubber_band_y) - tree_view->priv->dy;
3335   rubber_rect.width = ABS (tree_view->priv->press_start_x - tree_view->priv->rubber_band_x) + 1;
3336   rubber_rect.height = ABS (tree_view->priv->press_start_y - tree_view->priv->rubber_band_y) + 1;
3337
3338   if (!gdk_rectangle_intersect (&rubber_rect, area, &rect))
3339     return;
3340
3341   cr = gdk_cairo_create (tree_view->priv->bin_window);
3342   cairo_set_line_width (cr, 1.0);
3343
3344   cairo_set_source_rgba (cr,
3345                          GTK_WIDGET (tree_view)->style->fg[GTK_STATE_NORMAL].red / 65535.,
3346                          GTK_WIDGET (tree_view)->style->fg[GTK_STATE_NORMAL].green / 65535.,
3347                          GTK_WIDGET (tree_view)->style->fg[GTK_STATE_NORMAL].blue / 65535.,
3348                          .25);
3349
3350   gdk_cairo_rectangle (cr, &rect);
3351   cairo_clip (cr);
3352   cairo_paint (cr);
3353
3354   cairo_set_source_rgb (cr,
3355                         GTK_WIDGET (tree_view)->style->fg[GTK_STATE_NORMAL].red / 65535.,
3356                         GTK_WIDGET (tree_view)->style->fg[GTK_STATE_NORMAL].green / 65535.,
3357                         GTK_WIDGET (tree_view)->style->fg[GTK_STATE_NORMAL].blue / 65535.);
3358
3359   cairo_rectangle (cr,
3360                    rubber_rect.x + 0.5, rubber_rect.y + 0.5,
3361                    rubber_rect.width - 1, rubber_rect.height - 1);
3362   cairo_stroke (cr);
3363
3364   cairo_destroy (cr);
3365 }
3366
3367 static gboolean
3368 pspp_sheet_view_motion_bin_window (GtkWidget      *widget,
3369                                  GdkEventMotion *event)
3370 {
3371   PsppSheetView *tree_view;
3372   int node;
3373   gint new_y;
3374
3375   tree_view = (PsppSheetView *) widget;
3376
3377   if (tree_view->priv->row_count == 0)
3378     return FALSE;
3379
3380   if (tree_view->priv->rubber_band_status == RUBBER_BAND_MAYBE_START)
3381     {
3382       gtk_grab_add (GTK_WIDGET (tree_view));
3383       pspp_sheet_view_update_rubber_band (tree_view);
3384
3385       tree_view->priv->rubber_band_status = RUBBER_BAND_ACTIVE;
3386     }
3387   else if (tree_view->priv->rubber_band_status == RUBBER_BAND_ACTIVE)
3388     {
3389       pspp_sheet_view_update_rubber_band (tree_view);
3390
3391       add_scroll_timeout (tree_view);
3392     }
3393
3394   /* only check for an initiated drag when a button is pressed */
3395   if (tree_view->priv->pressed_button >= 0
3396       && !tree_view->priv->rubber_band_status)
3397     pspp_sheet_view_maybe_begin_dragging_row (tree_view, event);
3398
3399   new_y = TREE_WINDOW_Y_TO_RBTREE_Y(tree_view, event->y);
3400   if (new_y < 0)
3401     new_y = 0;
3402
3403   pspp_sheet_view_find_offset (tree_view, new_y, &node);
3404
3405   tree_view->priv->event_last_x = event->x;
3406   tree_view->priv->event_last_y = event->y;
3407
3408   prelight_or_select (tree_view, node, event->x, event->y);
3409
3410   return TRUE;
3411 }
3412
3413 static gboolean
3414 pspp_sheet_view_motion (GtkWidget      *widget,
3415                       GdkEventMotion *event)
3416 {
3417   PsppSheetView *tree_view;
3418
3419   tree_view = (PsppSheetView *) widget;
3420
3421   /* Resizing a column */
3422   if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_RESIZE))
3423     return pspp_sheet_view_motion_resize_column (widget, event);
3424
3425   /* Drag column */
3426   if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_DRAG))
3427     return pspp_sheet_view_motion_drag_column (widget, event);
3428
3429   /* Sanity check it */
3430   if (event->window == tree_view->priv->bin_window)
3431     return pspp_sheet_view_motion_bin_window (widget, event);
3432
3433   return FALSE;
3434 }
3435
3436 /* Invalidate the focus rectangle near the edge of the bin_window; used when
3437  * the tree is empty.
3438  */
3439 static void
3440 invalidate_empty_focus (PsppSheetView *tree_view)
3441 {
3442   GdkRectangle area;
3443
3444   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
3445     return;
3446
3447   area.x = 0;
3448   area.y = 0;
3449   gdk_drawable_get_size (tree_view->priv->bin_window, &area.width, &area.height);
3450   gdk_window_invalidate_rect (tree_view->priv->bin_window, &area, FALSE);
3451 }
3452
3453 /* Draws a focus rectangle near the edge of the bin_window; used when the tree
3454  * is empty.
3455  */
3456 static void
3457 draw_empty_focus (PsppSheetView *tree_view, GdkRectangle *clip_area)
3458 {
3459   GtkWidget *widget = GTK_WIDGET (tree_view);
3460   gint w, h;
3461
3462   if (!gtk_widget_has_focus (widget))
3463     return;
3464
3465   gdk_drawable_get_size (tree_view->priv->bin_window, &w, &h);
3466
3467   w -= 2;
3468   h -= 2;
3469
3470   if (w > 0 && h > 0)
3471     gtk_paint_focus (gtk_widget_get_style (widget),
3472                      tree_view->priv->bin_window,
3473                      gtk_widget_get_state (widget),
3474                      clip_area,
3475                      widget,
3476                      NULL,
3477                      1, 1, w, h);
3478 }
3479
3480 static void
3481 pspp_sheet_view_draw_grid_lines (PsppSheetView    *tree_view,
3482                                GdkEventExpose *event,
3483                                gint            n_visible_columns)
3484 {
3485   GList *list = tree_view->priv->columns;
3486   gint i = 0;
3487   gint current_x = 0;
3488
3489   if (tree_view->priv->grid_lines != PSPP_SHEET_VIEW_GRID_LINES_VERTICAL
3490       && tree_view->priv->grid_lines != PSPP_SHEET_VIEW_GRID_LINES_BOTH)
3491     return;
3492
3493   /* Only draw the lines for visible rows and columns */
3494   for (list = tree_view->priv->columns; list; list = list->next, i++)
3495     {
3496       PsppSheetViewColumn *column = list->data;
3497
3498       /* We don't want a line for the last column */
3499       if (i == n_visible_columns - 1)
3500         break;
3501
3502       if (! column->visible)
3503         continue;
3504
3505       current_x += column->width;
3506
3507       gdk_draw_line (event->window,
3508                      tree_view->priv->grid_line_gc,
3509                      current_x - 1, 0,
3510                      current_x - 1, tree_view->priv->height);
3511     }
3512 }
3513
3514 /* Warning: Very scary function.
3515  * Modify at your own risk
3516  *
3517  * KEEP IN SYNC WITH pspp_sheet_view_create_row_drag_icon()!
3518  * FIXME: It's not...
3519  */
3520 static gboolean
3521 pspp_sheet_view_bin_expose (GtkWidget      *widget,
3522                           GdkEventExpose *event)
3523 {
3524   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
3525   GtkTreePath *path;
3526   GList *list;
3527   int node;
3528   int cursor = -1;
3529   int drag_highlight = -1;
3530   GtkTreeIter iter;
3531   gint new_y;
3532   gint y_offset, cell_offset;
3533   gint max_height;
3534   gint depth;
3535   GdkRectangle background_area;
3536   GdkRectangle cell_area;
3537   guint flags;
3538   gint bin_window_width;
3539   gint bin_window_height;
3540   GtkTreePath *cursor_path;
3541   GtkTreePath *drag_dest_path;
3542   GList *first_column, *last_column;
3543   gint vertical_separator;
3544   gint horizontal_separator;
3545   gint focus_line_width;
3546   gboolean allow_rules;
3547   gboolean has_special_cell;
3548   gboolean rtl;
3549   gint n_visible_columns;
3550   gint grid_line_width;
3551   gboolean row_ending_details;
3552   gboolean draw_vgrid_lines, draw_hgrid_lines;
3553
3554   rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
3555
3556   gtk_widget_style_get (widget,
3557                         "horizontal-separator", &horizontal_separator,
3558                         "vertical-separator", &vertical_separator,
3559                         "allow-rules", &allow_rules,
3560                         "focus-line-width", &focus_line_width,
3561                         "row-ending-details", &row_ending_details,
3562                         NULL);
3563
3564   if (tree_view->priv->row_count == 0)
3565     {
3566       draw_empty_focus (tree_view, &event->area);
3567       return TRUE;
3568     }
3569
3570   /* clip event->area to the visible area */
3571   if (event->area.height < 0)
3572     return TRUE;
3573
3574   validate_visible_area (tree_view);
3575
3576   new_y = TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, event->area.y);
3577
3578   if (new_y < 0)
3579     new_y = 0;
3580   y_offset = -pspp_sheet_view_find_offset (tree_view, new_y, &node);
3581   gdk_drawable_get_size (tree_view->priv->bin_window,
3582                          &bin_window_width, &bin_window_height);
3583
3584   if (tree_view->priv->height < bin_window_height)
3585     {
3586       gtk_paint_flat_box (widget->style,
3587                           event->window,
3588                           widget->state,
3589                           GTK_SHADOW_NONE,
3590                           &event->area,
3591                           widget,
3592                           "cell_even",
3593                           0, tree_view->priv->height,
3594                           bin_window_width,
3595                           bin_window_height - tree_view->priv->height);
3596     }
3597
3598   if (node < 0)
3599     return TRUE;
3600
3601   /* find the path for the node */
3602   path = _pspp_sheet_view_find_path ((PsppSheetView *)widget, node);
3603   gtk_tree_model_get_iter (tree_view->priv->model,
3604                            &iter,
3605                            path);
3606   depth = gtk_tree_path_get_depth (path);
3607   gtk_tree_path_free (path);
3608   
3609   cursor_path = NULL;
3610   drag_dest_path = NULL;
3611
3612   if (tree_view->priv->cursor)
3613     cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
3614
3615   if (cursor_path)
3616     _pspp_sheet_view_find_node (tree_view, cursor_path, &cursor);
3617
3618   if (tree_view->priv->drag_dest_row)
3619     drag_dest_path = gtk_tree_row_reference_get_path (tree_view->priv->drag_dest_row);
3620
3621   if (drag_dest_path)
3622     _pspp_sheet_view_find_node (tree_view, drag_dest_path,
3623                                 &drag_highlight);
3624
3625   draw_vgrid_lines =
3626     tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_VERTICAL
3627     || tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_BOTH;
3628   draw_hgrid_lines =
3629     tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_HORIZONTAL
3630     || tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_BOTH;
3631
3632   if (draw_vgrid_lines || draw_hgrid_lines)
3633     gtk_widget_style_get (widget, "grid-line-width", &grid_line_width, NULL);
3634   
3635   n_visible_columns = 0;
3636   for (list = tree_view->priv->columns; list; list = list->next)
3637     {
3638       if (! PSPP_SHEET_VIEW_COLUMN (list->data)->visible)
3639         continue;
3640       n_visible_columns ++;
3641     }
3642
3643   /* Find the last column */
3644   for (last_column = g_list_last (tree_view->priv->columns);
3645        last_column && !(PSPP_SHEET_VIEW_COLUMN (last_column->data)->visible);
3646        last_column = last_column->prev)
3647     ;
3648
3649   /* and the first */
3650   for (first_column = g_list_first (tree_view->priv->columns);
3651        first_column && !(PSPP_SHEET_VIEW_COLUMN (first_column->data)->visible);
3652        first_column = first_column->next)
3653     ;
3654
3655   /* Actually process the expose event.  To do this, we want to
3656    * start at the first node of the event, and walk the tree in
3657    * order, drawing each successive node.
3658    */
3659
3660   do
3661     {
3662       gboolean parity;
3663       gboolean is_first = FALSE;
3664       gboolean is_last = FALSE;
3665       gboolean done = FALSE;
3666
3667       max_height = ROW_HEIGHT (tree_view);
3668
3669       cell_offset = 0;
3670
3671       background_area.y = y_offset + event->area.y;
3672       background_area.height = max_height;
3673
3674       flags = 0;
3675
3676       if (node == tree_view->priv->prelight_node)
3677         flags |= GTK_CELL_RENDERER_PRELIT;
3678
3679       if (pspp_sheet_view_node_is_selected (tree_view, node))
3680         flags |= GTK_CELL_RENDERER_SELECTED;
3681
3682       parity = node % 2;
3683
3684       /* we *need* to set cell data on all cells before the call
3685        * to _has_special_cell, else _has_special_cell() does not
3686        * return a correct value.
3687        */
3688       for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns));
3689            list;
3690            list = (rtl ? list->prev : list->next))
3691         {
3692           PsppSheetViewColumn *column = list->data;
3693           pspp_sheet_view_column_cell_set_cell_data (column,
3694                                                    tree_view->priv->model,
3695                                                    &iter);
3696         }
3697
3698       has_special_cell = pspp_sheet_view_has_special_cell (tree_view);
3699
3700       for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns));
3701            list;
3702            list = (rtl ? list->prev : list->next))
3703         {
3704           PsppSheetViewColumn *column = list->data;
3705           const gchar *detail = NULL;
3706           GtkStateType state;
3707
3708           if (!column->visible)
3709             continue;
3710
3711           if (cell_offset > event->area.x + event->area.width ||
3712               cell_offset + column->width < event->area.x)
3713             {
3714               cell_offset += column->width;
3715               continue;
3716             }
3717
3718           if (column->show_sort_indicator)
3719             flags |= GTK_CELL_RENDERER_SORTED;
3720           else
3721             flags &= ~GTK_CELL_RENDERER_SORTED;
3722
3723           if (cursor == node)
3724             flags |= GTK_CELL_RENDERER_FOCUSED;
3725           else
3726             flags &= ~GTK_CELL_RENDERER_FOCUSED;
3727
3728           background_area.x = cell_offset;
3729           background_area.width = column->width;
3730
3731           cell_area = background_area;
3732           cell_area.y += vertical_separator / 2;
3733           cell_area.x += horizontal_separator / 2;
3734           cell_area.height -= vertical_separator;
3735           cell_area.width -= horizontal_separator;
3736
3737           if (draw_vgrid_lines)
3738             {
3739               if (list == first_column)
3740                 {
3741                   cell_area.width -= grid_line_width / 2;
3742                 }
3743               else if (list == last_column)
3744                 {
3745                   cell_area.x += grid_line_width / 2;
3746                   cell_area.width -= grid_line_width / 2;
3747                 }
3748               else
3749                 {
3750                   cell_area.x += grid_line_width / 2;
3751                   cell_area.width -= grid_line_width;
3752                 }
3753             }
3754
3755           if (draw_hgrid_lines)
3756             {
3757               cell_area.y += grid_line_width / 2;
3758               cell_area.height -= grid_line_width;
3759             }
3760
3761           if (gdk_region_rect_in (event->region, &background_area) == GDK_OVERLAP_RECTANGLE_OUT)
3762             {
3763               cell_offset += column->width;
3764               continue;
3765             }
3766
3767           pspp_sheet_view_column_cell_set_cell_data (column,
3768                                                      tree_view->priv->model,
3769                                                      &iter);
3770
3771           /* Select the detail for drawing the cell.  relevant
3772            * factors are parity, sortedness, and whether to
3773            * display rules.
3774            */
3775           if (allow_rules && tree_view->priv->has_rules)
3776             {
3777               if ((flags & GTK_CELL_RENDERER_SORTED) &&
3778                   n_visible_columns >= 3)
3779                 {
3780                   if (parity)
3781                     detail = "cell_odd_ruled_sorted";
3782                   else
3783                     detail = "cell_even_ruled_sorted";
3784                 }
3785               else
3786                 {
3787                   if (parity)
3788                     detail = "cell_odd_ruled";
3789                   else
3790                     detail = "cell_even_ruled";
3791                 }
3792             }
3793           else
3794             {
3795               if ((flags & GTK_CELL_RENDERER_SORTED) &&
3796                   n_visible_columns >= 3)
3797                 {
3798                   if (parity)
3799                     detail = "cell_odd_sorted";
3800                   else
3801                     detail = "cell_even_sorted";
3802                 }
3803               else
3804                 {
3805                   if (parity)
3806                     detail = "cell_odd";
3807                   else
3808                     detail = "cell_even";
3809                 }
3810             }
3811
3812           g_assert (detail);
3813
3814           if (widget->state == GTK_STATE_INSENSITIVE)
3815             state = GTK_STATE_INSENSITIVE;          
3816           else if (flags & GTK_CELL_RENDERER_SELECTED)
3817             state = GTK_STATE_SELECTED;
3818           else
3819             state = GTK_STATE_NORMAL;
3820
3821           /* Draw background */
3822           if (row_ending_details)
3823             {
3824               char new_detail[128];
3825
3826               is_first = (rtl ? !list->next : !list->prev);
3827               is_last = (rtl ? !list->prev : !list->next);
3828
3829               /* (I don't like the snprintfs either, but couldn't find a
3830                * less messy way).
3831                */
3832               if (is_first && is_last)
3833                 g_snprintf (new_detail, 127, "%s", detail);
3834               else if (is_first)
3835                 g_snprintf (new_detail, 127, "%s_start", detail);
3836               else if (is_last)
3837                 g_snprintf (new_detail, 127, "%s_end", detail);
3838               else
3839                 g_snprintf (new_detail, 128, "%s_middle", detail);
3840
3841               gtk_paint_flat_box (widget->style,
3842                                   event->window,
3843                                   state,
3844                                   GTK_SHADOW_NONE,
3845                                   &event->area,
3846                                   widget,
3847                                   new_detail,
3848                                   background_area.x,
3849                                   background_area.y,
3850                                   background_area.width,
3851                                   background_area.height);
3852             }
3853           else
3854             {
3855               gtk_paint_flat_box (widget->style,
3856                                   event->window,
3857                                   state,
3858                                   GTK_SHADOW_NONE,
3859                                   &event->area,
3860                                   widget,
3861                                   detail,
3862                                   background_area.x,
3863                                   background_area.y,
3864                                   background_area.width,
3865                                   background_area.height);
3866             }
3867
3868           if (draw_hgrid_lines)
3869             {
3870               if (background_area.y > 0)
3871                 gdk_draw_line (event->window,
3872                                tree_view->priv->grid_line_gc,
3873                                background_area.x, background_area.y,
3874                                background_area.x + background_area.width,
3875                                background_area.y);
3876
3877               if (y_offset + max_height >= event->area.height)
3878                 gdk_draw_line (event->window,
3879                                tree_view->priv->grid_line_gc,
3880                                background_area.x, background_area.y + max_height,
3881                                background_area.x + background_area.width,
3882                                background_area.y + max_height);
3883             }
3884
3885           _pspp_sheet_view_column_cell_render (column,
3886                                                event->window,
3887                                                &background_area,
3888                                                &cell_area,
3889                                                &event->area,
3890                                                flags);
3891
3892           if (node == cursor && has_special_cell &&
3893               ((column == tree_view->priv->focus_column &&
3894                 PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_DRAW_KEYFOCUS) &&
3895                 gtk_widget_has_focus (widget)) ||
3896                (column == tree_view->priv->edited_column)))
3897             {
3898               _pspp_sheet_view_column_cell_draw_focus (column,
3899                                                      event->window,
3900                                                      &background_area,
3901                                                      &cell_area,
3902                                                      &event->area,
3903                                                      flags);
3904             }
3905
3906           cell_offset += column->width;
3907         }
3908
3909       if (node == drag_highlight)
3910         {
3911           /* Draw indicator for the drop
3912            */
3913           gint highlight_y = -1;
3914           int node = -1;
3915           gint width;
3916
3917           switch (tree_view->priv->drag_dest_pos)
3918             {
3919             case PSPP_SHEET_VIEW_DROP_BEFORE:
3920               highlight_y = background_area.y - 1;
3921               if (highlight_y < 0)
3922                       highlight_y = 0;
3923               break;
3924
3925             case PSPP_SHEET_VIEW_DROP_AFTER:
3926               highlight_y = background_area.y + background_area.height - 1;
3927               break;
3928
3929             case PSPP_SHEET_VIEW_DROP_INTO_OR_BEFORE:
3930             case PSPP_SHEET_VIEW_DROP_INTO_OR_AFTER:
3931               _pspp_sheet_view_find_node (tree_view, drag_dest_path, &node);
3932
3933               if (node < 0)
3934                 break;
3935               gdk_drawable_get_size (tree_view->priv->bin_window,
3936                                      &width, NULL);
3937
3938               if (row_ending_details)
3939                 gtk_paint_focus (widget->style,
3940                                  tree_view->priv->bin_window,
3941                                  gtk_widget_get_state (widget),
3942                                  &event->area,
3943                                  widget,
3944                                  (is_first
3945                                   ? (is_last ? "treeview-drop-indicator" : "treeview-drop-indicator-left" )
3946                                   : (is_last ? "treeview-drop-indicator-right" : "tree-view-drop-indicator-middle" )),
3947                                  0, BACKGROUND_FIRST_PIXEL (tree_view, node)
3948                                  - focus_line_width / 2,
3949                                  width, ROW_HEIGHT (tree_view)
3950                                - focus_line_width + 1);
3951               else
3952                 gtk_paint_focus (widget->style,
3953                                  tree_view->priv->bin_window,
3954                                  gtk_widget_get_state (widget),
3955                                  &event->area,
3956                                  widget,
3957                                  "treeview-drop-indicator",
3958                                  0, BACKGROUND_FIRST_PIXEL (tree_view, node)
3959                                  - focus_line_width / 2,
3960                                  width, ROW_HEIGHT (tree_view)
3961                                  - focus_line_width + 1);
3962               break;
3963             }
3964
3965           if (highlight_y >= 0)
3966             {
3967               gdk_draw_line (event->window,
3968                              widget->style->fg_gc[gtk_widget_get_state (widget)],
3969                              0,
3970                              highlight_y,
3971                              rtl ? 0 : bin_window_width,
3972                              highlight_y);
3973             }
3974         }
3975
3976       /* draw the big row-spanning focus rectangle, if needed */
3977       if (!has_special_cell && node == cursor &&
3978           PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_DRAW_KEYFOCUS) &&
3979           gtk_widget_has_focus (widget))
3980         {
3981           gint tmp_y, tmp_height;
3982           gint width;
3983           GtkStateType focus_rect_state;
3984
3985           focus_rect_state =
3986             flags & GTK_CELL_RENDERER_SELECTED ? GTK_STATE_SELECTED :
3987             (flags & GTK_CELL_RENDERER_PRELIT ? GTK_STATE_PRELIGHT :
3988              (flags & GTK_CELL_RENDERER_INSENSITIVE ? GTK_STATE_INSENSITIVE :
3989               GTK_STATE_NORMAL));
3990
3991           gdk_drawable_get_size (tree_view->priv->bin_window,
3992                                  &width, NULL);
3993           
3994           if (draw_hgrid_lines)
3995             {
3996               tmp_y = BACKGROUND_FIRST_PIXEL (tree_view, node) + grid_line_width / 2;
3997               tmp_height = ROW_HEIGHT (tree_view) - grid_line_width;
3998             }
3999           else
4000             {
4001               tmp_y = BACKGROUND_FIRST_PIXEL (tree_view, node);
4002               tmp_height = ROW_HEIGHT (tree_view);
4003             }
4004
4005           if (row_ending_details)
4006             gtk_paint_focus (widget->style,
4007                              tree_view->priv->bin_window,
4008                              focus_rect_state,
4009                              &event->area,
4010                              widget,
4011                              (is_first
4012                               ? (is_last ? "treeview" : "treeview-left" )
4013                               : (is_last ? "treeview-right" : "treeview-middle" )),
4014                              0, tmp_y,
4015                              width, tmp_height);
4016           else
4017             gtk_paint_focus (widget->style,
4018                              tree_view->priv->bin_window,
4019                              focus_rect_state,
4020                              &event->area,
4021                              widget,
4022                              "treeview",
4023                              0, tmp_y,
4024                              width, tmp_height);
4025         }
4026
4027       y_offset += max_height;
4028
4029       do
4030         {
4031           node = pspp_sheet_view_node_next (tree_view, node);
4032           if (node >= 0)
4033             {
4034               gboolean has_next = gtk_tree_model_iter_next (tree_view->priv->model, &iter);
4035               done = TRUE;
4036
4037               /* Sanity Check! */
4038               TREE_VIEW_INTERNAL_ASSERT (has_next, FALSE);
4039             }
4040           else
4041             goto done;
4042         }
4043       while (!done);
4044     }
4045   while (y_offset < event->area.height);
4046
4047 done:
4048   pspp_sheet_view_draw_grid_lines (tree_view, event, n_visible_columns);
4049
4050  if (tree_view->priv->rubber_band_status == RUBBER_BAND_ACTIVE)
4051    {
4052      GdkRectangle *rectangles;
4053      gint n_rectangles;
4054
4055      gdk_region_get_rectangles (event->region,
4056                                 &rectangles,
4057                                 &n_rectangles);
4058
4059      while (n_rectangles--)
4060        pspp_sheet_view_paint_rubber_band (tree_view, &rectangles[n_rectangles]);
4061
4062      g_free (rectangles);
4063    }
4064
4065   if (cursor_path)
4066     gtk_tree_path_free (cursor_path);
4067
4068   if (drag_dest_path)
4069     gtk_tree_path_free (drag_dest_path);
4070
4071   return FALSE;
4072 }
4073
4074 static gboolean
4075 pspp_sheet_view_expose (GtkWidget      *widget,
4076                       GdkEventExpose *event)
4077 {
4078   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
4079
4080   if (event->window == tree_view->priv->bin_window)
4081     {
4082       gboolean retval;
4083       GList *tmp_list;
4084
4085       retval = pspp_sheet_view_bin_expose (widget, event);
4086
4087       /* We can't just chain up to Container::expose as it will try to send the
4088        * event to the headers, so we handle propagating it to our children
4089        * (eg. widgets being edited) ourselves.
4090        */
4091       tmp_list = tree_view->priv->children;
4092       while (tmp_list)
4093         {
4094           PsppSheetViewChild *child = tmp_list->data;
4095           tmp_list = tmp_list->next;
4096
4097           gtk_container_propagate_expose (GTK_CONTAINER (tree_view), child->widget, event);
4098         }
4099
4100       return retval;
4101     }
4102
4103   else if (event->window == tree_view->priv->header_window)
4104     {
4105       GList *list;
4106       
4107       for (list = tree_view->priv->columns; list != NULL; list = list->next)
4108         {
4109           PsppSheetViewColumn *column = list->data;
4110
4111           if (column == tree_view->priv->drag_column)
4112             continue;
4113
4114           if (column->visible)
4115             gtk_container_propagate_expose (GTK_CONTAINER (tree_view),
4116                                             column->button,
4117                                             event);
4118         }
4119     }
4120   else if (event->window == tree_view->priv->drag_window)
4121     {
4122       gtk_container_propagate_expose (GTK_CONTAINER (tree_view),
4123                                       tree_view->priv->drag_column->button,
4124                                       event);
4125     }
4126   return TRUE;
4127 }
4128
4129 enum
4130 {
4131   DROP_HOME,
4132   DROP_RIGHT,
4133   DROP_LEFT,
4134   DROP_END
4135 };
4136
4137 /* returns 0x1 when no column has been found -- yes it's hackish */
4138 static PsppSheetViewColumn *
4139 pspp_sheet_view_get_drop_column (PsppSheetView       *tree_view,
4140                                PsppSheetViewColumn *column,
4141                                gint               drop_position)
4142 {
4143   PsppSheetViewColumn *left_column = NULL;
4144   PsppSheetViewColumn *cur_column = NULL;
4145   GList *tmp_list;
4146
4147   if (!column->reorderable)
4148     return (PsppSheetViewColumn *)0x1;
4149
4150   switch (drop_position)
4151     {
4152       case DROP_HOME:
4153         /* find first column where we can drop */
4154         tmp_list = tree_view->priv->columns;
4155         if (column == PSPP_SHEET_VIEW_COLUMN (tmp_list->data))
4156           return (PsppSheetViewColumn *)0x1;
4157
4158         while (tmp_list)
4159           {
4160             g_assert (tmp_list);
4161
4162             cur_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
4163             tmp_list = tmp_list->next;
4164
4165             if (left_column && left_column->visible == FALSE)
4166               continue;
4167
4168             if (!tree_view->priv->column_drop_func)
4169               return left_column;
4170
4171             if (!tree_view->priv->column_drop_func (tree_view, column, left_column, cur_column, tree_view->priv->column_drop_func_data))
4172               {
4173                 left_column = cur_column;
4174                 continue;
4175               }
4176
4177             return left_column;
4178           }
4179
4180         if (!tree_view->priv->column_drop_func)
4181           return left_column;
4182
4183         if (tree_view->priv->column_drop_func (tree_view, column, left_column, NULL, tree_view->priv->column_drop_func_data))
4184           return left_column;
4185         else
4186           return (PsppSheetViewColumn *)0x1;
4187         break;
4188
4189       case DROP_RIGHT:
4190         /* find first column after column where we can drop */
4191         tmp_list = tree_view->priv->columns;
4192
4193         for (; tmp_list; tmp_list = tmp_list->next)
4194           if (PSPP_SHEET_VIEW_COLUMN (tmp_list->data) == column)
4195             break;
4196
4197         if (!tmp_list || !tmp_list->next)
4198           return (PsppSheetViewColumn *)0x1;
4199
4200         tmp_list = tmp_list->next;
4201         left_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
4202         tmp_list = tmp_list->next;
4203
4204         while (tmp_list)
4205           {
4206             g_assert (tmp_list);
4207
4208             cur_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
4209             tmp_list = tmp_list->next;
4210
4211             if (left_column && left_column->visible == FALSE)
4212               {
4213                 left_column = cur_column;
4214                 if (tmp_list)
4215                   tmp_list = tmp_list->next;
4216                 continue;
4217               }
4218
4219             if (!tree_view->priv->column_drop_func)
4220               return left_column;
4221
4222             if (!tree_view->priv->column_drop_func (tree_view, column, left_column, cur_column, tree_view->priv->column_drop_func_data))
4223               {
4224                 left_column = cur_column;
4225                 continue;
4226               }
4227
4228             return left_column;
4229           }
4230
4231         if (!tree_view->priv->column_drop_func)
4232           return left_column;
4233
4234         if (tree_view->priv->column_drop_func (tree_view, column, left_column, NULL, tree_view->priv->column_drop_func_data))
4235           return left_column;
4236         else
4237           return (PsppSheetViewColumn *)0x1;
4238         break;
4239
4240       case DROP_LEFT:
4241         /* find first column before column where we can drop */
4242         tmp_list = tree_view->priv->columns;
4243
4244         for (; tmp_list; tmp_list = tmp_list->next)
4245           if (PSPP_SHEET_VIEW_COLUMN (tmp_list->data) == column)
4246             break;
4247
4248         if (!tmp_list || !tmp_list->prev)
4249           return (PsppSheetViewColumn *)0x1;
4250
4251         tmp_list = tmp_list->prev;
4252         cur_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
4253         tmp_list = tmp_list->prev;
4254
4255         while (tmp_list)
4256           {
4257             g_assert (tmp_list);
4258
4259             left_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
4260
4261             if (left_column && !left_column->visible)
4262               {
4263                 /*if (!tmp_list->prev)
4264                   return (PsppSheetViewColumn *)0x1;
4265                   */
4266 /*
4267                 cur_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->prev->data);
4268                 tmp_list = tmp_list->prev->prev;
4269                 continue;*/
4270
4271                 cur_column = left_column;
4272                 if (tmp_list)
4273                   tmp_list = tmp_list->prev;
4274                 continue;
4275               }
4276
4277             if (!tree_view->priv->column_drop_func)
4278               return left_column;
4279
4280             if (tree_view->priv->column_drop_func (tree_view, column, left_column, cur_column, tree_view->priv->column_drop_func_data))
4281               return left_column;
4282
4283             cur_column = left_column;
4284             tmp_list = tmp_list->prev;
4285           }
4286
4287         if (!tree_view->priv->column_drop_func)
4288           return NULL;
4289
4290         if (tree_view->priv->column_drop_func (tree_view, column, NULL, cur_column, tree_view->priv->column_drop_func_data))
4291           return NULL;
4292         else
4293           return (PsppSheetViewColumn *)0x1;
4294         break;
4295
4296       case DROP_END:
4297         /* same as DROP_HOME case, but doing it backwards */
4298         tmp_list = g_list_last (tree_view->priv->columns);
4299         cur_column = NULL;
4300
4301         if (column == PSPP_SHEET_VIEW_COLUMN (tmp_list->data))
4302           return (PsppSheetViewColumn *)0x1;
4303
4304         while (tmp_list)
4305           {
4306             g_assert (tmp_list);
4307
4308             left_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
4309
4310             if (left_column && !left_column->visible)
4311               {
4312                 cur_column = left_column;
4313                 tmp_list = tmp_list->prev;
4314               }
4315
4316             if (!tree_view->priv->column_drop_func)
4317               return left_column;
4318
4319             if (tree_view->priv->column_drop_func (tree_view, column, left_column, cur_column, tree_view->priv->column_drop_func_data))
4320               return left_column;
4321
4322             cur_column = left_column;
4323             tmp_list = tmp_list->prev;
4324           }
4325
4326         if (!tree_view->priv->column_drop_func)
4327           return NULL;
4328
4329         if (tree_view->priv->column_drop_func (tree_view, column, NULL, cur_column, tree_view->priv->column_drop_func_data))
4330           return NULL;
4331         else
4332           return (PsppSheetViewColumn *)0x1;
4333         break;
4334     }
4335
4336   return (PsppSheetViewColumn *)0x1;
4337 }
4338
4339 static gboolean
4340 pspp_sheet_view_key_press (GtkWidget   *widget,
4341                          GdkEventKey *event)
4342 {
4343   PsppSheetView *tree_view = (PsppSheetView *) widget;
4344
4345   if (tree_view->priv->rubber_band_status)
4346     {
4347       if (event->keyval == GDK_Escape)
4348         pspp_sheet_view_stop_rubber_band (tree_view);
4349
4350       return TRUE;
4351     }
4352
4353   if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_DRAG))
4354     {
4355       if (event->keyval == GDK_Escape)
4356         {
4357           tree_view->priv->cur_reorder = NULL;
4358           pspp_sheet_view_button_release_drag_column (widget, NULL);
4359         }
4360       return TRUE;
4361     }
4362
4363   if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_HEADERS_VISIBLE))
4364     {
4365       GList *focus_column;
4366       gboolean rtl;
4367
4368       rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
4369
4370       for (focus_column = tree_view->priv->columns;
4371            focus_column;
4372            focus_column = focus_column->next)
4373         {
4374           PsppSheetViewColumn *column = PSPP_SHEET_VIEW_COLUMN (focus_column->data);
4375
4376           if (gtk_widget_has_focus (column->button))
4377             break;
4378         }
4379
4380       if (focus_column &&
4381           (event->state & GDK_SHIFT_MASK) && (event->state & GDK_MOD1_MASK) &&
4382           (event->keyval == GDK_Left || event->keyval == GDK_KP_Left
4383            || event->keyval == GDK_Right || event->keyval == GDK_KP_Right))
4384         {
4385           PsppSheetViewColumn *column = PSPP_SHEET_VIEW_COLUMN (focus_column->data);
4386
4387           if (!column->resizable)
4388             {
4389               gtk_widget_error_bell (widget);
4390               return TRUE;
4391             }
4392
4393           if (event->keyval == (rtl ? GDK_Right : GDK_Left)
4394               || event->keyval == (rtl ? GDK_KP_Right : GDK_KP_Left))
4395             {
4396               gint old_width = column->resized_width;
4397
4398               column->resized_width = MAX (column->resized_width,
4399                                            column->width);
4400               column->resized_width -= 2;
4401               if (column->resized_width < 0)
4402                 column->resized_width = 0;
4403
4404               if (column->min_width == -1)
4405                 column->resized_width = MAX (column->button->requisition.width,
4406                                              column->resized_width);
4407               else
4408                 column->resized_width = MAX (column->min_width,
4409                                              column->resized_width);
4410
4411               if (column->max_width != -1)
4412                 column->resized_width = MIN (column->resized_width,
4413                                              column->max_width);
4414
4415               column->use_resized_width = TRUE;
4416
4417               if (column->resized_width != old_width)
4418                 gtk_widget_queue_resize (widget);
4419               else
4420                 gtk_widget_error_bell (widget);
4421             }
4422           else if (event->keyval == (rtl ? GDK_Left : GDK_Right)
4423                    || event->keyval == (rtl ? GDK_KP_Left : GDK_KP_Right))
4424             {
4425               gint old_width = column->resized_width;
4426
4427               column->resized_width = MAX (column->resized_width,
4428                                            column->width);
4429               column->resized_width += 2;
4430
4431               if (column->max_width != -1)
4432                 column->resized_width = MIN (column->resized_width,
4433                                              column->max_width);
4434
4435               column->use_resized_width = TRUE;
4436
4437               if (column->resized_width != old_width)
4438                 gtk_widget_queue_resize (widget);
4439               else
4440                 gtk_widget_error_bell (widget);
4441             }
4442
4443           return TRUE;
4444         }
4445
4446       if (focus_column &&
4447           (event->state & GDK_MOD1_MASK) &&
4448           (event->keyval == GDK_Left || event->keyval == GDK_KP_Left
4449            || event->keyval == GDK_Right || event->keyval == GDK_KP_Right
4450            || event->keyval == GDK_Home || event->keyval == GDK_KP_Home
4451            || event->keyval == GDK_End || event->keyval == GDK_KP_End))
4452         {
4453           PsppSheetViewColumn *column = PSPP_SHEET_VIEW_COLUMN (focus_column->data);
4454
4455           if (event->keyval == (rtl ? GDK_Right : GDK_Left)
4456               || event->keyval == (rtl ? GDK_KP_Right : GDK_KP_Left))
4457             {
4458               PsppSheetViewColumn *col;
4459               col = pspp_sheet_view_get_drop_column (tree_view, column, DROP_LEFT);
4460               if (col != (PsppSheetViewColumn *)0x1)
4461                 pspp_sheet_view_move_column_after (tree_view, column, col);
4462               else
4463                 gtk_widget_error_bell (widget);
4464             }
4465           else if (event->keyval == (rtl ? GDK_Left : GDK_Right)
4466                    || event->keyval == (rtl ? GDK_KP_Left : GDK_KP_Right))
4467             {
4468               PsppSheetViewColumn *col;
4469               col = pspp_sheet_view_get_drop_column (tree_view, column, DROP_RIGHT);
4470               if (col != (PsppSheetViewColumn *)0x1)
4471                 pspp_sheet_view_move_column_after (tree_view, column, col);
4472               else
4473                 gtk_widget_error_bell (widget);
4474             }
4475           else if (event->keyval == GDK_Home || event->keyval == GDK_KP_Home)
4476             {
4477               PsppSheetViewColumn *col;
4478               col = pspp_sheet_view_get_drop_column (tree_view, column, DROP_HOME);
4479               if (col != (PsppSheetViewColumn *)0x1)
4480                 pspp_sheet_view_move_column_after (tree_view, column, col);
4481               else
4482                 gtk_widget_error_bell (widget);
4483             }
4484           else if (event->keyval == GDK_End || event->keyval == GDK_KP_End)
4485             {
4486               PsppSheetViewColumn *col;
4487               col = pspp_sheet_view_get_drop_column (tree_view, column, DROP_END);
4488               if (col != (PsppSheetViewColumn *)0x1)
4489                 pspp_sheet_view_move_column_after (tree_view, column, col);
4490               else
4491                 gtk_widget_error_bell (widget);
4492             }
4493
4494           return TRUE;
4495         }
4496     }
4497
4498   /* Chain up to the parent class.  It handles the keybindings. */
4499   if (GTK_WIDGET_CLASS (pspp_sheet_view_parent_class)->key_press_event (widget, event))
4500     return TRUE;
4501
4502   if (tree_view->priv->search_entry_avoid_unhandled_binding)
4503     {
4504       tree_view->priv->search_entry_avoid_unhandled_binding = FALSE;
4505       return FALSE;
4506     }
4507
4508   /* We pass the event to the search_entry.  If its text changes, then we start
4509    * the typeahead find capabilities. */
4510   if (gtk_widget_has_focus (GTK_WIDGET (tree_view))
4511       && tree_view->priv->enable_search
4512       && !tree_view->priv->search_custom_entry_set)
4513     {
4514       GdkEvent *new_event;
4515       char *old_text;
4516       const char *new_text;
4517       gboolean retval;
4518       GdkScreen *screen;
4519       gboolean text_modified;
4520       gulong popup_menu_id;
4521
4522       pspp_sheet_view_ensure_interactive_directory (tree_view);
4523
4524       /* Make a copy of the current text */
4525       old_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (tree_view->priv->search_entry)));
4526       new_event = gdk_event_copy ((GdkEvent *) event);
4527       g_object_unref (((GdkEventKey *) new_event)->window);
4528       ((GdkEventKey *) new_event)->window = g_object_ref (tree_view->priv->search_window->window);
4529       gtk_widget_realize (tree_view->priv->search_window);
4530
4531       popup_menu_id = g_signal_connect (tree_view->priv->search_entry, 
4532                                         "popup-menu", G_CALLBACK (gtk_true),
4533                                         NULL);
4534
4535       /* Move the entry off screen */
4536       screen = gtk_widget_get_screen (GTK_WIDGET (tree_view));
4537       gtk_window_move (GTK_WINDOW (tree_view->priv->search_window),
4538                        gdk_screen_get_width (screen) + 1,
4539                        gdk_screen_get_height (screen) + 1);
4540       gtk_widget_show (tree_view->priv->search_window);
4541
4542       /* Send the event to the window.  If the preedit_changed signal is emitted
4543        * during this event, we will set priv->imcontext_changed  */
4544       tree_view->priv->imcontext_changed = FALSE;
4545       retval = gtk_widget_event (tree_view->priv->search_window, new_event);
4546       gdk_event_free (new_event);
4547       gtk_widget_hide (tree_view->priv->search_window);
4548
4549       g_signal_handler_disconnect (tree_view->priv->search_entry, 
4550                                    popup_menu_id);
4551
4552       /* We check to make sure that the entry tried to handle the text, and that
4553        * the text has changed.
4554        */
4555       new_text = gtk_entry_get_text (GTK_ENTRY (tree_view->priv->search_entry));
4556       text_modified = strcmp (old_text, new_text) != 0;
4557       g_free (old_text);
4558       if (tree_view->priv->imcontext_changed ||    /* we're in a preedit */
4559           (retval && text_modified))               /* ...or the text was modified */
4560         {
4561           if (pspp_sheet_view_real_start_interactive_search (tree_view, FALSE))
4562             {
4563               gtk_widget_grab_focus (GTK_WIDGET (tree_view));
4564               return TRUE;
4565             }
4566           else
4567             {
4568               gtk_entry_set_text (GTK_ENTRY (tree_view->priv->search_entry), "");
4569               return FALSE;
4570             }
4571         }
4572     }
4573
4574   return FALSE;
4575 }
4576
4577 static gboolean
4578 pspp_sheet_view_key_release (GtkWidget   *widget,
4579                            GdkEventKey *event)
4580 {
4581   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
4582
4583   if (tree_view->priv->rubber_band_status)
4584     return TRUE;
4585
4586   return GTK_WIDGET_CLASS (pspp_sheet_view_parent_class)->key_release_event (widget, event);
4587 }
4588
4589 /* FIXME Is this function necessary? Can I get an enter_notify event
4590  * w/o either an expose event or a mouse motion event?
4591  */
4592 static gboolean
4593 pspp_sheet_view_enter_notify (GtkWidget        *widget,
4594                             GdkEventCrossing *event)
4595 {
4596   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
4597   int node;
4598   gint new_y;
4599
4600   /* Sanity check it */
4601   if (event->window != tree_view->priv->bin_window)
4602     return FALSE;
4603
4604   if (tree_view->priv->row_count == 0)
4605     return FALSE;
4606
4607   if (event->mode == GDK_CROSSING_GRAB ||
4608       event->mode == GDK_CROSSING_GTK_GRAB ||
4609       event->mode == GDK_CROSSING_GTK_UNGRAB ||
4610       event->mode == GDK_CROSSING_STATE_CHANGED)
4611     return TRUE;
4612
4613   /* find the node internally */
4614   new_y = TREE_WINDOW_Y_TO_RBTREE_Y(tree_view, event->y);
4615   if (new_y < 0)
4616     new_y = 0;
4617   pspp_sheet_view_find_offset (tree_view, new_y, &node);
4618
4619   tree_view->priv->event_last_x = event->x;
4620   tree_view->priv->event_last_y = event->y;
4621
4622   prelight_or_select (tree_view, node, event->x, event->y);
4623
4624   return TRUE;
4625 }
4626
4627 static gboolean
4628 pspp_sheet_view_leave_notify (GtkWidget        *widget,
4629                             GdkEventCrossing *event)
4630 {
4631   PsppSheetView *tree_view;
4632
4633   if (event->mode == GDK_CROSSING_GRAB)
4634     return TRUE;
4635
4636   tree_view = PSPP_SHEET_VIEW (widget);
4637
4638   if (tree_view->priv->prelight_node >= 0)
4639     _pspp_sheet_view_queue_draw_node (tree_view,
4640                                    tree_view->priv->prelight_node,
4641                                    NULL);
4642
4643   tree_view->priv->event_last_x = -10000;
4644   tree_view->priv->event_last_y = -10000;
4645
4646   prelight_or_select (tree_view,
4647                       -1,
4648                       -1000, -1000); /* coords not possibly over an arrow */
4649
4650   return TRUE;
4651 }
4652
4653
4654 static gint
4655 pspp_sheet_view_focus_out (GtkWidget     *widget,
4656                          GdkEventFocus *event)
4657 {
4658   PsppSheetView *tree_view;
4659
4660   tree_view = PSPP_SHEET_VIEW (widget);
4661
4662   gtk_widget_queue_draw (widget);
4663
4664   /* destroy interactive search dialog */
4665   if (tree_view->priv->search_window)
4666     pspp_sheet_view_search_dialog_hide (tree_view->priv->search_window, tree_view);
4667
4668   return FALSE;
4669 }
4670
4671
4672 /* Incremental Reflow
4673  */
4674
4675 static void
4676 pspp_sheet_view_node_queue_redraw (PsppSheetView *tree_view,
4677                                  int node)
4678 {
4679   gint y;
4680
4681   y = pspp_sheet_view_node_find_offset (tree_view, node)
4682     - tree_view->priv->vadjustment->value
4683     + TREE_VIEW_HEADER_HEIGHT (tree_view);
4684
4685   gtk_widget_queue_draw_area (GTK_WIDGET (tree_view),
4686                               0, y,
4687                               GTK_WIDGET (tree_view)->allocation.width,
4688                               tree_view->priv->fixed_height);
4689 }
4690
4691 static gboolean
4692 node_is_visible (PsppSheetView *tree_view,
4693                  int node)
4694 {
4695   int y;
4696   int height;
4697
4698   y = pspp_sheet_view_node_find_offset (tree_view, node);
4699   height = ROW_HEIGHT (tree_view);
4700
4701   if (y >= tree_view->priv->vadjustment->value &&
4702       y + height <= (tree_view->priv->vadjustment->value
4703                      + tree_view->priv->vadjustment->page_size))
4704     return TRUE;
4705
4706   return FALSE;
4707 }
4708
4709 /* Returns the row height. */
4710 static gint
4711 validate_row (PsppSheetView *tree_view,
4712               int node,
4713               GtkTreeIter *iter,
4714               GtkTreePath *path)
4715 {
4716   PsppSheetViewColumn *column;
4717   GList *list, *first_column, *last_column;
4718   gint height = 0;
4719   gint horizontal_separator;
4720   gint vertical_separator;
4721   gint focus_line_width;
4722   gboolean retval = FALSE;
4723   gboolean draw_vgrid_lines, draw_hgrid_lines;
4724   gint focus_pad;
4725   gint grid_line_width;
4726   gboolean wide_separators;
4727   gint separator_height;
4728
4729   gtk_widget_style_get (GTK_WIDGET (tree_view),
4730                         "focus-padding", &focus_pad,
4731                         "focus-line-width", &focus_line_width,
4732                         "horizontal-separator", &horizontal_separator,
4733                         "vertical-separator", &vertical_separator,
4734                         "grid-line-width", &grid_line_width,
4735                         "wide-separators",  &wide_separators,
4736                         "separator-height", &separator_height,
4737                         NULL);
4738   
4739   draw_vgrid_lines =
4740     tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_VERTICAL
4741     || tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_BOTH;
4742   draw_hgrid_lines =
4743     tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_HORIZONTAL
4744     || tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_BOTH;
4745
4746   for (last_column = g_list_last (tree_view->priv->columns);
4747        last_column && !(PSPP_SHEET_VIEW_COLUMN (last_column->data)->visible);
4748        last_column = last_column->prev)
4749     ;
4750
4751   for (first_column = g_list_first (tree_view->priv->columns);
4752        first_column && !(PSPP_SHEET_VIEW_COLUMN (first_column->data)->visible);
4753        first_column = first_column->next)
4754     ;
4755
4756   for (list = tree_view->priv->columns; list; list = list->next)
4757     {
4758       gint tmp_width;
4759       gint tmp_height;
4760
4761       column = list->data;
4762
4763       if (! column->visible)
4764         continue;
4765
4766       pspp_sheet_view_column_cell_set_cell_data (column, tree_view->priv->model, iter);
4767       pspp_sheet_view_column_cell_get_size (column,
4768                                           NULL, NULL, NULL,
4769                                           &tmp_width, &tmp_height);
4770
4771       tmp_height += vertical_separator;
4772       height = MAX (height, tmp_height);
4773
4774       tmp_width = tmp_width + horizontal_separator;
4775
4776       if (draw_vgrid_lines)
4777         {
4778           if (list->data == first_column || list->data == last_column)
4779             tmp_width += grid_line_width / 2.0;
4780           else
4781             tmp_width += grid_line_width;
4782         }
4783
4784       if (tmp_width > column->requested_width)
4785         {
4786           retval = TRUE;
4787           column->requested_width = tmp_width;
4788         }
4789     }
4790
4791   if (draw_hgrid_lines)
4792     height += grid_line_width;
4793
4794   tree_view->priv->post_validation_flag = TRUE;
4795   return height;
4796 }
4797
4798
4799 static void
4800 validate_visible_area (PsppSheetView *tree_view)
4801 {
4802   GtkTreePath *path = NULL;
4803   GtkTreePath *above_path = NULL;
4804   GtkTreeIter iter;
4805   int node = -1;
4806   gboolean size_changed = FALSE;
4807   gint total_height;
4808   gint area_above = 0;
4809   gint area_below = 0;
4810
4811   if (tree_view->priv->row_count == 0)
4812     return;
4813
4814   if (tree_view->priv->scroll_to_path == NULL)
4815     return;
4816
4817   total_height = GTK_WIDGET (tree_view)->allocation.height - TREE_VIEW_HEADER_HEIGHT (tree_view);
4818
4819   if (total_height == 0)
4820     return;
4821
4822   path = gtk_tree_row_reference_get_path (tree_view->priv->scroll_to_path);
4823   if (path)
4824     {
4825       /* we are going to scroll, and will update dy */
4826       _pspp_sheet_view_find_node (tree_view, path, &node);
4827       gtk_tree_model_get_iter (tree_view->priv->model, &iter, path);
4828
4829       if (tree_view->priv->scroll_to_use_align)
4830         {
4831           gint height = ROW_HEIGHT (tree_view);
4832           area_above = (total_height - height) *
4833             tree_view->priv->scroll_to_row_align;
4834           area_below = total_height - area_above - height;
4835           area_above = MAX (area_above, 0);
4836           area_below = MAX (area_below, 0);
4837         }
4838       else
4839         {
4840           /* two cases:
4841            * 1) row not visible
4842            * 2) row visible
4843            */
4844           gint dy;
4845           gint height = ROW_HEIGHT (tree_view);
4846
4847           dy = pspp_sheet_view_node_find_offset (tree_view, node);
4848
4849           if (dy >= tree_view->priv->vadjustment->value &&
4850               dy + height <= (tree_view->priv->vadjustment->value
4851                               + tree_view->priv->vadjustment->page_size))
4852             {
4853               /* row visible: keep the row at the same position */
4854               area_above = dy - tree_view->priv->vadjustment->value;
4855               area_below = (tree_view->priv->vadjustment->value +
4856                             tree_view->priv->vadjustment->page_size)
4857                 - dy - height;
4858             }
4859           else
4860             {
4861               /* row not visible */
4862               if (dy >= 0
4863                   && dy + height <= tree_view->priv->vadjustment->page_size)
4864                 {
4865                   /* row at the beginning -- fixed */
4866                   area_above = dy;
4867                   area_below = tree_view->priv->vadjustment->page_size
4868                     - area_above - height;
4869                 }
4870               else if (dy >= (tree_view->priv->vadjustment->upper -
4871                               tree_view->priv->vadjustment->page_size))
4872                 {
4873                   /* row at the end -- fixed */
4874                   area_above = dy - (tree_view->priv->vadjustment->upper -
4875                                      tree_view->priv->vadjustment->page_size);
4876                   area_below = tree_view->priv->vadjustment->page_size -
4877                     area_above - height;
4878
4879                   if (area_below < 0)
4880                     {
4881                       area_above = tree_view->priv->vadjustment->page_size - height;
4882                       area_below = 0;
4883                     }
4884                 }
4885               else
4886                 {
4887                   /* row somewhere in the middle, bring it to the top
4888                    * of the view
4889                    */
4890                   area_above = 0;
4891                   area_below = total_height - height;
4892                 }
4893             }
4894         }
4895     }
4896   else
4897     /* the scroll to isn't valid; ignore it.
4898      */
4899     {
4900       gtk_tree_row_reference_free (tree_view->priv->scroll_to_path);
4901       tree_view->priv->scroll_to_path = NULL;
4902       return;
4903     }
4904
4905   above_path = gtk_tree_path_copy (path);
4906
4907   /* Now, we walk forwards and backwards, measuring rows. Unfortunately,
4908    * backwards is much slower then forward, as there is no iter_prev function.
4909    * We go forwards first in case we run out of tree.  Then we go backwards to
4910    * fill out the top.
4911    */
4912   while (node >= 0 && area_below > 0)
4913     {
4914       gboolean done = FALSE;
4915       do
4916         {
4917           node = pspp_sheet_view_node_next (tree_view, node);
4918           if (node >= 0)
4919             {
4920               gboolean has_next = gtk_tree_model_iter_next (tree_view->priv->model, &iter);
4921               done = TRUE;
4922               gtk_tree_path_next (path);
4923
4924               /* Sanity Check! */
4925               TREE_VIEW_INTERNAL_ASSERT_VOID (has_next);
4926             }
4927           else
4928             break;
4929         }
4930       while (!done);
4931
4932       if (node < 0)
4933         break;
4934
4935       area_below -= ROW_HEIGHT (tree_view);
4936     }
4937   gtk_tree_path_free (path);
4938
4939   /* If we ran out of tree, and have extra area_below left, we need to add it
4940    * to area_above */
4941   if (area_below > 0)
4942     area_above += area_below;
4943
4944   _pspp_sheet_view_find_node (tree_view, above_path, &node);
4945
4946   /* We walk backwards */
4947   while (area_above > 0)
4948     {
4949       node = pspp_sheet_view_node_prev (tree_view, node);
4950
4951       /* Always find the new path in the tree.  We cannot just assume
4952        * a gtk_tree_path_prev() is enough here, as there might be children
4953        * in between this node and the previous sibling node.  If this
4954        * appears to be a performance hotspot in profiles, we can look into
4955        * intrigate logic for keeping path, node and iter in sync like
4956        * we do for forward walks.  (Which will be hard because of the lacking
4957        * iter_prev).
4958        */
4959
4960       if (node < 0)
4961         break;
4962
4963       gtk_tree_path_free (above_path);
4964       above_path = _pspp_sheet_view_find_path (tree_view, node);
4965
4966       gtk_tree_model_get_iter (tree_view->priv->model, &iter, above_path);
4967
4968       area_above -= ROW_HEIGHT (tree_view);
4969     }
4970
4971   /* set the dy here to scroll to the path,
4972    * and sync the top row accordingly
4973    */
4974   pspp_sheet_view_set_top_row (tree_view, above_path, -area_above);
4975   pspp_sheet_view_top_row_to_dy (tree_view);
4976
4977   /* update width/height and queue a resize */
4978   if (size_changed)
4979     {
4980       GtkRequisition requisition;
4981
4982       /* We temporarily guess a size, under the assumption that it will be the
4983        * same when we get our next size_allocate.  If we don't do this, we'll be
4984        * in an inconsistent state if we call top_row_to_dy. */
4985
4986       gtk_widget_size_request (GTK_WIDGET (tree_view), &requisition);
4987       tree_view->priv->hadjustment->upper = MAX (tree_view->priv->hadjustment->upper, (gfloat)requisition.width);
4988       tree_view->priv->vadjustment->upper = MAX (tree_view->priv->vadjustment->upper, (gfloat)requisition.height);
4989       gtk_adjustment_changed (tree_view->priv->hadjustment);
4990       gtk_adjustment_changed (tree_view->priv->vadjustment);
4991       gtk_widget_queue_resize (GTK_WIDGET (tree_view));
4992     }
4993
4994   gtk_tree_row_reference_free (tree_view->priv->scroll_to_path);
4995   tree_view->priv->scroll_to_path = NULL;
4996
4997   if (above_path)
4998     gtk_tree_path_free (above_path);
4999
5000   if (tree_view->priv->scroll_to_column)
5001     {
5002       tree_view->priv->scroll_to_column = NULL;
5003     }
5004   gtk_widget_queue_draw (GTK_WIDGET (tree_view));
5005 }
5006
5007 static void
5008 initialize_fixed_height_mode (PsppSheetView *tree_view)
5009 {
5010   if (!tree_view->priv->row_count)
5011     return;
5012
5013   if (tree_view->priv->fixed_height < 0)
5014     {
5015       GtkTreeIter iter;
5016       GtkTreePath *path;
5017
5018       int node = 0;
5019
5020       path = _pspp_sheet_view_find_path (tree_view, node);
5021       gtk_tree_model_get_iter (tree_view->priv->model, &iter, path);
5022
5023       tree_view->priv->fixed_height = validate_row (tree_view, node, &iter, path);
5024
5025       gtk_tree_path_free (path);
5026     }
5027 }
5028
5029 /* Our strategy for finding nodes to validate is a little convoluted.  We find
5030  * the left-most uninvalidated node.  We then try walking right, validating
5031  * nodes.  Once we find a valid node, we repeat the previous process of finding
5032  * the first invalid node.
5033  */
5034
5035 static gboolean
5036 validate_rows_handler (PsppSheetView *tree_view)
5037 {
5038   initialize_fixed_height_mode (tree_view);
5039   if (tree_view->priv->validate_rows_timer)
5040     {
5041       g_source_remove (tree_view->priv->validate_rows_timer);
5042       tree_view->priv->validate_rows_timer = 0;
5043     }
5044
5045   return FALSE;
5046 }
5047
5048 static gboolean
5049 do_presize_handler (PsppSheetView *tree_view)
5050 {
5051   GtkRequisition requisition;
5052
5053   validate_visible_area (tree_view);
5054   tree_view->priv->presize_handler_timer = 0;
5055
5056   gtk_widget_size_request (GTK_WIDGET (tree_view), &requisition);
5057
5058   tree_view->priv->hadjustment->upper = MAX (tree_view->priv->hadjustment->upper, (gfloat)requisition.width);
5059   tree_view->priv->vadjustment->upper = MAX (tree_view->priv->vadjustment->upper, (gfloat)requisition.height);
5060   gtk_adjustment_changed (tree_view->priv->hadjustment);
5061   gtk_adjustment_changed (tree_view->priv->vadjustment);
5062   gtk_widget_queue_resize (GTK_WIDGET (tree_view));
5063                    
5064   return FALSE;
5065 }
5066
5067 static gboolean
5068 presize_handler_callback (gpointer data)
5069 {
5070   do_presize_handler (PSPP_SHEET_VIEW (data));
5071                    
5072   return FALSE;
5073 }
5074
5075 static void
5076 install_presize_handler (PsppSheetView *tree_view)
5077 {
5078   if (! gtk_widget_get_realized (GTK_WIDGET (tree_view)))
5079     return;
5080
5081   if (! tree_view->priv->presize_handler_timer)
5082     {
5083       tree_view->priv->presize_handler_timer =
5084         gdk_threads_add_idle_full (GTK_PRIORITY_RESIZE - 2, presize_handler_callback, tree_view, NULL);
5085     }
5086   if (! tree_view->priv->validate_rows_timer)
5087     {
5088       tree_view->priv->validate_rows_timer =
5089         gdk_threads_add_idle_full (PSPP_SHEET_VIEW_PRIORITY_VALIDATE, (GSourceFunc) validate_rows_handler, tree_view, NULL);
5090     }
5091 }
5092
5093 static gboolean
5094 scroll_sync_handler (PsppSheetView *tree_view)
5095 {
5096   if (tree_view->priv->height <= tree_view->priv->vadjustment->page_size)
5097     gtk_adjustment_set_value (GTK_ADJUSTMENT (tree_view->priv->vadjustment), 0);
5098   else if (gtk_tree_row_reference_valid (tree_view->priv->top_row))
5099     pspp_sheet_view_top_row_to_dy (tree_view);
5100   else
5101     pspp_sheet_view_dy_to_top_row (tree_view);
5102
5103   tree_view->priv->scroll_sync_timer = 0;
5104
5105   return FALSE;
5106 }
5107
5108 static void
5109 install_scroll_sync_handler (PsppSheetView *tree_view)
5110 {
5111   if (!gtk_widget_get_realized (GTK_WIDGET (tree_view)))
5112     return;
5113
5114   if (!tree_view->priv->scroll_sync_timer)
5115     {
5116       tree_view->priv->scroll_sync_timer =
5117         gdk_threads_add_idle_full (PSPP_SHEET_VIEW_PRIORITY_SCROLL_SYNC, (GSourceFunc) scroll_sync_handler, tree_view, NULL);
5118     }
5119 }
5120
5121 static void
5122 pspp_sheet_view_set_top_row (PsppSheetView *tree_view,
5123                            GtkTreePath *path,
5124                            gint         offset)
5125 {
5126   gtk_tree_row_reference_free (tree_view->priv->top_row);
5127
5128   if (!path)
5129     {
5130       tree_view->priv->top_row = NULL;
5131       tree_view->priv->top_row_dy = 0;
5132     }
5133   else
5134     {
5135       tree_view->priv->top_row = gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view), tree_view->priv->model, path);
5136       tree_view->priv->top_row_dy = offset;
5137     }
5138 }
5139
5140 /* Always call this iff dy is in the visible range.  If the tree is empty, then
5141  * it's set to be NULL, and top_row_dy is 0;
5142  */
5143 static void
5144 pspp_sheet_view_dy_to_top_row (PsppSheetView *tree_view)
5145 {
5146   gint offset;
5147   GtkTreePath *path;
5148   int node;
5149
5150   if (tree_view->priv->row_count == 0)
5151     {
5152       pspp_sheet_view_set_top_row (tree_view, NULL, 0);
5153     }
5154   else
5155     {
5156       offset = pspp_sheet_view_find_offset (tree_view,
5157                                             tree_view->priv->dy,
5158                                             &node);
5159
5160       if (node < 0)
5161         {
5162           pspp_sheet_view_set_top_row (tree_view, NULL, 0);
5163         }
5164       else
5165         {
5166           path = _pspp_sheet_view_find_path (tree_view, node);
5167           pspp_sheet_view_set_top_row (tree_view, path, offset);
5168           gtk_tree_path_free (path);
5169         }
5170     }
5171 }
5172
5173 static void
5174 pspp_sheet_view_top_row_to_dy (PsppSheetView *tree_view)
5175 {
5176   GtkTreePath *path;
5177   int node;
5178   int new_dy;
5179
5180   /* Avoid recursive calls */
5181   if (tree_view->priv->in_top_row_to_dy)
5182     return;
5183
5184   if (tree_view->priv->top_row)
5185     path = gtk_tree_row_reference_get_path (tree_view->priv->top_row);
5186   else
5187     path = NULL;
5188
5189   if (!path)
5190     node = -1;
5191   else
5192     _pspp_sheet_view_find_node (tree_view, path, &node);
5193
5194   if (path)
5195     gtk_tree_path_free (path);
5196
5197   if (node < 0)
5198     {
5199       /* keep dy and set new toprow */
5200       gtk_tree_row_reference_free (tree_view->priv->top_row);
5201       tree_view->priv->top_row = NULL;
5202       tree_view->priv->top_row_dy = 0;
5203       /* DO NOT install the idle handler */
5204       pspp_sheet_view_dy_to_top_row (tree_view);
5205       return;
5206     }
5207
5208   if (ROW_HEIGHT (tree_view) < tree_view->priv->top_row_dy)
5209     {
5210       /* new top row -- do NOT install the idle handler */
5211       pspp_sheet_view_dy_to_top_row (tree_view);
5212       return;
5213     }
5214
5215   new_dy = pspp_sheet_view_node_find_offset (tree_view, node);
5216   new_dy += tree_view->priv->top_row_dy;
5217
5218   if (new_dy + tree_view->priv->vadjustment->page_size > tree_view->priv->height)
5219     new_dy = tree_view->priv->height - tree_view->priv->vadjustment->page_size;
5220
5221   new_dy = MAX (0, new_dy);
5222
5223   tree_view->priv->in_top_row_to_dy = TRUE;
5224   gtk_adjustment_set_value (tree_view->priv->vadjustment, (gdouble)new_dy);
5225   tree_view->priv->in_top_row_to_dy = FALSE;
5226 }
5227
5228
5229 void
5230 _pspp_sheet_view_install_mark_rows_col_dirty (PsppSheetView *tree_view)
5231 {
5232   install_presize_handler (tree_view);
5233 }
5234
5235 /* Drag-and-drop */
5236
5237 static void
5238 set_source_row (GdkDragContext *context,
5239                 GtkTreeModel   *model,
5240                 GtkTreePath    *source_row)
5241 {
5242   g_object_set_data_full (G_OBJECT (context),
5243                           "gtk-tree-view-source-row",
5244                           source_row ? gtk_tree_row_reference_new (model, source_row) : NULL,
5245                           (GDestroyNotify) (source_row ? gtk_tree_row_reference_free : NULL));
5246 }
5247
5248 static GtkTreePath*
5249 get_source_row (GdkDragContext *context)
5250 {
5251   GtkTreeRowReference *ref =
5252     g_object_get_data (G_OBJECT (context), "gtk-tree-view-source-row");
5253
5254   if (ref)
5255     return gtk_tree_row_reference_get_path (ref);
5256   else
5257     return NULL;
5258 }
5259
5260 typedef struct
5261 {
5262   GtkTreeRowReference *dest_row;
5263   guint                path_down_mode   : 1;
5264   guint                empty_view_drop  : 1;
5265   guint                drop_append_mode : 1;
5266 }
5267 DestRow;
5268
5269 static void
5270 dest_row_free (gpointer data)
5271 {
5272   DestRow *dr = (DestRow *)data;
5273
5274   gtk_tree_row_reference_free (dr->dest_row);
5275   g_slice_free (DestRow, dr);
5276 }
5277
5278 static void
5279 set_dest_row (GdkDragContext *context,
5280               GtkTreeModel   *model,
5281               GtkTreePath    *dest_row,
5282               gboolean        path_down_mode,
5283               gboolean        empty_view_drop,
5284               gboolean        drop_append_mode)
5285 {
5286   DestRow *dr;
5287
5288   if (!dest_row)
5289     {
5290       g_object_set_data_full (G_OBJECT (context), "gtk-tree-view-dest-row",
5291                               NULL, NULL);
5292       return;
5293     }
5294
5295   dr = g_slice_new (DestRow);
5296
5297   dr->dest_row = gtk_tree_row_reference_new (model, dest_row);
5298   dr->path_down_mode = path_down_mode != FALSE;
5299   dr->empty_view_drop = empty_view_drop != FALSE;
5300   dr->drop_append_mode = drop_append_mode != FALSE;
5301
5302   g_object_set_data_full (G_OBJECT (context), "gtk-tree-view-dest-row",
5303                           dr, (GDestroyNotify) dest_row_free);
5304 }
5305
5306 static GtkTreePath*
5307 get_dest_row (GdkDragContext *context,
5308               gboolean       *path_down_mode)
5309 {
5310   DestRow *dr =
5311     g_object_get_data (G_OBJECT (context), "gtk-tree-view-dest-row");
5312
5313   if (dr)
5314     {
5315       GtkTreePath *path = NULL;
5316
5317       if (path_down_mode)
5318         *path_down_mode = dr->path_down_mode;
5319
5320       if (dr->dest_row)
5321         path = gtk_tree_row_reference_get_path (dr->dest_row);
5322       else if (dr->empty_view_drop)
5323         path = gtk_tree_path_new_from_indices (0, -1);
5324       else
5325         path = NULL;
5326
5327       if (path && dr->drop_append_mode)
5328         gtk_tree_path_next (path);
5329
5330       return path;
5331     }
5332   else
5333     return NULL;
5334 }
5335
5336 /* Get/set whether drag_motion requested the drag data and
5337  * drag_data_received should thus not actually insert the data,
5338  * since the data doesn't result from a drop.
5339  */
5340 static void
5341 set_status_pending (GdkDragContext *context,
5342                     GdkDragAction   suggested_action)
5343 {
5344   g_object_set_data (G_OBJECT (context),
5345                      "gtk-tree-view-status-pending",
5346                      GINT_TO_POINTER (suggested_action));
5347 }
5348
5349 static GdkDragAction
5350 get_status_pending (GdkDragContext *context)
5351 {
5352   return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (context),
5353                                              "gtk-tree-view-status-pending"));
5354 }
5355
5356 static TreeViewDragInfo*
5357 get_info (PsppSheetView *tree_view)
5358 {
5359   return g_object_get_data (G_OBJECT (tree_view), "gtk-tree-view-drag-info");
5360 }
5361
5362 static void
5363 destroy_info (TreeViewDragInfo *di)
5364 {
5365   g_slice_free (TreeViewDragInfo, di);
5366 }
5367
5368 static TreeViewDragInfo*
5369 ensure_info (PsppSheetView *tree_view)
5370 {
5371   TreeViewDragInfo *di;
5372
5373   di = get_info (tree_view);
5374
5375   if (di == NULL)
5376     {
5377       di = g_slice_new0 (TreeViewDragInfo);
5378
5379       g_object_set_data_full (G_OBJECT (tree_view),
5380                               "gtk-tree-view-drag-info",
5381                               di,
5382                               (GDestroyNotify) destroy_info);
5383     }
5384
5385   return di;
5386 }
5387
5388 static void
5389 remove_info (PsppSheetView *tree_view)
5390 {
5391   g_object_set_data (G_OBJECT (tree_view), "gtk-tree-view-drag-info", NULL);
5392 }
5393
5394 #if 0
5395 static gint
5396 drag_scan_timeout (gpointer data)
5397 {
5398   PsppSheetView *tree_view;
5399   gint x, y;
5400   GdkModifierType state;
5401   GtkTreePath *path = NULL;
5402   PsppSheetViewColumn *column = NULL;
5403   GdkRectangle visible_rect;
5404
5405   GDK_THREADS_ENTER ();
5406
5407   tree_view = PSPP_SHEET_VIEW (data);
5408
5409   gdk_window_get_pointer (tree_view->priv->bin_window,
5410                           &x, &y, &state);
5411
5412   pspp_sheet_view_get_visible_rect (tree_view, &visible_rect);
5413
5414   /* See if we are near the edge. */
5415   if ((x - visible_rect.x) < SCROLL_EDGE_SIZE ||
5416       (visible_rect.x + visible_rect.width - x) < SCROLL_EDGE_SIZE ||
5417       (y - visible_rect.y) < SCROLL_EDGE_SIZE ||
5418       (visible_rect.y + visible_rect.height - y) < SCROLL_EDGE_SIZE)
5419     {
5420       pspp_sheet_view_get_path_at_pos (tree_view,
5421                                      tree_view->priv->bin_window,
5422                                      x, y,
5423                                      &path,
5424                                      &column,
5425                                      NULL,
5426                                      NULL);
5427
5428       if (path != NULL)
5429         {
5430           pspp_sheet_view_scroll_to_cell (tree_view,
5431                                         path,
5432                                         column,
5433                                         TRUE,
5434                                         0.5, 0.5);
5435
5436           gtk_tree_path_free (path);
5437         }
5438     }
5439
5440   GDK_THREADS_LEAVE ();
5441
5442   return TRUE;
5443 }
5444 #endif /* 0 */
5445
5446 static void
5447 add_scroll_timeout (PsppSheetView *tree_view)
5448 {
5449   if (tree_view->priv->scroll_timeout == 0)
5450     {
5451       tree_view->priv->scroll_timeout =
5452         gdk_threads_add_timeout (150, scroll_row_timeout, tree_view);
5453     }
5454 }
5455
5456 static void
5457 remove_scroll_timeout (PsppSheetView *tree_view)
5458 {
5459   if (tree_view->priv->scroll_timeout != 0)
5460     {
5461       g_source_remove (tree_view->priv->scroll_timeout);
5462       tree_view->priv->scroll_timeout = 0;
5463     }
5464 }
5465
5466 static gboolean
5467 check_model_dnd (GtkTreeModel *model,
5468                  GType         required_iface,
5469                  const gchar  *signal)
5470 {
5471   if (model == NULL || !G_TYPE_CHECK_INSTANCE_TYPE ((model), required_iface))
5472     {
5473       g_warning ("You must override the default '%s' handler "
5474                  "on PsppSheetView when using models that don't support "
5475                  "the %s interface and enabling drag-and-drop. The simplest way to do this "
5476                  "is to connect to '%s' and call "
5477                  "g_signal_stop_emission_by_name() in your signal handler to prevent "
5478                  "the default handler from running. Look at the source code "
5479                  "for the default handler in gtktreeview.c to get an idea what "
5480                  "your handler should do. (gtktreeview.c is in the GTK source "
5481                  "code.) If you're using GTK from a language other than C, "
5482                  "there may be a more natural way to override default handlers, e.g. via derivation.",
5483                  signal, g_type_name (required_iface), signal);
5484       return FALSE;
5485     }
5486   else
5487     return TRUE;
5488 }
5489
5490 static gboolean
5491 scroll_row_timeout (gpointer data)
5492 {
5493   PsppSheetView *tree_view = data;
5494
5495   pspp_sheet_view_vertical_autoscroll (tree_view);
5496
5497   if (tree_view->priv->rubber_band_status == RUBBER_BAND_ACTIVE)
5498     pspp_sheet_view_update_rubber_band (tree_view);
5499
5500   return TRUE;
5501 }
5502
5503 /* Returns TRUE if event should not be propagated to parent widgets */
5504 static gboolean
5505 set_destination_row (PsppSheetView    *tree_view,
5506                      GdkDragContext *context,
5507                      /* coordinates relative to the widget */
5508                      gint            x,
5509                      gint            y,
5510                      GdkDragAction  *suggested_action,
5511                      GdkAtom        *target)
5512 {
5513   GtkTreePath *path = NULL;
5514   PsppSheetViewDropPosition pos;
5515   PsppSheetViewDropPosition old_pos;
5516   TreeViewDragInfo *di;
5517   GtkWidget *widget;
5518   GtkTreePath *old_dest_path = NULL;
5519   gboolean can_drop = FALSE;
5520
5521   *suggested_action = 0;
5522   *target = GDK_NONE;
5523
5524   widget = GTK_WIDGET (tree_view);
5525
5526   di = get_info (tree_view);
5527
5528   if (di == NULL || y - TREE_VIEW_HEADER_HEIGHT (tree_view) < 0)
5529     {
5530       /* someone unset us as a drag dest, note that if
5531        * we return FALSE drag_leave isn't called
5532        */
5533
5534       pspp_sheet_view_set_drag_dest_row (tree_view,
5535                                        NULL,
5536                                        PSPP_SHEET_VIEW_DROP_BEFORE);
5537
5538       remove_scroll_timeout (PSPP_SHEET_VIEW (widget));
5539
5540       return FALSE; /* no longer a drop site */
5541     }
5542
5543   *target = gtk_drag_dest_find_target (widget, context,
5544                                        gtk_drag_dest_get_target_list (widget));
5545   if (*target == GDK_NONE)
5546     {
5547       return FALSE;
5548     }
5549
5550   if (!pspp_sheet_view_get_dest_row_at_pos (tree_view,
5551                                           x, y,
5552                                           &path,
5553                                           &pos))
5554     {
5555       gint n_children;
5556       GtkTreeModel *model;
5557
5558       /* the row got dropped on empty space, let's setup a special case
5559        */
5560
5561       if (path)
5562         gtk_tree_path_free (path);
5563
5564       model = pspp_sheet_view_get_model (tree_view);
5565
5566       n_children = gtk_tree_model_iter_n_children (model, NULL);
5567       if (n_children)
5568         {
5569           pos = PSPP_SHEET_VIEW_DROP_AFTER;
5570           path = gtk_tree_path_new_from_indices (n_children - 1, -1);
5571         }
5572       else
5573         {
5574           pos = PSPP_SHEET_VIEW_DROP_BEFORE;
5575           path = gtk_tree_path_new_from_indices (0, -1);
5576         }
5577
5578       can_drop = TRUE;
5579
5580       goto out;
5581     }
5582
5583   g_assert (path);
5584
5585   /* If we left the current row's "open" zone, unset the timeout for
5586    * opening the row
5587    */
5588   pspp_sheet_view_get_drag_dest_row (tree_view,
5589                                    &old_dest_path,
5590                                    &old_pos);
5591
5592   if (old_dest_path)
5593     gtk_tree_path_free (old_dest_path);
5594
5595   if (TRUE /* FIXME if the location droppable predicate */)
5596     {
5597       can_drop = TRUE;
5598     }
5599
5600 out:
5601   if (can_drop)
5602     {
5603       GtkWidget *source_widget;
5604
5605       *suggested_action = context->suggested_action;
5606       source_widget = gtk_drag_get_source_widget (context);
5607
5608       if (source_widget == widget)
5609         {
5610           /* Default to MOVE, unless the user has
5611            * pressed ctrl or shift to affect available actions
5612            */
5613           if ((context->actions & GDK_ACTION_MOVE) != 0)
5614             *suggested_action = GDK_ACTION_MOVE;
5615         }
5616
5617       pspp_sheet_view_set_drag_dest_row (PSPP_SHEET_VIEW (widget),
5618                                        path, pos);
5619     }
5620   else
5621     {
5622       /* can't drop here */
5623       pspp_sheet_view_set_drag_dest_row (PSPP_SHEET_VIEW (widget),
5624                                        NULL,
5625                                        PSPP_SHEET_VIEW_DROP_BEFORE);
5626     }
5627
5628   if (path)
5629     gtk_tree_path_free (path);
5630
5631   return TRUE;
5632 }
5633
5634 static GtkTreePath*
5635 get_logical_dest_row (PsppSheetView *tree_view,
5636                       gboolean    *path_down_mode,
5637                       gboolean    *drop_append_mode)
5638 {
5639   /* adjust path to point to the row the drop goes in front of */
5640   GtkTreePath *path = NULL;
5641   PsppSheetViewDropPosition pos;
5642
5643   g_return_val_if_fail (path_down_mode != NULL, NULL);
5644   g_return_val_if_fail (drop_append_mode != NULL, NULL);
5645
5646   *path_down_mode = FALSE;
5647   *drop_append_mode = 0;
5648
5649   pspp_sheet_view_get_drag_dest_row (tree_view, &path, &pos);
5650
5651   if (path == NULL)
5652     return NULL;
5653
5654   if (pos == PSPP_SHEET_VIEW_DROP_BEFORE)
5655     ; /* do nothing */
5656   else if (pos == PSPP_SHEET_VIEW_DROP_INTO_OR_BEFORE ||
5657            pos == PSPP_SHEET_VIEW_DROP_INTO_OR_AFTER)
5658     *path_down_mode = TRUE;
5659   else
5660     {
5661       GtkTreeIter iter;
5662       GtkTreeModel *model = pspp_sheet_view_get_model (tree_view);
5663
5664       g_assert (pos == PSPP_SHEET_VIEW_DROP_AFTER);
5665
5666       if (!gtk_tree_model_get_iter (model, &iter, path) ||
5667           !gtk_tree_model_iter_next (model, &iter))
5668         *drop_append_mode = 1;
5669       else
5670         {
5671           *drop_append_mode = 0;
5672           gtk_tree_path_next (path);
5673         }
5674     }
5675
5676   return path;
5677 }
5678
5679 static gboolean
5680 pspp_sheet_view_maybe_begin_dragging_row (PsppSheetView      *tree_view,
5681                                         GdkEventMotion   *event)
5682 {
5683   GtkWidget *widget = GTK_WIDGET (tree_view);
5684   GdkDragContext *context;
5685   TreeViewDragInfo *di;
5686   GtkTreePath *path = NULL;
5687   gint button;
5688   gint cell_x, cell_y;
5689   GtkTreeModel *model;
5690   gboolean retval = FALSE;
5691
5692   di = get_info (tree_view);
5693
5694   if (di == NULL || !di->source_set)
5695     goto out;
5696
5697   if (tree_view->priv->pressed_button < 0)
5698     goto out;
5699
5700   if (!gtk_drag_check_threshold (widget,
5701                                  tree_view->priv->press_start_x,
5702                                  tree_view->priv->press_start_y,
5703                                  event->x, event->y))
5704     goto out;
5705
5706   model = pspp_sheet_view_get_model (tree_view);
5707
5708   if (model == NULL)
5709     goto out;
5710
5711   button = tree_view->priv->pressed_button;
5712   tree_view->priv->pressed_button = -1;
5713
5714   pspp_sheet_view_get_path_at_pos (tree_view,
5715                                  tree_view->priv->press_start_x,
5716                                  tree_view->priv->press_start_y,
5717                                  &path,
5718                                  NULL,
5719                                  &cell_x,
5720                                  &cell_y);
5721
5722   if (path == NULL)
5723     goto out;
5724
5725   if (!GTK_IS_TREE_DRAG_SOURCE (model) ||
5726       !gtk_tree_drag_source_row_draggable (GTK_TREE_DRAG_SOURCE (model),
5727                                            path))
5728     goto out;
5729
5730   if (!(GDK_BUTTON1_MASK << (button - 1) & di->start_button_mask))
5731     goto out;
5732
5733   /* Now we can begin the drag */
5734
5735   retval = TRUE;
5736
5737   context = gtk_drag_begin (widget,
5738                             gtk_drag_source_get_target_list (widget),
5739                             di->source_actions,
5740                             button,
5741                             (GdkEvent*)event);
5742
5743   set_source_row (context, model, path);
5744
5745  out:
5746   if (path)
5747     gtk_tree_path_free (path);
5748
5749   return retval;
5750 }
5751
5752
5753 static void
5754 pspp_sheet_view_drag_begin (GtkWidget      *widget,
5755                           GdkDragContext *context)
5756 {
5757   PsppSheetView *tree_view;
5758   GtkTreePath *path = NULL;
5759   gint cell_x, cell_y;
5760   GdkPixmap *row_pix;
5761   TreeViewDragInfo *di;
5762
5763   tree_view = PSPP_SHEET_VIEW (widget);
5764
5765   /* if the user uses a custom DND source impl, we don't set the icon here */
5766   di = get_info (tree_view);
5767
5768   if (di == NULL || !di->source_set)
5769     return;
5770
5771   pspp_sheet_view_get_path_at_pos (tree_view,
5772                                  tree_view->priv->press_start_x,
5773                                  tree_view->priv->press_start_y,
5774                                  &path,
5775                                  NULL,
5776                                  &cell_x,
5777                                  &cell_y);
5778
5779   g_return_if_fail (path != NULL);
5780
5781   row_pix = pspp_sheet_view_create_row_drag_icon (tree_view,
5782                                                 path);
5783
5784   gtk_drag_set_icon_pixmap (context,
5785                             gdk_drawable_get_colormap (row_pix),
5786                             row_pix,
5787                             NULL,
5788                             /* the + 1 is for the black border in the icon */
5789                             tree_view->priv->press_start_x + 1,
5790                             cell_y + 1);
5791
5792   g_object_unref (row_pix);
5793   gtk_tree_path_free (path);
5794 }
5795
5796 static void
5797 pspp_sheet_view_drag_end (GtkWidget      *widget,
5798                         GdkDragContext *context)
5799 {
5800   /* do nothing */
5801 }
5802
5803 /* Default signal implementations for the drag signals */
5804 static void
5805 pspp_sheet_view_drag_data_get (GtkWidget        *widget,
5806                              GdkDragContext   *context,
5807                              GtkSelectionData *selection_data,
5808                              guint             info,
5809                              guint             time)
5810 {
5811   PsppSheetView *tree_view;
5812   GtkTreeModel *model;
5813   TreeViewDragInfo *di;
5814   GtkTreePath *source_row;
5815
5816   tree_view = PSPP_SHEET_VIEW (widget);
5817
5818   model = pspp_sheet_view_get_model (tree_view);
5819
5820   if (model == NULL)
5821     return;
5822
5823   di = get_info (PSPP_SHEET_VIEW (widget));
5824
5825   if (di == NULL)
5826     return;
5827
5828   source_row = get_source_row (context);
5829
5830   if (source_row == NULL)
5831     return;
5832
5833   /* We can implement the GTK_TREE_MODEL_ROW target generically for
5834    * any model; for DragSource models there are some other targets
5835    * we also support.
5836    */
5837
5838   if (GTK_IS_TREE_DRAG_SOURCE (model) &&
5839       gtk_tree_drag_source_drag_data_get (GTK_TREE_DRAG_SOURCE (model),
5840                                           source_row,
5841                                           selection_data))
5842     goto done;
5843
5844   /* If drag_data_get does nothing, try providing row data. */
5845   if (selection_data->target == gdk_atom_intern_static_string ("GTK_TREE_MODEL_ROW"))
5846     {
5847       gtk_tree_set_row_drag_data (selection_data,
5848                                   model,
5849                                   source_row);
5850     }
5851
5852  done:
5853   gtk_tree_path_free (source_row);
5854 }
5855
5856
5857 static void
5858 pspp_sheet_view_drag_data_delete (GtkWidget      *widget,
5859                                 GdkDragContext *context)
5860 {
5861   TreeViewDragInfo *di;
5862   GtkTreeModel *model;
5863   PsppSheetView *tree_view;
5864   GtkTreePath *source_row;
5865
5866   tree_view = PSPP_SHEET_VIEW (widget);
5867   model = pspp_sheet_view_get_model (tree_view);
5868
5869   if (!check_model_dnd (model, GTK_TYPE_TREE_DRAG_SOURCE, "drag_data_delete"))
5870     return;
5871
5872   di = get_info (tree_view);
5873
5874   if (di == NULL)
5875     return;
5876
5877   source_row = get_source_row (context);
5878
5879   if (source_row == NULL)
5880     return;
5881
5882   gtk_tree_drag_source_drag_data_delete (GTK_TREE_DRAG_SOURCE (model),
5883                                          source_row);
5884
5885   gtk_tree_path_free (source_row);
5886
5887   set_source_row (context, NULL, NULL);
5888 }
5889
5890 static void
5891 pspp_sheet_view_drag_leave (GtkWidget      *widget,
5892                           GdkDragContext *context,
5893                           guint             time)
5894 {
5895   /* unset any highlight row */
5896   pspp_sheet_view_set_drag_dest_row (PSPP_SHEET_VIEW (widget),
5897                                    NULL,
5898                                    PSPP_SHEET_VIEW_DROP_BEFORE);
5899
5900   remove_scroll_timeout (PSPP_SHEET_VIEW (widget));
5901 }
5902
5903
5904 static gboolean
5905 pspp_sheet_view_drag_motion (GtkWidget        *widget,
5906                            GdkDragContext   *context,
5907                            /* coordinates relative to the widget */
5908                            gint              x,
5909                            gint              y,
5910                            guint             time)
5911 {
5912   gboolean empty;
5913   GtkTreePath *path = NULL;
5914   PsppSheetViewDropPosition pos;
5915   PsppSheetView *tree_view;
5916   GdkDragAction suggested_action = 0;
5917   GdkAtom target;
5918
5919   tree_view = PSPP_SHEET_VIEW (widget);
5920
5921   if (!set_destination_row (tree_view, context, x, y, &suggested_action, &target))
5922     return FALSE;
5923
5924   pspp_sheet_view_get_drag_dest_row (tree_view, &path, &pos);
5925
5926   /* we only know this *after* set_desination_row */
5927   empty = tree_view->priv->empty_view_drop;
5928
5929   if (path == NULL && !empty)
5930     {
5931       /* Can't drop here. */
5932       gdk_drag_status (context, 0, time);
5933     }
5934   else
5935     {
5936       if (tree_view->priv->open_dest_timeout == 0 &&
5937           (pos == PSPP_SHEET_VIEW_DROP_INTO_OR_AFTER ||
5938            pos == PSPP_SHEET_VIEW_DROP_INTO_OR_BEFORE))
5939         {
5940           /* Nothing. */
5941         }
5942       else
5943         {
5944           add_scroll_timeout (tree_view);
5945         }
5946
5947       if (target == gdk_atom_intern_static_string ("GTK_TREE_MODEL_ROW"))
5948         {
5949           /* Request data so we can use the source row when
5950            * determining whether to accept the drop
5951            */
5952           set_status_pending (context, suggested_action);
5953           gtk_drag_get_data (widget, context, target, time);
5954         }
5955       else
5956         {
5957           set_status_pending (context, 0);
5958           gdk_drag_status (context, suggested_action, time);
5959         }
5960     }
5961
5962   if (path)
5963     gtk_tree_path_free (path);
5964
5965   return TRUE;
5966 }
5967
5968
5969 static gboolean
5970 pspp_sheet_view_drag_drop (GtkWidget        *widget,
5971                          GdkDragContext   *context,
5972                          /* coordinates relative to the widget */
5973                          gint              x,
5974                          gint              y,
5975                          guint             time)
5976 {
5977   PsppSheetView *tree_view;
5978   GtkTreePath *path;
5979   GdkDragAction suggested_action = 0;
5980   GdkAtom target = GDK_NONE;
5981   TreeViewDragInfo *di;
5982   GtkTreeModel *model;
5983   gboolean path_down_mode;
5984   gboolean drop_append_mode;
5985
5986   tree_view = PSPP_SHEET_VIEW (widget);
5987
5988   model = pspp_sheet_view_get_model (tree_view);
5989
5990   remove_scroll_timeout (PSPP_SHEET_VIEW (widget));
5991
5992   di = get_info (tree_view);
5993
5994   if (di == NULL)
5995     return FALSE;
5996
5997   if (!check_model_dnd (model, GTK_TYPE_TREE_DRAG_DEST, "drag_drop"))
5998     return FALSE;
5999
6000   if (!set_destination_row (tree_view, context, x, y, &suggested_action, &target))
6001     return FALSE;
6002
6003   path = get_logical_dest_row (tree_view, &path_down_mode, &drop_append_mode);
6004
6005   if (target != GDK_NONE && path != NULL)
6006     {
6007       /* in case a motion had requested drag data, change things so we
6008        * treat drag data receives as a drop.
6009        */
6010       set_status_pending (context, 0);
6011       set_dest_row (context, model, path,
6012                     path_down_mode, tree_view->priv->empty_view_drop,
6013                     drop_append_mode);
6014     }
6015
6016   if (path)
6017     gtk_tree_path_free (path);
6018
6019   /* Unset this thing */
6020   pspp_sheet_view_set_drag_dest_row (PSPP_SHEET_VIEW (widget),
6021                                    NULL,
6022                                    PSPP_SHEET_VIEW_DROP_BEFORE);
6023
6024   if (target != GDK_NONE)
6025     {
6026       gtk_drag_get_data (widget, context, target, time);
6027       return TRUE;
6028     }
6029   else
6030     return FALSE;
6031 }
6032
6033 static void
6034 pspp_sheet_view_drag_data_received (GtkWidget        *widget,
6035                                   GdkDragContext   *context,
6036                                   /* coordinates relative to the widget */
6037                                   gint              x,
6038                                   gint              y,
6039                                   GtkSelectionData *selection_data,
6040                                   guint             info,
6041                                   guint             time)
6042 {
6043   GtkTreePath *path;
6044   TreeViewDragInfo *di;
6045   gboolean accepted = FALSE;
6046   GtkTreeModel *model;
6047   PsppSheetView *tree_view;
6048   GtkTreePath *dest_row;
6049   GdkDragAction suggested_action;
6050   gboolean path_down_mode;
6051   gboolean drop_append_mode;
6052
6053   tree_view = PSPP_SHEET_VIEW (widget);
6054
6055   model = pspp_sheet_view_get_model (tree_view);
6056
6057   if (!check_model_dnd (model, GTK_TYPE_TREE_DRAG_DEST, "drag_data_received"))
6058     return;
6059
6060   di = get_info (tree_view);
6061
6062   if (di == NULL)
6063     return;
6064
6065   suggested_action = get_status_pending (context);
6066
6067   if (suggested_action)
6068     {
6069       /* We are getting this data due to a request in drag_motion,
6070        * rather than due to a request in drag_drop, so we are just
6071        * supposed to call drag_status, not actually paste in the
6072        * data.
6073        */
6074       path = get_logical_dest_row (tree_view, &path_down_mode,
6075                                    &drop_append_mode);
6076
6077       if (path == NULL)
6078         suggested_action = 0;
6079       else if (path_down_mode)
6080         gtk_tree_path_down (path);
6081
6082       if (suggested_action)
6083         {
6084           if (!gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (model),
6085                                                      path,
6086                                                      selection_data))
6087             {
6088               if (path_down_mode)
6089                 {
6090                   path_down_mode = FALSE;
6091                   gtk_tree_path_up (path);
6092
6093                   if (!gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (model),
6094                                                              path,
6095                                                              selection_data))
6096                     suggested_action = 0;
6097                 }
6098               else
6099                 suggested_action = 0;
6100             }
6101         }
6102
6103       gdk_drag_status (context, suggested_action, time);
6104
6105       if (path)
6106         gtk_tree_path_free (path);
6107
6108       /* If you can't drop, remove user drop indicator until the next motion */
6109       if (suggested_action == 0)
6110         pspp_sheet_view_set_drag_dest_row (PSPP_SHEET_VIEW (widget),
6111                                          NULL,
6112                                          PSPP_SHEET_VIEW_DROP_BEFORE);
6113
6114       return;
6115     }
6116
6117   dest_row = get_dest_row (context, &path_down_mode);
6118
6119   if (dest_row == NULL)
6120     return;
6121
6122   if (selection_data->length >= 0)
6123     {
6124       if (path_down_mode)
6125         {
6126           gtk_tree_path_down (dest_row);
6127           if (!gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (model),
6128                                                      dest_row, selection_data))
6129             gtk_tree_path_up (dest_row);
6130         }
6131     }
6132
6133   if (selection_data->length >= 0)
6134     {
6135       if (gtk_tree_drag_dest_drag_data_received (GTK_TREE_DRAG_DEST (model),
6136                                                  dest_row,
6137                                                  selection_data))
6138         accepted = TRUE;
6139     }
6140
6141   gtk_drag_finish (context,
6142                    accepted,
6143                    (context->action == GDK_ACTION_MOVE),
6144                    time);
6145
6146   if (gtk_tree_path_get_depth (dest_row) == 1
6147       && gtk_tree_path_get_indices (dest_row)[0] == 0)
6148     {
6149       /* special special case drag to "0", scroll to first item */
6150       if (!tree_view->priv->scroll_to_path)
6151         pspp_sheet_view_scroll_to_cell (tree_view, dest_row, NULL, FALSE, 0.0, 0.0);
6152     }
6153
6154   gtk_tree_path_free (dest_row);
6155
6156   /* drop dest_row */
6157   set_dest_row (context, NULL, NULL, FALSE, FALSE, FALSE);
6158 }
6159
6160
6161
6162 /* GtkContainer Methods
6163  */
6164
6165
6166 static void
6167 pspp_sheet_view_remove (GtkContainer *container,
6168                       GtkWidget    *widget)
6169 {
6170   PsppSheetView *tree_view = PSPP_SHEET_VIEW (container);
6171   PsppSheetViewChild *child = NULL;
6172   GList *tmp_list;
6173
6174   tmp_list = tree_view->priv->children;
6175   while (tmp_list)
6176     {
6177       child = tmp_list->data;
6178       if (child->widget == widget)
6179         {
6180           gtk_widget_unparent (widget);
6181
6182           tree_view->priv->children = g_list_remove_link (tree_view->priv->children, tmp_list);
6183           g_list_free_1 (tmp_list);
6184           g_slice_free (PsppSheetViewChild, child);
6185           return;
6186         }
6187
6188       tmp_list = tmp_list->next;
6189     }
6190
6191   tmp_list = tree_view->priv->columns;
6192
6193   while (tmp_list)
6194     {
6195       PsppSheetViewColumn *column;
6196       column = tmp_list->data;
6197       if (column->button == widget)
6198         {
6199           gtk_widget_unparent (widget);
6200           return;
6201         }
6202       tmp_list = tmp_list->next;
6203     }
6204 }
6205
6206 static void
6207 pspp_sheet_view_forall (GtkContainer *container,
6208                       gboolean      include_internals,
6209                       GtkCallback   callback,
6210                       gpointer      callback_data)
6211 {
6212   PsppSheetView *tree_view = PSPP_SHEET_VIEW (container);
6213   PsppSheetViewChild *child = NULL;
6214   PsppSheetViewColumn *column;
6215   GList *tmp_list;
6216
6217   tmp_list = tree_view->priv->children;
6218   while (tmp_list)
6219     {
6220       child = tmp_list->data;
6221       tmp_list = tmp_list->next;
6222
6223       (* callback) (child->widget, callback_data);
6224     }
6225   if (include_internals == FALSE)
6226     return;
6227
6228   for (tmp_list = tree_view->priv->columns; tmp_list; tmp_list = tmp_list->next)
6229     {
6230       column = tmp_list->data;
6231
6232       if (column->button)
6233         (* callback) (column->button, callback_data);
6234     }
6235 }
6236
6237 /* Returns TRUE if the treeview contains no "special" (editable or activatable)
6238  * cells. If so we draw one big row-spanning focus rectangle.
6239  */
6240 static gboolean
6241 pspp_sheet_view_has_special_cell (PsppSheetView *tree_view)
6242 {
6243   GList *list;
6244
6245   for (list = tree_view->priv->columns; list; list = list->next)
6246     {
6247       if (!((PsppSheetViewColumn *)list->data)->visible)
6248         continue;
6249       if (_pspp_sheet_view_column_count_special_cells (list->data))
6250         return TRUE;
6251     }
6252
6253   return FALSE;
6254 }
6255
6256 /* Returns TRUE if the focus is within the headers, after the focus operation is
6257  * done
6258  */
6259 static gboolean
6260 pspp_sheet_view_header_focus (PsppSheetView      *tree_view,
6261                             GtkDirectionType  dir,
6262                             gboolean          clamp_column_visible)
6263 {
6264   GtkWidget *focus_child;
6265
6266   GList *last_column, *first_column;
6267   GList *tmp_list;
6268   gboolean rtl;
6269
6270   if (! PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_HEADERS_VISIBLE))
6271     return FALSE;
6272
6273   focus_child = GTK_CONTAINER (tree_view)->focus_child;
6274
6275   first_column = tree_view->priv->columns;
6276   while (first_column)
6277     {
6278       if (gtk_widget_get_can_focus (PSPP_SHEET_VIEW_COLUMN (first_column->data)->button) &&
6279           PSPP_SHEET_VIEW_COLUMN (first_column->data)->visible &&
6280           (PSPP_SHEET_VIEW_COLUMN (first_column->data)->clickable ||
6281            PSPP_SHEET_VIEW_COLUMN (first_column->data)->reorderable))
6282         break;
6283       first_column = first_column->next;
6284     }
6285
6286   /* No headers are visible, or are focusable.  We can't focus in or out.
6287    */
6288   if (first_column == NULL)
6289     return FALSE;
6290
6291   last_column = g_list_last (tree_view->priv->columns);
6292   while (last_column)
6293     {
6294       if (gtk_widget_get_can_focus (PSPP_SHEET_VIEW_COLUMN (last_column->data)->button) &&
6295           PSPP_SHEET_VIEW_COLUMN (last_column->data)->visible &&
6296           (PSPP_SHEET_VIEW_COLUMN (last_column->data)->clickable ||
6297            PSPP_SHEET_VIEW_COLUMN (last_column->data)->reorderable))
6298         break;
6299       last_column = last_column->prev;
6300     }
6301
6302
6303   rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
6304
6305   switch (dir)
6306     {
6307     case GTK_DIR_TAB_BACKWARD:
6308     case GTK_DIR_TAB_FORWARD:
6309     case GTK_DIR_UP:
6310     case GTK_DIR_DOWN:
6311       if (focus_child == NULL)
6312         {
6313           if (tree_view->priv->focus_column != NULL &&
6314               gtk_widget_get_can_focus (tree_view->priv->focus_column->button))
6315             focus_child = tree_view->priv->focus_column->button;
6316           else
6317             focus_child = PSPP_SHEET_VIEW_COLUMN (first_column->data)->button;
6318           gtk_widget_grab_focus (focus_child);
6319           break;
6320         }
6321       return FALSE;
6322
6323     case GTK_DIR_LEFT:
6324     case GTK_DIR_RIGHT:
6325       if (focus_child == NULL)
6326         {
6327           if (tree_view->priv->focus_column != NULL)
6328             focus_child = tree_view->priv->focus_column->button;
6329           else if (dir == GTK_DIR_LEFT)
6330             focus_child = PSPP_SHEET_VIEW_COLUMN (last_column->data)->button;
6331           else
6332             focus_child = PSPP_SHEET_VIEW_COLUMN (first_column->data)->button;
6333           gtk_widget_grab_focus (focus_child);
6334           break;
6335         }
6336
6337       if (gtk_widget_child_focus (focus_child, dir))
6338         {
6339           /* The focus moves inside the button. */
6340           /* This is probably a great example of bad UI */
6341           break;
6342         }
6343
6344       /* We need to move the focus among the row of buttons. */
6345       for (tmp_list = tree_view->priv->columns; tmp_list; tmp_list = tmp_list->next)
6346         if (PSPP_SHEET_VIEW_COLUMN (tmp_list->data)->button == focus_child)
6347           break;
6348
6349       if ((tmp_list == first_column && dir == (rtl ? GTK_DIR_RIGHT : GTK_DIR_LEFT))
6350           || (tmp_list == last_column && dir == (rtl ? GTK_DIR_LEFT : GTK_DIR_RIGHT)))
6351         {
6352           gtk_widget_error_bell (GTK_WIDGET (tree_view));
6353           break;
6354         }
6355
6356       while (tmp_list)
6357         {
6358           PsppSheetViewColumn *column;
6359
6360           if (dir == (rtl ? GTK_DIR_LEFT : GTK_DIR_RIGHT))
6361             tmp_list = tmp_list->next;
6362           else
6363             tmp_list = tmp_list->prev;
6364
6365           if (tmp_list == NULL)
6366             {
6367               g_warning ("Internal button not found");
6368               break;
6369             }
6370           column = tmp_list->data;
6371           if (column->button &&
6372               column->visible &&
6373               gtk_widget_get_can_focus (column->button))
6374             {
6375               focus_child = column->button;
6376               gtk_widget_grab_focus (column->button);
6377               break;
6378             }
6379         }
6380       break;
6381     default:
6382       g_assert_not_reached ();
6383       break;
6384     }
6385
6386   /* if focus child is non-null, we assume it's been set to the current focus child
6387    */
6388   if (focus_child)
6389     {
6390       for (tmp_list = tree_view->priv->columns; tmp_list; tmp_list = tmp_list->next)
6391         if (PSPP_SHEET_VIEW_COLUMN (tmp_list->data)->button == focus_child)
6392           {
6393             tree_view->priv->focus_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
6394             break;
6395           }
6396
6397       if (clamp_column_visible)
6398         {
6399           pspp_sheet_view_clamp_column_visible (tree_view,
6400                                               tree_view->priv->focus_column,
6401                                               FALSE);
6402         }
6403     }
6404
6405   return (focus_child != NULL);
6406 }
6407
6408 /* This function returns in 'path' the first focusable path, if the given path
6409  * is already focusable, it's the returned one.
6410  */
6411 static gboolean
6412 search_first_focusable_path (PsppSheetView  *tree_view,
6413                              GtkTreePath **path,
6414                              gboolean      search_forward,
6415                              int *new_node)
6416 {
6417   int node = -1;
6418
6419   if (!path || !*path)
6420     return FALSE;
6421
6422   _pspp_sheet_view_find_node (tree_view, *path, &node);
6423
6424   if (node < 0)
6425     return FALSE;
6426
6427   if (new_node)
6428     *new_node = node;
6429
6430   return (*path != NULL);
6431 }
6432
6433 static gint
6434 pspp_sheet_view_focus (GtkWidget        *widget,
6435                      GtkDirectionType  direction)
6436 {
6437   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
6438   GtkContainer *container = GTK_CONTAINER (widget);
6439   GtkWidget *focus_child;
6440
6441   if (!gtk_widget_is_sensitive (widget) || !gtk_widget_get_can_focus (widget))
6442     return FALSE;
6443
6444   focus_child = container->focus_child;
6445
6446   pspp_sheet_view_stop_editing (PSPP_SHEET_VIEW (widget), FALSE);
6447   /* Case 1.  Headers currently have focus. */
6448   if (focus_child)
6449     {
6450       switch (direction)
6451         {
6452         case GTK_DIR_LEFT:
6453         case GTK_DIR_RIGHT:
6454           pspp_sheet_view_header_focus (tree_view, direction, TRUE);
6455           return TRUE;
6456         case GTK_DIR_TAB_BACKWARD:
6457         case GTK_DIR_UP:
6458           return FALSE;
6459         case GTK_DIR_TAB_FORWARD:
6460         case GTK_DIR_DOWN:
6461           gtk_widget_grab_focus (widget);
6462           return TRUE;
6463         default:
6464           g_assert_not_reached ();
6465           return FALSE;
6466         }
6467     }
6468
6469   /* Case 2. We don't have focus at all. */
6470   if (!gtk_widget_has_focus (widget))
6471     {
6472       if (!pspp_sheet_view_header_focus (tree_view, direction, FALSE))
6473         gtk_widget_grab_focus (widget);
6474       return TRUE;
6475     }
6476
6477   /* Case 3. We have focus already. */
6478   if (direction == GTK_DIR_TAB_BACKWARD)
6479     return (pspp_sheet_view_header_focus (tree_view, direction, FALSE));
6480   else if (direction == GTK_DIR_TAB_FORWARD)
6481     return FALSE;
6482
6483   /* Other directions caught by the keybindings */
6484   gtk_widget_grab_focus (widget);
6485   return TRUE;
6486 }
6487
6488 static void
6489 pspp_sheet_view_grab_focus (GtkWidget *widget)
6490 {
6491   GTK_WIDGET_CLASS (pspp_sheet_view_parent_class)->grab_focus (widget);
6492
6493   pspp_sheet_view_focus_to_cursor (PSPP_SHEET_VIEW (widget));
6494 }
6495
6496 static void
6497 pspp_sheet_view_style_set (GtkWidget *widget,
6498                          GtkStyle *previous_style)
6499 {
6500   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
6501   GList *list;
6502   PsppSheetViewColumn *column;
6503
6504   if (gtk_widget_get_realized (widget))
6505     {
6506       gdk_window_set_back_pixmap (widget->window, NULL, FALSE);
6507       gdk_window_set_background (tree_view->priv->bin_window, &widget->style->base[widget->state]);
6508       gtk_style_set_background (widget->style, tree_view->priv->header_window, GTK_STATE_NORMAL);
6509
6510       pspp_sheet_view_set_grid_lines (tree_view, tree_view->priv->grid_lines);
6511     }
6512
6513   gtk_widget_style_get (widget,
6514                         "expander-size", &tree_view->priv->expander_size,
6515                         NULL);
6516   tree_view->priv->expander_size += EXPANDER_EXTRA_PADDING;
6517
6518   for (list = tree_view->priv->columns; list; list = list->next)
6519     {
6520       column = list->data;
6521       _pspp_sheet_view_column_cell_set_dirty (column);
6522     }
6523
6524   tree_view->priv->fixed_height = -1;
6525
6526   gtk_widget_queue_resize (widget);
6527 }
6528
6529
6530 static void
6531 pspp_sheet_view_set_focus_child (GtkContainer *container,
6532                                GtkWidget    *child)
6533 {
6534   PsppSheetView *tree_view = PSPP_SHEET_VIEW (container);
6535   GList *list;
6536
6537   for (list = tree_view->priv->columns; list; list = list->next)
6538     {
6539       if (PSPP_SHEET_VIEW_COLUMN (list->data)->button == child)
6540         {
6541           tree_view->priv->focus_column = PSPP_SHEET_VIEW_COLUMN (list->data);
6542           break;
6543         }
6544     }
6545
6546   GTK_CONTAINER_CLASS (pspp_sheet_view_parent_class)->set_focus_child (container, child);
6547 }
6548
6549 static void
6550 pspp_sheet_view_set_adjustments (PsppSheetView   *tree_view,
6551                                GtkAdjustment *hadj,
6552                                GtkAdjustment *vadj)
6553 {
6554   gboolean need_adjust = FALSE;
6555
6556   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
6557
6558   if (hadj)
6559     g_return_if_fail (GTK_IS_ADJUSTMENT (hadj));
6560   else
6561     hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
6562   if (vadj)
6563     g_return_if_fail (GTK_IS_ADJUSTMENT (vadj));
6564   else
6565     vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
6566
6567   if (tree_view->priv->hadjustment && (tree_view->priv->hadjustment != hadj))
6568     {
6569       g_signal_handlers_disconnect_by_func (tree_view->priv->hadjustment,
6570                                             pspp_sheet_view_adjustment_changed,
6571                                             tree_view);
6572       g_object_unref (tree_view->priv->hadjustment);
6573     }
6574
6575   if (tree_view->priv->vadjustment && (tree_view->priv->vadjustment != vadj))
6576     {
6577       g_signal_handlers_disconnect_by_func (tree_view->priv->vadjustment,
6578                                             pspp_sheet_view_adjustment_changed,
6579                                             tree_view);
6580       g_object_unref (tree_view->priv->vadjustment);
6581     }
6582
6583   if (tree_view->priv->hadjustment != hadj)
6584     {
6585       tree_view->priv->hadjustment = hadj;
6586       g_object_ref_sink (tree_view->priv->hadjustment);
6587
6588       g_signal_connect (tree_view->priv->hadjustment, "value-changed",
6589                         G_CALLBACK (pspp_sheet_view_adjustment_changed),
6590                         tree_view);
6591       need_adjust = TRUE;
6592     }
6593
6594   if (tree_view->priv->vadjustment != vadj)
6595     {
6596       tree_view->priv->vadjustment = vadj;
6597       g_object_ref_sink (tree_view->priv->vadjustment);
6598
6599       g_signal_connect (tree_view->priv->vadjustment, "value-changed",
6600                         G_CALLBACK (pspp_sheet_view_adjustment_changed),
6601                         tree_view);
6602       need_adjust = TRUE;
6603     }
6604
6605   if (need_adjust)
6606     pspp_sheet_view_adjustment_changed (NULL, tree_view);
6607 }
6608
6609
6610 static gboolean
6611 pspp_sheet_view_real_move_cursor (PsppSheetView       *tree_view,
6612                                 GtkMovementStep    step,
6613                                 gint               count)
6614 {
6615   GdkModifierType state;
6616
6617   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
6618   g_return_val_if_fail (step == GTK_MOVEMENT_LOGICAL_POSITIONS ||
6619                         step == GTK_MOVEMENT_VISUAL_POSITIONS ||
6620                         step == GTK_MOVEMENT_DISPLAY_LINES ||
6621                         step == GTK_MOVEMENT_PAGES ||
6622                         step == GTK_MOVEMENT_BUFFER_ENDS, FALSE);
6623
6624   if (tree_view->priv->row_count == 0)
6625     return FALSE;
6626   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
6627     return FALSE;
6628
6629   pspp_sheet_view_stop_editing (tree_view, FALSE);
6630   PSPP_SHEET_VIEW_SET_FLAG (tree_view, PSPP_SHEET_VIEW_DRAW_KEYFOCUS);
6631   gtk_widget_grab_focus (GTK_WIDGET (tree_view));
6632
6633   if (gtk_get_current_event_state (&state))
6634     {
6635       if ((state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
6636         tree_view->priv->ctrl_pressed = TRUE;
6637       if ((state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK)
6638         tree_view->priv->shift_pressed = TRUE;
6639     }
6640   /* else we assume not pressed */
6641
6642   switch (step)
6643     {
6644       /* currently we make no distinction.  When we go bi-di, we need to */
6645     case GTK_MOVEMENT_LOGICAL_POSITIONS:
6646     case GTK_MOVEMENT_VISUAL_POSITIONS:
6647       pspp_sheet_view_move_cursor_left_right (tree_view, count);
6648       break;
6649     case GTK_MOVEMENT_DISPLAY_LINES:
6650       pspp_sheet_view_move_cursor_up_down (tree_view, count);
6651       break;
6652     case GTK_MOVEMENT_PAGES:
6653       pspp_sheet_view_move_cursor_page_up_down (tree_view, count);
6654       break;
6655     case GTK_MOVEMENT_BUFFER_ENDS:
6656       pspp_sheet_view_move_cursor_start_end (tree_view, count);
6657       break;
6658     default:
6659       g_assert_not_reached ();
6660     }
6661
6662   tree_view->priv->ctrl_pressed = FALSE;
6663   tree_view->priv->shift_pressed = FALSE;
6664
6665   return TRUE;
6666 }
6667
6668 static void
6669 pspp_sheet_view_put (PsppSheetView *tree_view,
6670                    GtkWidget   *child_widget,
6671                    /* in bin_window coordinates */
6672                    gint         x,
6673                    gint         y,
6674                    gint         width,
6675                    gint         height)
6676 {
6677   PsppSheetViewChild *child;
6678   
6679   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
6680   g_return_if_fail (GTK_IS_WIDGET (child_widget));
6681
6682   child = g_slice_new (PsppSheetViewChild);
6683
6684   child->widget = child_widget;
6685   child->x = x;
6686   child->y = y;
6687   child->width = width;
6688   child->height = height;
6689
6690   tree_view->priv->children = g_list_append (tree_view->priv->children, child);
6691
6692   if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
6693     gtk_widget_set_parent_window (child->widget, tree_view->priv->bin_window);
6694   
6695   gtk_widget_set_parent (child_widget, GTK_WIDGET (tree_view));
6696 }
6697
6698 void
6699 _pspp_sheet_view_child_move_resize (PsppSheetView *tree_view,
6700                                   GtkWidget   *widget,
6701                                   /* in tree coordinates */
6702                                   gint         x,
6703                                   gint         y,
6704                                   gint         width,
6705                                   gint         height)
6706 {
6707   PsppSheetViewChild *child = NULL;
6708   GList *list;
6709   GdkRectangle allocation;
6710
6711   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
6712   g_return_if_fail (GTK_IS_WIDGET (widget));
6713
6714   for (list = tree_view->priv->children; list; list = list->next)
6715     {
6716       if (((PsppSheetViewChild *)list->data)->widget == widget)
6717         {
6718           child = list->data;
6719           break;
6720         }
6721     }
6722   if (child == NULL)
6723     return;
6724
6725   allocation.x = child->x = x;
6726   allocation.y = child->y = y;
6727   allocation.width = child->width = width;
6728   allocation.height = child->height = height;
6729
6730   if (gtk_widget_get_realized (widget))
6731     gtk_widget_size_allocate (widget, &allocation);
6732 }
6733
6734
6735 /* TreeModel Callbacks
6736  */
6737
6738 static void
6739 pspp_sheet_view_row_changed (GtkTreeModel *model,
6740                            GtkTreePath  *path,
6741                            GtkTreeIter  *iter,
6742                            gpointer      data)
6743 {
6744   PsppSheetView *tree_view = (PsppSheetView *)data;
6745   int node;
6746   gboolean free_path = FALSE;
6747   GtkTreePath *cursor_path;
6748
6749   g_return_if_fail (path != NULL || iter != NULL);
6750
6751   if (tree_view->priv->cursor != NULL)
6752     cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
6753   else
6754     cursor_path = NULL;
6755
6756   if (tree_view->priv->edited_column &&
6757       (cursor_path == NULL || gtk_tree_path_compare (cursor_path, path) == 0))
6758     pspp_sheet_view_stop_editing (tree_view, TRUE);
6759
6760   if (cursor_path != NULL)
6761     gtk_tree_path_free (cursor_path);
6762
6763   if (path == NULL)
6764     {
6765       path = gtk_tree_model_get_path (model, iter);
6766       free_path = TRUE;
6767     }
6768   else if (iter == NULL)
6769     gtk_tree_model_get_iter (model, iter, path);
6770
6771   _pspp_sheet_view_find_node (tree_view,
6772                               path,
6773                               &node);
6774
6775   if (node >= 0)
6776     {
6777       if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
6778         pspp_sheet_view_node_queue_redraw (tree_view, node);
6779     }
6780   
6781   if (free_path)
6782     gtk_tree_path_free (path);
6783 }
6784
6785 static void
6786 pspp_sheet_view_row_inserted (GtkTreeModel *model,
6787                             GtkTreePath  *path,
6788                             GtkTreeIter  *iter,
6789                             gpointer      data)
6790 {
6791   PsppSheetView *tree_view = (PsppSheetView *) data;
6792   gint *indices;
6793   int tmpnode = -1;
6794   gint height = tree_view->priv->fixed_height;
6795   gboolean free_path = FALSE;
6796   gboolean node_visible = TRUE;
6797
6798   g_return_if_fail (path != NULL || iter != NULL);
6799
6800   if (path == NULL)
6801     {
6802       path = gtk_tree_model_get_path (model, iter);
6803       free_path = TRUE;
6804     }
6805   else if (iter == NULL)
6806     gtk_tree_model_get_iter (model, iter, path);
6807
6808   tree_view->priv->row_count = gtk_tree_model_iter_n_children (model, NULL);
6809
6810   /* Update all row-references */
6811   gtk_tree_row_reference_inserted (G_OBJECT (data), path);
6812   indices = gtk_tree_path_get_indices (path);
6813   tmpnode = indices[0];
6814
6815   range_tower_insert0 (tree_view->priv->selected, tmpnode, 1);
6816
6817   if (height > 0)
6818     {
6819       if (node_visible && node_is_visible (tree_view, tmpnode))
6820         gtk_widget_queue_resize (GTK_WIDGET (tree_view));
6821       else
6822         gtk_widget_queue_resize_no_redraw (GTK_WIDGET (tree_view));
6823     }
6824   else
6825     install_presize_handler (tree_view);
6826   if (free_path)
6827     gtk_tree_path_free (path);
6828 }
6829
6830 static void
6831 pspp_sheet_view_row_deleted (GtkTreeModel *model,
6832                            GtkTreePath  *path,
6833                            gpointer      data)
6834 {
6835   PsppSheetView *tree_view = (PsppSheetView *)data;
6836   int node;
6837
6838   g_return_if_fail (path != NULL);
6839
6840   gtk_tree_row_reference_deleted (G_OBJECT (data), path);
6841
6842   _pspp_sheet_view_find_node (tree_view, path, &node);
6843
6844   if (node < 0)
6845     return;
6846
6847   range_tower_delete (tree_view->priv->selected, node, 1);
6848
6849   /* Ensure we don't have a dangling pointer to a dead node */
6850   ensure_unprelighted (tree_view);
6851
6852   /* Cancel editting if we've started */
6853   pspp_sheet_view_stop_editing (tree_view, TRUE);
6854
6855   if (tree_view->priv->destroy_count_func)
6856     {
6857       gint child_count = 0;
6858       tree_view->priv->destroy_count_func (tree_view, path, child_count, tree_view->priv->destroy_count_data);
6859     }
6860
6861   tree_view->priv->row_count = gtk_tree_model_iter_n_children (model, NULL);
6862
6863   if (! gtk_tree_row_reference_valid (tree_view->priv->top_row))
6864     {
6865       gtk_tree_row_reference_free (tree_view->priv->top_row);
6866       tree_view->priv->top_row = NULL;
6867     }
6868
6869   install_scroll_sync_handler (tree_view);
6870
6871   gtk_widget_queue_resize (GTK_WIDGET (tree_view));
6872
6873 #if 0
6874   if (helper_data.changed)
6875     g_signal_emit_by_name (tree_view->priv->selection, "changed");
6876 #endif
6877 }
6878
6879 static void
6880 pspp_sheet_view_rows_reordered (GtkTreeModel *model,
6881                               GtkTreePath  *parent,
6882                               GtkTreeIter  *iter,
6883                               gint         *new_order,
6884                               gpointer      data)
6885 {
6886   PsppSheetView *tree_view = PSPP_SHEET_VIEW (data);
6887   gint len;
6888
6889   /* XXX need to adjust selection */
6890   len = gtk_tree_model_iter_n_children (model, iter);
6891
6892   if (len < 2)
6893     return;
6894
6895   gtk_tree_row_reference_reordered (G_OBJECT (data),
6896                                     parent,
6897                                     iter,
6898                                     new_order);
6899
6900   if (gtk_tree_path_get_depth (parent) != 0)
6901     return;
6902
6903   if (tree_view->priv->edited_column)
6904     pspp_sheet_view_stop_editing (tree_view, TRUE);
6905
6906   /* we need to be unprelighted */
6907   ensure_unprelighted (tree_view);
6908
6909   gtk_widget_queue_draw (GTK_WIDGET (tree_view));
6910
6911   pspp_sheet_view_dy_to_top_row (tree_view);
6912 }
6913
6914
6915 /* Internal tree functions
6916  */
6917
6918
6919 static void
6920 pspp_sheet_view_get_background_xrange (PsppSheetView       *tree_view,
6921                                      PsppSheetViewColumn *column,
6922                                      gint              *x1,
6923                                      gint              *x2)
6924 {
6925   PsppSheetViewColumn *tmp_column = NULL;
6926   gint total_width;
6927   GList *list;
6928   gboolean rtl;
6929
6930   if (x1)
6931     *x1 = 0;
6932
6933   if (x2)
6934     *x2 = 0;
6935
6936   rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
6937
6938   total_width = 0;
6939   for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns));
6940        list;
6941        list = (rtl ? list->prev : list->next))
6942     {
6943       tmp_column = list->data;
6944
6945       if (tmp_column == column)
6946         break;
6947
6948       if (tmp_column->visible)
6949         total_width += tmp_column->width;
6950     }
6951
6952   if (tmp_column != column)
6953     {
6954       g_warning (G_STRLOC": passed-in column isn't in the tree");
6955       return;
6956     }
6957
6958   if (x1)
6959     *x1 = total_width;
6960
6961   if (x2)
6962     {
6963       if (column->visible)
6964         *x2 = total_width + column->width;
6965       else
6966         *x2 = total_width; /* width of 0 */
6967     }
6968 }
6969
6970 /* Make sure the node is visible vertically */
6971 static void
6972 pspp_sheet_view_clamp_node_visible (PsppSheetView *tree_view,
6973                                     int node)
6974 {
6975   gint node_dy, height;
6976   GtkTreePath *path = NULL;
6977
6978   if (!gtk_widget_get_realized (GTK_WIDGET (tree_view)))
6979     return;
6980
6981   /* just return if the node is visible, avoiding a costly expose */
6982   node_dy = pspp_sheet_view_node_find_offset (tree_view, node);
6983   height = ROW_HEIGHT (tree_view);
6984   if (node_dy >= tree_view->priv->vadjustment->value
6985       && node_dy + height <= (tree_view->priv->vadjustment->value
6986                               + tree_view->priv->vadjustment->page_size))
6987     return;
6988
6989   path = _pspp_sheet_view_find_path (tree_view, node);
6990   if (path)
6991     {
6992       /* We process updates because we want to clear old selected items when we scroll.
6993        * if this is removed, we get a "selection streak" at the bottom. */
6994       gdk_window_process_updates (tree_view->priv->bin_window, TRUE);
6995       pspp_sheet_view_scroll_to_cell (tree_view, path, NULL, FALSE, 0.0, 0.0);
6996       gtk_tree_path_free (path);
6997     }
6998 }
6999
7000 static void
7001 pspp_sheet_view_clamp_column_visible (PsppSheetView       *tree_view,
7002                                     PsppSheetViewColumn *column,
7003                                     gboolean           focus_to_cell)
7004 {
7005   gint x, width;
7006
7007   if (column == NULL)
7008     return;
7009
7010   x = column->button->allocation.x;
7011   width = column->button->allocation.width;
7012
7013   if (width > tree_view->priv->hadjustment->page_size)
7014     {
7015       /* The column is larger than the horizontal page size.  If the
7016        * column has cells which can be focussed individually, then we make
7017        * sure the cell which gets focus is fully visible (if even the
7018        * focus cell is bigger than the page size, we make sure the
7019        * left-hand side of the cell is visible).
7020        *
7021        * If the column does not have those so-called special cells, we
7022        * make sure the left-hand side of the column is visible.
7023        */
7024
7025       if (focus_to_cell && pspp_sheet_view_has_special_cell (tree_view))
7026         {
7027           GtkTreePath *cursor_path;
7028           GdkRectangle background_area, cell_area, focus_area;
7029
7030           cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
7031
7032           pspp_sheet_view_get_cell_area (tree_view,
7033                                        cursor_path, column, &cell_area);
7034           pspp_sheet_view_get_background_area (tree_view,
7035                                              cursor_path, column,
7036                                              &background_area);
7037
7038           gtk_tree_path_free (cursor_path);
7039
7040           _pspp_sheet_view_column_get_focus_area (column,
7041                                                 &background_area,
7042                                                 &cell_area,
7043                                                 &focus_area);
7044
7045           x = focus_area.x;
7046           width = focus_area.width;
7047
7048           if (width < tree_view->priv->hadjustment->page_size)
7049             {
7050               if ((tree_view->priv->hadjustment->value + tree_view->priv->hadjustment->page_size) < (x + width))
7051                 gtk_adjustment_set_value (tree_view->priv->hadjustment,
7052                                           x + width - tree_view->priv->hadjustment->page_size);
7053               else if (tree_view->priv->hadjustment->value > x)
7054                 gtk_adjustment_set_value (tree_view->priv->hadjustment, x);
7055             }
7056         }
7057
7058       gtk_adjustment_set_value (tree_view->priv->hadjustment,
7059                                 CLAMP (x,
7060                                        tree_view->priv->hadjustment->lower,
7061                                        tree_view->priv->hadjustment->upper
7062                                        - tree_view->priv->hadjustment->page_size));
7063     }
7064   else
7065     {
7066       if ((tree_view->priv->hadjustment->value + tree_view->priv->hadjustment->page_size) < (x + width))
7067           gtk_adjustment_set_value (tree_view->priv->hadjustment,
7068                                     x + width - tree_view->priv->hadjustment->page_size);
7069       else if (tree_view->priv->hadjustment->value > x)
7070         gtk_adjustment_set_value (tree_view->priv->hadjustment, x);
7071   }
7072 }
7073
7074 GtkTreePath *
7075 _pspp_sheet_view_find_path (PsppSheetView *tree_view,
7076                             int node)
7077 {
7078   GtkTreePath *path;
7079
7080   path = gtk_tree_path_new ();
7081   if (node >= 0)
7082     gtk_tree_path_append_index (path, node);
7083   return path;
7084 }
7085
7086 void
7087 _pspp_sheet_view_find_node (PsppSheetView  *tree_view,
7088                           GtkTreePath  *path,
7089                           int *node)
7090 {
7091   gint *indices = gtk_tree_path_get_indices (path);
7092   gint depth = gtk_tree_path_get_depth (path);
7093
7094   *node = -1;
7095   if (depth == 0 || indices[0] < 0 || indices[0] >= tree_view->priv->row_count)
7096     return;
7097   *node = indices[0];
7098 }
7099
7100 static void
7101 pspp_sheet_view_add_move_binding (GtkBindingSet  *binding_set,
7102                                 guint           keyval,
7103                                 guint           modmask,
7104                                 gboolean        add_shifted_binding,
7105                                 GtkMovementStep step,
7106                                 gint            count)
7107 {
7108   
7109   gtk_binding_entry_add_signal (binding_set, keyval, modmask,
7110                                 "move-cursor", 2,
7111                                 G_TYPE_ENUM, step,
7112                                 G_TYPE_INT, count);
7113
7114   if (add_shifted_binding)
7115     gtk_binding_entry_add_signal (binding_set, keyval, GDK_SHIFT_MASK,
7116                                   "move-cursor", 2,
7117                                   G_TYPE_ENUM, step,
7118                                   G_TYPE_INT, count);
7119
7120   if ((modmask & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
7121    return;
7122
7123   gtk_binding_entry_add_signal (binding_set, keyval, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
7124                                 "move-cursor", 2,
7125                                 G_TYPE_ENUM, step,
7126                                 G_TYPE_INT, count);
7127
7128   gtk_binding_entry_add_signal (binding_set, keyval, GDK_CONTROL_MASK,
7129                                 "move-cursor", 2,
7130                                 G_TYPE_ENUM, step,
7131                                 G_TYPE_INT, count);
7132 }
7133
7134 static void
7135 pspp_sheet_view_set_column_drag_info (PsppSheetView       *tree_view,
7136                                     PsppSheetViewColumn *column)
7137 {
7138   PsppSheetViewColumn *left_column;
7139   PsppSheetViewColumn *cur_column = NULL;
7140   PsppSheetViewColumnReorder *reorder;
7141   gboolean rtl;
7142   GList *tmp_list;
7143   gint left;
7144
7145   /* We want to precalculate the motion list such that we know what column slots
7146    * are available.
7147    */
7148   left_column = NULL;
7149   rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
7150
7151   /* First, identify all possible drop spots */
7152   if (rtl)
7153     tmp_list = g_list_last (tree_view->priv->columns);
7154   else
7155     tmp_list = g_list_first (tree_view->priv->columns);
7156
7157   while (tmp_list)
7158     {
7159       cur_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
7160       tmp_list = rtl?g_list_previous (tmp_list):g_list_next (tmp_list);
7161
7162       if (cur_column->visible == FALSE)
7163         continue;
7164
7165       /* If it's not the column moving and func tells us to skip over the column, we continue. */
7166       if (left_column != column && cur_column != column &&
7167           tree_view->priv->column_drop_func &&
7168           ! tree_view->priv->column_drop_func (tree_view, column, left_column, cur_column, tree_view->priv->column_drop_func_data))
7169         {
7170           left_column = cur_column;
7171           continue;
7172         }
7173       reorder = g_slice_new0 (PsppSheetViewColumnReorder);
7174       reorder->left_column = left_column;
7175       left_column = reorder->right_column = cur_column;
7176
7177       tree_view->priv->column_drag_info = g_list_append (tree_view->priv->column_drag_info, reorder);
7178     }
7179
7180   /* Add the last one */
7181   if (tree_view->priv->column_drop_func == NULL ||
7182       ((left_column != column) &&
7183        tree_view->priv->column_drop_func (tree_view, column, left_column, NULL, tree_view->priv->column_drop_func_data)))
7184     {
7185       reorder = g_slice_new0 (PsppSheetViewColumnReorder);
7186       reorder->left_column = left_column;
7187       reorder->right_column = NULL;
7188       tree_view->priv->column_drag_info = g_list_append (tree_view->priv->column_drag_info, reorder);
7189     }
7190
7191   /* We quickly check to see if it even makes sense to reorder columns. */
7192   /* If there is nothing that can be moved, then we return */
7193
7194   if (tree_view->priv->column_drag_info == NULL)
7195     return;
7196
7197   /* We know there are always 2 slots possbile, as you can always return column. */
7198   /* If that's all there is, return */
7199   if (tree_view->priv->column_drag_info->next == NULL || 
7200       (tree_view->priv->column_drag_info->next->next == NULL &&
7201        ((PsppSheetViewColumnReorder *)tree_view->priv->column_drag_info->data)->right_column == column &&
7202        ((PsppSheetViewColumnReorder *)tree_view->priv->column_drag_info->next->data)->left_column == column))
7203     {
7204       for (tmp_list = tree_view->priv->column_drag_info; tmp_list; tmp_list = tmp_list->next)
7205         g_slice_free (PsppSheetViewColumnReorder, tmp_list->data);
7206       g_list_free (tree_view->priv->column_drag_info);
7207       tree_view->priv->column_drag_info = NULL;
7208       return;
7209     }
7210   /* We fill in the ranges for the columns, now that we've isolated them */
7211   left = - TREE_VIEW_COLUMN_DRAG_DEAD_MULTIPLIER (tree_view);
7212
7213   for (tmp_list = tree_view->priv->column_drag_info; tmp_list; tmp_list = tmp_list->next)
7214     {
7215       reorder = (PsppSheetViewColumnReorder *) tmp_list->data;
7216
7217       reorder->left_align = left;
7218       if (tmp_list->next != NULL)
7219         {
7220           g_assert (tmp_list->next->data);
7221           left = reorder->right_align = (reorder->right_column->button->allocation.x +
7222                                          reorder->right_column->button->allocation.width +
7223                                          ((PsppSheetViewColumnReorder *)tmp_list->next->data)->left_column->button->allocation.x)/2;
7224         }
7225       else
7226         {
7227           gint width;
7228
7229           gdk_drawable_get_size (tree_view->priv->header_window, &width, NULL);
7230           reorder->right_align = width + TREE_VIEW_COLUMN_DRAG_DEAD_MULTIPLIER (tree_view);
7231         }
7232     }
7233 }
7234
7235 void
7236 _pspp_sheet_view_column_start_drag (PsppSheetView       *tree_view,
7237                                   PsppSheetViewColumn *column)
7238 {
7239   GdkEvent *send_event;
7240   GtkAllocation allocation;
7241   gint x, y, width, height;
7242   GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (tree_view));
7243   GdkDisplay *display = gdk_screen_get_display (screen);
7244
7245   g_return_if_fail (tree_view->priv->column_drag_info == NULL);
7246   g_return_if_fail (tree_view->priv->cur_reorder == NULL);
7247
7248   pspp_sheet_view_set_column_drag_info (tree_view, column);
7249
7250   if (tree_view->priv->column_drag_info == NULL)
7251     return;
7252
7253   if (tree_view->priv->drag_window == NULL)
7254     {
7255       GdkWindowAttr attributes;
7256       guint attributes_mask;
7257
7258       attributes.window_type = GDK_WINDOW_CHILD;
7259       attributes.wclass = GDK_INPUT_OUTPUT;
7260       attributes.x = column->button->allocation.x;
7261       attributes.y = 0;
7262       attributes.width = column->button->allocation.width;
7263       attributes.height = column->button->allocation.height;
7264       attributes.visual = gtk_widget_get_visual (GTK_WIDGET (tree_view));
7265       attributes.colormap = gtk_widget_get_colormap (GTK_WIDGET (tree_view));
7266       attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK;
7267       attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
7268
7269       tree_view->priv->drag_window = gdk_window_new (tree_view->priv->bin_window,
7270                                                      &attributes,
7271                                                      attributes_mask);
7272       gdk_window_set_user_data (tree_view->priv->drag_window, GTK_WIDGET (tree_view));
7273     }
7274
7275   gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME);
7276   gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME);
7277
7278   gtk_grab_remove (column->button);
7279
7280   send_event = gdk_event_new (GDK_LEAVE_NOTIFY);
7281   send_event->crossing.send_event = TRUE;
7282   send_event->crossing.window = g_object_ref (GTK_BUTTON (column->button)->event_window);
7283   send_event->crossing.subwindow = NULL;
7284   send_event->crossing.detail = GDK_NOTIFY_ANCESTOR;
7285   send_event->crossing.time = GDK_CURRENT_TIME;
7286
7287   gtk_propagate_event (column->button, send_event);
7288   gdk_event_free (send_event);
7289
7290   send_event = gdk_event_new (GDK_BUTTON_RELEASE);
7291   send_event->button.window = g_object_ref (gdk_screen_get_root_window (screen));
7292   send_event->button.send_event = TRUE;
7293   send_event->button.time = GDK_CURRENT_TIME;
7294   send_event->button.x = -1;
7295   send_event->button.y = -1;
7296   send_event->button.axes = NULL;
7297   send_event->button.state = 0;
7298   send_event->button.button = 1;
7299   send_event->button.device = gdk_display_get_core_pointer (display);
7300   send_event->button.x_root = 0;
7301   send_event->button.y_root = 0;
7302
7303   gtk_propagate_event (column->button, send_event);
7304   gdk_event_free (send_event);
7305
7306   /* Kids, don't try this at home */
7307   g_object_ref (column->button);
7308   gtk_container_remove (GTK_CONTAINER (tree_view), column->button);
7309   gtk_widget_set_parent_window (column->button, tree_view->priv->drag_window);
7310   gtk_widget_set_parent (column->button, GTK_WIDGET (tree_view));
7311   g_object_unref (column->button);
7312
7313   tree_view->priv->drag_column_x = column->button->allocation.x;
7314   allocation = column->button->allocation;
7315   allocation.x = 0;
7316   gtk_widget_size_allocate (column->button, &allocation);
7317   gtk_widget_set_parent_window (column->button, tree_view->priv->drag_window);
7318
7319   tree_view->priv->drag_column = column;
7320   gdk_window_show (tree_view->priv->drag_window);
7321
7322   gdk_window_get_origin (tree_view->priv->header_window, &x, &y);
7323   gdk_drawable_get_size (tree_view->priv->header_window, &width, &height);
7324
7325   gtk_widget_grab_focus (GTK_WIDGET (tree_view));
7326   while (gtk_events_pending ())
7327     gtk_main_iteration ();
7328
7329   PSPP_SHEET_VIEW_SET_FLAG (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_DRAG);
7330   gdk_pointer_grab (tree_view->priv->drag_window,
7331                     FALSE,
7332                     GDK_POINTER_MOTION_MASK|GDK_BUTTON_RELEASE_MASK,
7333                     NULL, NULL, GDK_CURRENT_TIME);
7334   gdk_keyboard_grab (tree_view->priv->drag_window,
7335                      FALSE,
7336                      GDK_CURRENT_TIME);
7337 }
7338
7339 void
7340 _pspp_sheet_view_queue_draw_node (PsppSheetView        *tree_view,
7341                                 int node,
7342                                 const GdkRectangle *clip_rect)
7343 {
7344   GdkRectangle rect;
7345
7346   if (!gtk_widget_get_realized (GTK_WIDGET (tree_view)))
7347     return;
7348
7349   rect.x = 0;
7350   rect.width = MAX (tree_view->priv->width, GTK_WIDGET (tree_view)->allocation.width);
7351
7352   rect.y = BACKGROUND_FIRST_PIXEL (tree_view, node);
7353   rect.height = ROW_HEIGHT (tree_view);
7354
7355   if (clip_rect)
7356     {
7357       GdkRectangle new_rect;
7358
7359       gdk_rectangle_intersect (clip_rect, &rect, &new_rect);
7360
7361       gdk_window_invalidate_rect (tree_view->priv->bin_window, &new_rect, TRUE);
7362     }
7363   else
7364     {
7365       gdk_window_invalidate_rect (tree_view->priv->bin_window, &rect, TRUE);
7366     }
7367 }
7368
7369 static void
7370 pspp_sheet_view_queue_draw_path (PsppSheetView        *tree_view,
7371                                GtkTreePath        *path,
7372                                const GdkRectangle *clip_rect)
7373 {
7374   int node = -1;
7375
7376   _pspp_sheet_view_find_node (tree_view, path, &node);
7377
7378   if (node)
7379     _pspp_sheet_view_queue_draw_node (tree_view, node, clip_rect);
7380 }
7381
7382 static void
7383 pspp_sheet_view_focus_to_cursor (PsppSheetView *tree_view)
7384
7385 {
7386   GtkTreePath *cursor_path;
7387
7388   if ((tree_view->priv->row_count == 0) ||
7389       (! gtk_widget_get_realized (GTK_WIDGET (tree_view))))
7390     return;
7391
7392   cursor_path = NULL;
7393   if (tree_view->priv->cursor)
7394     cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
7395
7396   if (cursor_path == NULL)
7397     {
7398       /* Consult the selection before defaulting to the
7399        * first focusable element
7400        */
7401       GList *selected_rows;
7402       GtkTreeModel *model;
7403       PsppSheetSelection *selection;
7404
7405       selection = pspp_sheet_view_get_selection (tree_view);
7406       selected_rows = pspp_sheet_selection_get_selected_rows (selection, &model);
7407
7408       if (selected_rows)
7409         {
7410           cursor_path = gtk_tree_path_copy((const GtkTreePath *)(selected_rows->data));
7411           g_list_foreach (selected_rows, (GFunc)gtk_tree_path_free, NULL);
7412           g_list_free (selected_rows);
7413         }
7414       else
7415         {
7416           cursor_path = gtk_tree_path_new_first ();
7417           search_first_focusable_path (tree_view, &cursor_path,
7418                                        TRUE, NULL);
7419         }
7420
7421       gtk_tree_row_reference_free (tree_view->priv->cursor);
7422       tree_view->priv->cursor = NULL;
7423
7424       if (cursor_path)
7425         {
7426           if (tree_view->priv->selection->type == GTK_SELECTION_MULTIPLE)
7427             pspp_sheet_view_real_set_cursor (tree_view, cursor_path, FALSE, FALSE);
7428           else
7429             pspp_sheet_view_real_set_cursor (tree_view, cursor_path, TRUE, FALSE);
7430         }
7431     }
7432
7433   if (cursor_path)
7434     {
7435       PSPP_SHEET_VIEW_SET_FLAG (tree_view, PSPP_SHEET_VIEW_DRAW_KEYFOCUS);
7436
7437       pspp_sheet_view_queue_draw_path (tree_view, cursor_path, NULL);
7438       gtk_tree_path_free (cursor_path);
7439
7440       if (tree_view->priv->focus_column == NULL)
7441         {
7442           GList *list;
7443           for (list = tree_view->priv->columns; list; list = list->next)
7444             {
7445               if (PSPP_SHEET_VIEW_COLUMN (list->data)->visible)
7446                 {
7447                   tree_view->priv->focus_column = PSPP_SHEET_VIEW_COLUMN (list->data);
7448                   break;
7449                 }
7450             }
7451         }
7452     }
7453 }
7454
7455 static void
7456 pspp_sheet_view_move_cursor_up_down (PsppSheetView *tree_view,
7457                                    gint         count)
7458 {
7459   gint selection_count;
7460   int cursor_node = -1;
7461   int new_cursor_node = -1;
7462   GtkTreePath *cursor_path = NULL;
7463   gboolean grab_focus = TRUE;
7464
7465   if (! gtk_widget_has_focus (GTK_WIDGET (tree_view)))
7466     return;
7467
7468   cursor_path = NULL;
7469   if (!gtk_tree_row_reference_valid (tree_view->priv->cursor))
7470     /* FIXME: we lost the cursor; should we get the first? */
7471     return;
7472
7473   cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
7474   _pspp_sheet_view_find_node (tree_view, cursor_path, &cursor_node);
7475
7476   if (cursor_node < 0)
7477     /* FIXME: we lost the cursor; should we get the first? */
7478     return;
7479
7480   selection_count = pspp_sheet_selection_count_selected_rows (tree_view->priv->selection);
7481
7482   if (selection_count == 0
7483       && tree_view->priv->selection->type != GTK_SELECTION_NONE
7484       && !tree_view->priv->ctrl_pressed)
7485     {
7486       /* Don't move the cursor, but just select the current node */
7487       new_cursor_node = cursor_node;
7488     }
7489   else
7490     {
7491       if (count == -1)
7492         new_cursor_node = pspp_sheet_view_node_prev (tree_view, cursor_node);
7493       else
7494         new_cursor_node = pspp_sheet_view_node_next (tree_view, cursor_node);
7495     }
7496
7497   gtk_tree_path_free (cursor_path);
7498
7499   if (new_cursor_node)
7500     {
7501       cursor_path = _pspp_sheet_view_find_path (tree_view, new_cursor_node);
7502
7503       search_first_focusable_path (tree_view, &cursor_path,
7504                                    (count != -1),
7505                                    &new_cursor_node);
7506
7507       if (cursor_path)
7508         gtk_tree_path_free (cursor_path);
7509     }
7510
7511   /*
7512    * If the list has only one item and multi-selection is set then select
7513    * the row (if not yet selected).
7514    */
7515   if (tree_view->priv->selection->type == GTK_SELECTION_MULTIPLE &&
7516       new_cursor_node < 0)
7517     {
7518       if (count == -1)
7519         new_cursor_node = pspp_sheet_view_node_next (tree_view, cursor_node);
7520       else
7521         new_cursor_node = pspp_sheet_view_node_prev (tree_view, cursor_node);
7522
7523       if (new_cursor_node < 0
7524           && !pspp_sheet_view_node_is_selected (tree_view, cursor_node))
7525         {
7526           new_cursor_node = cursor_node;
7527         }
7528       else
7529         {
7530           new_cursor_node = -1;
7531         }
7532     }
7533
7534   if (new_cursor_node >= 0)
7535     {
7536       cursor_path = _pspp_sheet_view_find_path (tree_view, new_cursor_node);
7537       pspp_sheet_view_real_set_cursor (tree_view, cursor_path, TRUE, TRUE);
7538       gtk_tree_path_free (cursor_path);
7539     }
7540   else
7541     {
7542       pspp_sheet_view_clamp_node_visible (tree_view, cursor_node);
7543
7544       if (!tree_view->priv->shift_pressed)
7545         {
7546           if (! gtk_widget_keynav_failed (GTK_WIDGET (tree_view),
7547                                           count < 0 ?
7548                                           GTK_DIR_UP : GTK_DIR_DOWN))
7549             {
7550               GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (tree_view));
7551
7552               if (toplevel)
7553                 gtk_widget_child_focus (toplevel,
7554                                         count < 0 ?
7555                                         GTK_DIR_TAB_BACKWARD :
7556                                         GTK_DIR_TAB_FORWARD);
7557
7558               grab_focus = FALSE;
7559             }
7560         }
7561       else
7562         {
7563           gtk_widget_error_bell (GTK_WIDGET (tree_view));
7564         }
7565     }
7566
7567   if (grab_focus)
7568     gtk_widget_grab_focus (GTK_WIDGET (tree_view));
7569 }
7570
7571 static void
7572 pspp_sheet_view_move_cursor_page_up_down (PsppSheetView *tree_view,
7573                                         gint         count)
7574 {
7575   int cursor_node = -1;
7576   GtkTreePath *old_cursor_path = NULL;
7577   GtkTreePath *cursor_path = NULL;
7578   int start_cursor_node = -1;
7579   gint y;
7580   gint window_y;
7581   gint vertical_separator;
7582
7583   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
7584     return;
7585
7586   if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
7587     old_cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
7588   else
7589     /* This is sorta weird.  Focus in should give us a cursor */
7590     return;
7591
7592   gtk_widget_style_get (GTK_WIDGET (tree_view), "vertical-separator", &vertical_separator, NULL);
7593   _pspp_sheet_view_find_node (tree_view, old_cursor_path, &cursor_node);
7594
7595   if (cursor_node < 0)
7596     {
7597       /* FIXME: we lost the cursor.  Should we try to get one? */
7598       gtk_tree_path_free (old_cursor_path);
7599       return;
7600     }
7601
7602   y = pspp_sheet_view_node_find_offset (tree_view, cursor_node);
7603   window_y = RBTREE_Y_TO_TREE_WINDOW_Y (tree_view, y);
7604   y += tree_view->priv->cursor_offset;
7605   y += count * (int)tree_view->priv->vadjustment->page_increment;
7606   y = CLAMP (y, (gint)tree_view->priv->vadjustment->lower,  (gint)tree_view->priv->vadjustment->upper - vertical_separator);
7607
7608   if (y >= tree_view->priv->height)
7609     y = tree_view->priv->height - 1;
7610
7611   tree_view->priv->cursor_offset =
7612     pspp_sheet_view_find_offset (tree_view, y, &cursor_node);
7613
7614   if (tree_view->priv->cursor_offset > BACKGROUND_HEIGHT (tree_view))
7615     {
7616       cursor_node = pspp_sheet_view_node_next (tree_view, cursor_node);
7617       tree_view->priv->cursor_offset -= BACKGROUND_HEIGHT (tree_view);
7618     }
7619
7620   y -= tree_view->priv->cursor_offset;
7621   cursor_path = _pspp_sheet_view_find_path (tree_view, cursor_node);
7622
7623   start_cursor_node = cursor_node;
7624
7625   if (! search_first_focusable_path (tree_view, &cursor_path,
7626                                      (count != -1),
7627                                      &cursor_node))
7628     {
7629       /* It looks like we reached the end of the view without finding
7630        * a focusable row.  We will step backwards to find the last
7631        * focusable row.
7632        */
7633       cursor_node = start_cursor_node;
7634       cursor_path = _pspp_sheet_view_find_path (tree_view, cursor_node);
7635
7636       search_first_focusable_path (tree_view, &cursor_path,
7637                                    (count == -1),
7638                                    &cursor_node);
7639     }
7640
7641   if (!cursor_path)
7642     goto cleanup;
7643
7644   /* update y */
7645   y = pspp_sheet_view_node_find_offset (tree_view, cursor_node);
7646
7647   pspp_sheet_view_real_set_cursor (tree_view, cursor_path, TRUE, FALSE);
7648
7649   y -= window_y;
7650   pspp_sheet_view_scroll_to_point (tree_view, -1, y);
7651   pspp_sheet_view_clamp_node_visible (tree_view, cursor_node);
7652   _pspp_sheet_view_queue_draw_node (tree_view, cursor_node, NULL);
7653
7654   if (!gtk_tree_path_compare (old_cursor_path, cursor_path))
7655     gtk_widget_error_bell (GTK_WIDGET (tree_view));
7656
7657   gtk_widget_grab_focus (GTK_WIDGET (tree_view));
7658
7659 cleanup:
7660   gtk_tree_path_free (old_cursor_path);
7661   gtk_tree_path_free (cursor_path);
7662 }
7663
7664 static void
7665 pspp_sheet_view_move_cursor_left_right (PsppSheetView *tree_view,
7666                                       gint         count)
7667 {
7668   int cursor_node = -1;
7669   GtkTreePath *cursor_path = NULL;
7670   PsppSheetViewColumn *column;
7671   GtkTreeIter iter;
7672   GList *list;
7673   gboolean found_column = FALSE;
7674   gboolean rtl;
7675
7676   rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
7677
7678   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
7679     return;
7680
7681   if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
7682     cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
7683   else
7684     return;
7685
7686   _pspp_sheet_view_find_node (tree_view, cursor_path, &cursor_node);
7687   if (cursor_node < 0)
7688     return;
7689   if (gtk_tree_model_get_iter (tree_view->priv->model, &iter, cursor_path) == FALSE)
7690     {
7691       gtk_tree_path_free (cursor_path);
7692       return;
7693     }
7694   gtk_tree_path_free (cursor_path);
7695
7696   list = rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns);
7697   if (tree_view->priv->focus_column)
7698     {
7699       for (; list; list = (rtl ? list->prev : list->next))
7700         {
7701           if (list->data == tree_view->priv->focus_column)
7702             break;
7703         }
7704     }
7705
7706   while (list)
7707     {
7708       gboolean left, right;
7709
7710       column = list->data;
7711       if (column->visible == FALSE)
7712         goto loop_end;
7713
7714       pspp_sheet_view_column_cell_set_cell_data (column,
7715                                                tree_view->priv->model,
7716                                                &iter);
7717
7718       if (rtl)
7719         {
7720           right = list->prev ? TRUE : FALSE;
7721           left = list->next ? TRUE : FALSE;
7722         }
7723       else
7724         {
7725           left = list->prev ? TRUE : FALSE;
7726           right = list->next ? TRUE : FALSE;
7727         }
7728
7729       if (_pspp_sheet_view_column_cell_focus (column, count, left, right))
7730         {
7731           tree_view->priv->focus_column = column;
7732           found_column = TRUE;
7733           break;
7734         }
7735     loop_end:
7736       if (count == 1)
7737         list = rtl ? list->prev : list->next;
7738       else
7739         list = rtl ? list->next : list->prev;
7740     }
7741
7742   if (found_column)
7743     {
7744       if (!pspp_sheet_view_has_special_cell (tree_view))
7745         _pspp_sheet_view_queue_draw_node (tree_view,
7746                                           cursor_node,
7747                                           NULL);
7748       g_signal_emit (tree_view, tree_view_signals[CURSOR_CHANGED], 0);
7749       gtk_widget_grab_focus (GTK_WIDGET (tree_view));
7750     }
7751   else
7752     {
7753       gtk_widget_error_bell (GTK_WIDGET (tree_view));
7754     }
7755
7756   pspp_sheet_view_clamp_column_visible (tree_view,
7757                                       tree_view->priv->focus_column, TRUE);
7758 }
7759
7760 static void
7761 pspp_sheet_view_move_cursor_start_end (PsppSheetView *tree_view,
7762                                      gint         count)
7763 {
7764   int cursor_node;
7765   GtkTreePath *path;
7766   GtkTreePath *old_path;
7767
7768   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
7769     return;
7770
7771   g_return_if_fail (tree_view->priv->row_count > 0);
7772
7773   pspp_sheet_view_get_cursor (tree_view, &old_path, NULL);
7774
7775   if (count == -1)
7776     {
7777       /* Now go forward to find the first focusable row. */
7778       path = _pspp_sheet_view_find_path (tree_view, 0);
7779       search_first_focusable_path (tree_view, &path,
7780                                    TRUE, &cursor_node);
7781     }
7782   else
7783     {
7784       /* Now go backwards to find last focusable row. */
7785       path = _pspp_sheet_view_find_path (tree_view, tree_view->priv->row_count - 1);
7786       search_first_focusable_path (tree_view, &path,
7787                                    FALSE, &cursor_node);
7788     }
7789
7790   if (!path)
7791     goto cleanup;
7792
7793   if (gtk_tree_path_compare (old_path, path))
7794     {
7795       pspp_sheet_view_real_set_cursor (tree_view, path, TRUE, TRUE);
7796       gtk_widget_grab_focus (GTK_WIDGET (tree_view));
7797     }
7798   else
7799     {
7800       gtk_widget_error_bell (GTK_WIDGET (tree_view));
7801     }
7802
7803 cleanup:
7804   gtk_tree_path_free (old_path);
7805   gtk_tree_path_free (path);
7806 }
7807
7808 static gboolean
7809 pspp_sheet_view_real_select_all (PsppSheetView *tree_view)
7810 {
7811   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
7812     return FALSE;
7813
7814   if (tree_view->priv->selection->type != GTK_SELECTION_MULTIPLE)
7815     return FALSE;
7816
7817   pspp_sheet_selection_select_all (tree_view->priv->selection);
7818
7819   return TRUE;
7820 }
7821
7822 static gboolean
7823 pspp_sheet_view_real_unselect_all (PsppSheetView *tree_view)
7824 {
7825   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
7826     return FALSE;
7827
7828   if (tree_view->priv->selection->type != GTK_SELECTION_MULTIPLE)
7829     return FALSE;
7830
7831   pspp_sheet_selection_unselect_all (tree_view->priv->selection);
7832
7833   return TRUE;
7834 }
7835
7836 static gboolean
7837 pspp_sheet_view_real_select_cursor_row (PsppSheetView *tree_view,
7838                                       gboolean     start_editing)
7839 {
7840   int new_node = -1;
7841   int cursor_node = -1;
7842   GtkTreePath *cursor_path = NULL;
7843   GtkTreeSelectMode mode = 0;
7844
7845   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
7846     return FALSE;
7847
7848   if (tree_view->priv->cursor)
7849     cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
7850
7851   if (cursor_path == NULL)
7852     return FALSE;
7853
7854   _pspp_sheet_view_find_node (tree_view, cursor_path,
7855                               &cursor_node);
7856
7857   if (cursor_node < 0)
7858     {
7859       gtk_tree_path_free (cursor_path);
7860       return FALSE;
7861     }
7862
7863   if (!tree_view->priv->shift_pressed && start_editing &&
7864       tree_view->priv->focus_column)
7865     {
7866       if (pspp_sheet_view_start_editing (tree_view, cursor_path))
7867         {
7868           gtk_tree_path_free (cursor_path);
7869           return TRUE;
7870         }
7871     }
7872
7873   if (tree_view->priv->ctrl_pressed)
7874     mode |= GTK_TREE_SELECT_MODE_TOGGLE;
7875   if (tree_view->priv->shift_pressed)
7876     mode |= GTK_TREE_SELECT_MODE_EXTEND;
7877
7878   _pspp_sheet_selection_internal_select_node (tree_view->priv->selection,
7879                                             cursor_node,
7880                                             cursor_path,
7881                                             mode,
7882                                             FALSE);
7883
7884   /* We bail out if the original (tree, node) don't exist anymore after
7885    * handling the selection-changed callback.  We do return TRUE because
7886    * the key press has been handled at this point.
7887    */
7888   _pspp_sheet_view_find_node (tree_view, cursor_path, &new_node);
7889
7890   if (cursor_node != new_node)
7891     return FALSE;
7892
7893   pspp_sheet_view_clamp_node_visible (tree_view, cursor_node);
7894
7895   gtk_widget_grab_focus (GTK_WIDGET (tree_view));
7896   _pspp_sheet_view_queue_draw_node (tree_view, cursor_node, NULL);
7897
7898   if (!tree_view->priv->shift_pressed)
7899     pspp_sheet_view_row_activated (tree_view, cursor_path,
7900                                  tree_view->priv->focus_column);
7901     
7902   gtk_tree_path_free (cursor_path);
7903
7904   return TRUE;
7905 }
7906
7907 static gboolean
7908 pspp_sheet_view_real_toggle_cursor_row (PsppSheetView *tree_view)
7909 {
7910   int new_node = -1;
7911   int cursor_node = -1;
7912   GtkTreePath *cursor_path = NULL;
7913
7914   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
7915     return FALSE;
7916
7917   cursor_path = NULL;
7918   if (tree_view->priv->cursor)
7919     cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
7920
7921   if (cursor_path == NULL)
7922     return FALSE;
7923
7924   _pspp_sheet_view_find_node (tree_view, cursor_path, &cursor_node);
7925   if (cursor_node < 0)
7926     {
7927       gtk_tree_path_free (cursor_path);
7928       return FALSE;
7929     }
7930
7931   _pspp_sheet_selection_internal_select_node (tree_view->priv->selection,
7932                                             cursor_node,
7933                                             cursor_path,
7934                                             GTK_TREE_SELECT_MODE_TOGGLE,
7935                                             FALSE);
7936
7937   /* We bail out if the original (tree, node) don't exist anymore after
7938    * handling the selection-changed callback.  We do return TRUE because
7939    * the key press has been handled at this point.
7940    */
7941   _pspp_sheet_view_find_node (tree_view, cursor_path, &new_node);
7942
7943   if (cursor_node != new_node)
7944     return FALSE;
7945
7946   pspp_sheet_view_clamp_node_visible (tree_view, cursor_node);
7947
7948   gtk_widget_grab_focus (GTK_WIDGET (tree_view));
7949   pspp_sheet_view_queue_draw_path (tree_view, cursor_path, NULL);
7950   gtk_tree_path_free (cursor_path);
7951
7952   return TRUE;
7953 }
7954
7955 static gboolean
7956 pspp_sheet_view_search_entry_flush_timeout (PsppSheetView *tree_view)
7957 {
7958   pspp_sheet_view_search_dialog_hide (tree_view->priv->search_window, tree_view);
7959   tree_view->priv->typeselect_flush_timeout = 0;
7960
7961   return FALSE;
7962 }
7963
7964 /* Cut and paste from gtkwindow.c */
7965 static void
7966 send_focus_change (GtkWidget *widget,
7967                    gboolean   in)
7968 {
7969   GdkEvent *fevent = gdk_event_new (GDK_FOCUS_CHANGE);
7970
7971   g_object_ref (widget);
7972    
7973  if (in)
7974     GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
7975   else
7976     GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);
7977
7978   fevent->focus_change.type = GDK_FOCUS_CHANGE;
7979   fevent->focus_change.window = g_object_ref (widget->window);
7980   fevent->focus_change.in = in;
7981   
7982   gtk_widget_event (widget, fevent);
7983   
7984   g_object_notify (G_OBJECT (widget), "has-focus");
7985
7986   g_object_unref (widget);
7987   gdk_event_free (fevent);
7988 }
7989
7990 static void
7991 pspp_sheet_view_ensure_interactive_directory (PsppSheetView *tree_view)
7992 {
7993   GtkWidget *frame, *vbox, *toplevel;
7994   GdkScreen *screen;
7995
7996   if (tree_view->priv->search_custom_entry_set)
7997     return;
7998
7999   toplevel = gtk_widget_get_toplevel (GTK_WIDGET (tree_view));
8000   screen = gtk_widget_get_screen (GTK_WIDGET (tree_view));
8001
8002    if (tree_view->priv->search_window != NULL)
8003      {
8004        if (GTK_WINDOW (toplevel)->group)
8005          gtk_window_group_add_window (GTK_WINDOW (toplevel)->group,
8006                                       GTK_WINDOW (tree_view->priv->search_window));
8007        else if (GTK_WINDOW (tree_view->priv->search_window)->group)
8008          gtk_window_group_remove_window (GTK_WINDOW (tree_view->priv->search_window)->group,
8009                                          GTK_WINDOW (tree_view->priv->search_window));
8010        gtk_window_set_screen (GTK_WINDOW (tree_view->priv->search_window), screen);
8011        return;
8012      }
8013    
8014   tree_view->priv->search_window = gtk_window_new (GTK_WINDOW_POPUP);
8015   gtk_window_set_screen (GTK_WINDOW (tree_view->priv->search_window), screen);
8016
8017   if (GTK_WINDOW (toplevel)->group)
8018     gtk_window_group_add_window (GTK_WINDOW (toplevel)->group,
8019                                  GTK_WINDOW (tree_view->priv->search_window));
8020
8021   gtk_window_set_type_hint (GTK_WINDOW (tree_view->priv->search_window),
8022                             GDK_WINDOW_TYPE_HINT_UTILITY);
8023   gtk_window_set_modal (GTK_WINDOW (tree_view->priv->search_window), TRUE);
8024   g_signal_connect (tree_view->priv->search_window, "delete-event",
8025                     G_CALLBACK (pspp_sheet_view_search_delete_event),
8026                     tree_view);
8027   g_signal_connect (tree_view->priv->search_window, "key-press-event",
8028                     G_CALLBACK (pspp_sheet_view_search_key_press_event),
8029                     tree_view);
8030   g_signal_connect (tree_view->priv->search_window, "button-press-event",
8031                     G_CALLBACK (pspp_sheet_view_search_button_press_event),
8032                     tree_view);
8033   g_signal_connect (tree_view->priv->search_window, "scroll-event",
8034                     G_CALLBACK (pspp_sheet_view_search_scroll_event),
8035                     tree_view);
8036
8037   frame = gtk_frame_new (NULL);
8038   gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
8039   gtk_widget_show (frame);
8040   gtk_container_add (GTK_CONTAINER (tree_view->priv->search_window), frame);
8041
8042   vbox = gtk_vbox_new (FALSE, 0);
8043   gtk_widget_show (vbox);
8044   gtk_container_add (GTK_CONTAINER (frame), vbox);
8045   gtk_container_set_border_width (GTK_CONTAINER (vbox), 3);
8046
8047   /* add entry */
8048   tree_view->priv->search_entry = gtk_entry_new ();
8049   gtk_widget_show (tree_view->priv->search_entry);
8050   g_signal_connect (tree_view->priv->search_entry, "populate-popup",
8051                     G_CALLBACK (pspp_sheet_view_search_disable_popdown),
8052                     tree_view);
8053   g_signal_connect (tree_view->priv->search_entry,
8054                     "activate", G_CALLBACK (pspp_sheet_view_search_activate),
8055                     tree_view);
8056   g_signal_connect (GTK_ENTRY (tree_view->priv->search_entry)->im_context,
8057                     "preedit-changed",
8058                     G_CALLBACK (pspp_sheet_view_search_preedit_changed),
8059                     tree_view);
8060   gtk_container_add (GTK_CONTAINER (vbox),
8061                      tree_view->priv->search_entry);
8062
8063   gtk_widget_realize (tree_view->priv->search_entry);
8064 }
8065
8066 /* Pops up the interactive search entry.  If keybinding is TRUE then the user
8067  * started this by typing the start_interactive_search keybinding.  Otherwise, it came from 
8068  */
8069 static gboolean
8070 pspp_sheet_view_real_start_interactive_search (PsppSheetView *tree_view,
8071                                              gboolean     keybinding)
8072 {
8073   /* We only start interactive search if we have focus or the columns
8074    * have focus.  If one of our children have focus, we don't want to
8075    * start the search.
8076    */
8077   GList *list;
8078   gboolean found_focus = FALSE;
8079   GtkWidgetClass *entry_parent_class;
8080   
8081   if (!tree_view->priv->enable_search && !keybinding)
8082     return FALSE;
8083
8084   if (tree_view->priv->search_custom_entry_set)
8085     return FALSE;
8086
8087   if (tree_view->priv->search_window != NULL &&
8088       gtk_widget_get_visible (tree_view->priv->search_window))
8089     return TRUE;
8090
8091   for (list = tree_view->priv->columns; list; list = list->next)
8092     {
8093       PsppSheetViewColumn *column;
8094
8095       column = list->data;
8096       if (! column->visible)
8097         continue;
8098
8099       if (gtk_widget_has_focus (column->button))
8100         {
8101           found_focus = TRUE;
8102           break;
8103         }
8104     }
8105   
8106   if (gtk_widget_has_focus (GTK_WIDGET (tree_view)))
8107     found_focus = TRUE;
8108
8109   if (!found_focus)
8110     return FALSE;
8111
8112   if (tree_view->priv->search_column < 0)
8113     return FALSE;
8114
8115   pspp_sheet_view_ensure_interactive_directory (tree_view);
8116
8117   if (keybinding)
8118     gtk_entry_set_text (GTK_ENTRY (tree_view->priv->search_entry), "");
8119
8120   /* done, show it */
8121   tree_view->priv->search_position_func (tree_view, tree_view->priv->search_window, tree_view->priv->search_position_user_data);
8122   gtk_widget_show (tree_view->priv->search_window);
8123   if (tree_view->priv->search_entry_changed_id == 0)
8124     {
8125       tree_view->priv->search_entry_changed_id =
8126         g_signal_connect (tree_view->priv->search_entry, "changed",
8127                           G_CALLBACK (pspp_sheet_view_search_init),
8128                           tree_view);
8129     }
8130
8131   tree_view->priv->typeselect_flush_timeout =
8132     gdk_threads_add_timeout (PSPP_SHEET_VIEW_SEARCH_DIALOG_TIMEOUT,
8133                    (GSourceFunc) pspp_sheet_view_search_entry_flush_timeout,
8134                    tree_view);
8135
8136   /* Grab focus will select all the text.  We don't want that to happen, so we
8137    * call the parent instance and bypass the selection change.  This is probably
8138    * really non-kosher. */
8139   entry_parent_class = g_type_class_peek_parent (GTK_ENTRY_GET_CLASS (tree_view->priv->search_entry));
8140   (entry_parent_class->grab_focus) (tree_view->priv->search_entry);
8141
8142   /* send focus-in event */
8143   send_focus_change (tree_view->priv->search_entry, TRUE);
8144
8145   /* search first matching iter */
8146   pspp_sheet_view_search_init (tree_view->priv->search_entry, tree_view);
8147
8148   return TRUE;
8149 }
8150
8151 static gboolean
8152 pspp_sheet_view_start_interactive_search (PsppSheetView *tree_view)
8153 {
8154   return pspp_sheet_view_real_start_interactive_search (tree_view, TRUE);
8155 }
8156
8157 /* this function returns the new width of the column being resized given
8158  * the column and x position of the cursor; the x cursor position is passed
8159  * in as a pointer and automagicly corrected if it's beyond min/max limits
8160  */
8161 static gint
8162 pspp_sheet_view_new_column_width (PsppSheetView *tree_view,
8163                                 gint       i,
8164                                 gint      *x)
8165 {
8166   PsppSheetViewColumn *column;
8167   gint width;
8168   gboolean rtl;
8169
8170   /* first translate the x position from widget->window
8171    * to clist->clist_window
8172    */
8173   rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
8174   column = g_list_nth (tree_view->priv->columns, i)->data;
8175   width = rtl ? (column->button->allocation.x + column->button->allocation.width - *x) : (*x - column->button->allocation.x);
8176  
8177   /* Clamp down the value */
8178   if (column->min_width == -1)
8179     width = MAX (column->button->requisition.width,
8180                  width);
8181   else
8182     width = MAX (column->min_width,
8183                  width);
8184   if (column->max_width != -1)
8185     width = MIN (width, column->max_width);
8186
8187   *x = rtl ? (column->button->allocation.x + column->button->allocation.width - width) : (column->button->allocation.x + width);
8188  
8189   return width;
8190 }
8191
8192
8193 /* FIXME this adjust_allocation is a big cut-and-paste from
8194  * GtkCList, needs to be some "official" way to do this
8195  * factored out.
8196  */
8197 typedef struct
8198 {
8199   GdkWindow *window;
8200   int dx;
8201   int dy;
8202 } ScrollData;
8203
8204 /* The window to which widget->window is relative */
8205 #define ALLOCATION_WINDOW(widget)               \
8206    (!gtk_widget_get_has_window (widget) ?               \
8207     (widget)->window :                          \
8208      gdk_window_get_parent ((widget)->window))
8209
8210 static void
8211 adjust_allocation_recurse (GtkWidget *widget,
8212                            gpointer   data)
8213 {
8214   ScrollData *scroll_data = data;
8215
8216   /* Need to really size allocate instead of just poking
8217    * into widget->allocation if the widget is not realized.
8218    * FIXME someone figure out why this was.
8219    */
8220   if (!gtk_widget_get_realized (widget))
8221     {
8222       if (gtk_widget_get_visible (widget))
8223         {
8224           GdkRectangle tmp_rectangle = widget->allocation;
8225           tmp_rectangle.x += scroll_data->dx;
8226           tmp_rectangle.y += scroll_data->dy;
8227           
8228           gtk_widget_size_allocate (widget, &tmp_rectangle);
8229         }
8230     }
8231   else
8232     {
8233       if (ALLOCATION_WINDOW (widget) == scroll_data->window)
8234         {
8235           widget->allocation.x += scroll_data->dx;
8236           widget->allocation.y += scroll_data->dy;
8237           
8238           if (GTK_IS_CONTAINER (widget))
8239             gtk_container_forall (GTK_CONTAINER (widget),
8240                                   adjust_allocation_recurse,
8241                                   data);
8242         }
8243     }
8244 }
8245
8246 static void
8247 adjust_allocation (GtkWidget *widget,
8248                    int        dx,
8249                    int        dy)
8250 {
8251   ScrollData scroll_data;
8252
8253   if (gtk_widget_get_realized (widget))
8254     scroll_data.window = ALLOCATION_WINDOW (widget);
8255   else
8256     scroll_data.window = NULL;
8257     
8258   scroll_data.dx = dx;
8259   scroll_data.dy = dy;
8260   
8261   adjust_allocation_recurse (widget, &scroll_data);
8262 }
8263
8264 /* Callbacks */
8265 static void
8266 pspp_sheet_view_adjustment_changed (GtkAdjustment *adjustment,
8267                                   PsppSheetView   *tree_view)
8268 {
8269   if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
8270     {
8271       gint dy;
8272         
8273       gdk_window_move (tree_view->priv->bin_window,
8274                        - tree_view->priv->hadjustment->value,
8275                        TREE_VIEW_HEADER_HEIGHT (tree_view));
8276       gdk_window_move (tree_view->priv->header_window,
8277                        - tree_view->priv->hadjustment->value,
8278                        0);
8279       dy = tree_view->priv->dy - (int) tree_view->priv->vadjustment->value;
8280       if (dy)
8281         {
8282           update_prelight (tree_view,
8283                            tree_view->priv->event_last_x,
8284                            tree_view->priv->event_last_y - dy);
8285
8286           if (tree_view->priv->edited_column &&
8287               GTK_IS_WIDGET (tree_view->priv->edited_column->editable_widget))
8288             {
8289               GList *list;
8290               GtkWidget *widget;
8291               PsppSheetViewChild *child = NULL;
8292
8293               widget = GTK_WIDGET (tree_view->priv->edited_column->editable_widget);
8294               adjust_allocation (widget, 0, dy); 
8295               
8296               for (list = tree_view->priv->children; list; list = list->next)
8297                 {
8298                   child = (PsppSheetViewChild *)list->data;
8299                   if (child->widget == widget)
8300                     {
8301                       child->y += dy;
8302                       break;
8303                     }
8304                 }
8305             }
8306         }
8307       gdk_window_scroll (tree_view->priv->bin_window, 0, dy);
8308
8309       if (tree_view->priv->dy != (int) tree_view->priv->vadjustment->value)
8310         {
8311           /* update our dy and top_row */
8312           tree_view->priv->dy = (int) tree_view->priv->vadjustment->value;
8313
8314           if (!tree_view->priv->in_top_row_to_dy)
8315             pspp_sheet_view_dy_to_top_row (tree_view);
8316         }
8317
8318       gdk_window_process_updates (tree_view->priv->header_window, TRUE);
8319       gdk_window_process_updates (tree_view->priv->bin_window, TRUE);
8320     }
8321 }
8322
8323 \f
8324
8325 /* Public methods
8326  */
8327
8328 /**
8329  * pspp_sheet_view_new:
8330  *
8331  * Creates a new #PsppSheetView widget.
8332  *
8333  * Return value: A newly created #PsppSheetView widget.
8334  **/
8335 GtkWidget *
8336 pspp_sheet_view_new (void)
8337 {
8338   return g_object_new (PSPP_TYPE_SHEET_VIEW, NULL);
8339 }
8340
8341 /**
8342  * pspp_sheet_view_new_with_model:
8343  * @model: the model.
8344  *
8345  * Creates a new #PsppSheetView widget with the model initialized to @model.
8346  *
8347  * Return value: A newly created #PsppSheetView widget.
8348  **/
8349 GtkWidget *
8350 pspp_sheet_view_new_with_model (GtkTreeModel *model)
8351 {
8352   return g_object_new (PSPP_TYPE_SHEET_VIEW, "model", model, NULL);
8353 }
8354
8355 /* Public Accessors
8356  */
8357
8358 /**
8359  * pspp_sheet_view_get_model:
8360  * @tree_view: a #PsppSheetView
8361  *
8362  * Returns the model the #PsppSheetView is based on.  Returns %NULL if the
8363  * model is unset.
8364  *
8365  * Return value: A #GtkTreeModel, or %NULL if none is currently being used.
8366  **/
8367 GtkTreeModel *
8368 pspp_sheet_view_get_model (PsppSheetView *tree_view)
8369 {
8370   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
8371
8372   return tree_view->priv->model;
8373 }
8374
8375 /**
8376  * pspp_sheet_view_set_model:
8377  * @tree_view: A #GtkTreeNode.
8378  * @model: (allow-none): The model.
8379  *
8380  * Sets the model for a #PsppSheetView.  If the @tree_view already has a model
8381  * set, it will remove it before setting the new model.  If @model is %NULL,
8382  * then it will unset the old model.
8383  **/
8384 void
8385 pspp_sheet_view_set_model (PsppSheetView  *tree_view,
8386                          GtkTreeModel *model)
8387 {
8388   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
8389   g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model));
8390
8391   if (model == tree_view->priv->model)
8392     return;
8393
8394   if (tree_view->priv->scroll_to_path)
8395     {
8396       gtk_tree_row_reference_free (tree_view->priv->scroll_to_path);
8397       tree_view->priv->scroll_to_path = NULL;
8398     }
8399
8400   if (tree_view->priv->model)
8401     {
8402       GList *tmplist = tree_view->priv->columns;
8403
8404       if (tree_view->priv->selected)
8405         range_tower_set0 (tree_view->priv->selected, 0, ULONG_MAX);
8406       pspp_sheet_view_stop_editing (tree_view, TRUE);
8407
8408       g_signal_handlers_disconnect_by_func (tree_view->priv->model,
8409                                             pspp_sheet_view_row_changed,
8410                                             tree_view);
8411       g_signal_handlers_disconnect_by_func (tree_view->priv->model,
8412                                             pspp_sheet_view_row_inserted,
8413                                             tree_view);
8414       g_signal_handlers_disconnect_by_func (tree_view->priv->model,
8415                                             pspp_sheet_view_row_deleted,
8416                                             tree_view);
8417       g_signal_handlers_disconnect_by_func (tree_view->priv->model,
8418                                             pspp_sheet_view_rows_reordered,
8419                                             tree_view);
8420
8421       for (; tmplist; tmplist = tmplist->next)
8422         _pspp_sheet_view_column_unset_model (tmplist->data,
8423                                            tree_view->priv->model);
8424
8425       tree_view->priv->prelight_node = -1;
8426
8427       gtk_tree_row_reference_free (tree_view->priv->drag_dest_row);
8428       tree_view->priv->drag_dest_row = NULL;
8429       gtk_tree_row_reference_free (tree_view->priv->cursor);
8430       tree_view->priv->cursor = NULL;
8431       gtk_tree_row_reference_free (tree_view->priv->anchor);
8432       tree_view->priv->anchor = NULL;
8433       gtk_tree_row_reference_free (tree_view->priv->top_row);
8434       tree_view->priv->top_row = NULL;
8435       gtk_tree_row_reference_free (tree_view->priv->scroll_to_path);
8436       tree_view->priv->scroll_to_path = NULL;
8437
8438       tree_view->priv->scroll_to_column = NULL;
8439
8440       g_object_unref (tree_view->priv->model);
8441
8442       tree_view->priv->search_column = -1;
8443       tree_view->priv->fixed_height = -1;
8444       tree_view->priv->dy = tree_view->priv->top_row_dy = 0;
8445       tree_view->priv->last_button_x = -1;
8446       tree_view->priv->last_button_y = -1;
8447     }
8448
8449   tree_view->priv->model = model;
8450
8451   if (tree_view->priv->model)
8452     {
8453       gint i;
8454       GtkTreeModelFlags flags;
8455
8456       if (tree_view->priv->search_column == -1)
8457         {
8458           for (i = 0; i < gtk_tree_model_get_n_columns (model); i++)
8459             {
8460               GType type = gtk_tree_model_get_column_type (model, i);
8461
8462               if (g_value_type_transformable (type, G_TYPE_STRING))
8463                 {
8464                   tree_view->priv->search_column = i;
8465                   break;
8466                 }
8467             }
8468         }
8469
8470       g_object_ref (tree_view->priv->model);
8471       g_signal_connect (tree_view->priv->model,
8472                         "row-changed",
8473                         G_CALLBACK (pspp_sheet_view_row_changed),
8474                         tree_view);
8475       g_signal_connect (tree_view->priv->model,
8476                         "row-inserted",
8477                         G_CALLBACK (pspp_sheet_view_row_inserted),
8478                         tree_view);
8479       g_signal_connect (tree_view->priv->model,
8480                         "row-deleted",
8481                         G_CALLBACK (pspp_sheet_view_row_deleted),
8482                         tree_view);
8483       g_signal_connect (tree_view->priv->model,
8484                         "rows-reordered",
8485                         G_CALLBACK (pspp_sheet_view_rows_reordered),
8486                         tree_view);
8487
8488       flags = gtk_tree_model_get_flags (tree_view->priv->model);
8489
8490       tree_view->priv->row_count = gtk_tree_model_iter_n_children (tree_view->priv->model, NULL);
8491
8492       /*  FIXME: do I need to do this? pspp_sheet_view_create_buttons (tree_view); */
8493       install_presize_handler (tree_view);
8494     }
8495
8496   g_object_notify (G_OBJECT (tree_view), "model");
8497
8498   if (tree_view->priv->selection)
8499     _pspp_sheet_selection_emit_changed (tree_view->priv->selection);
8500
8501   if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
8502     gtk_widget_queue_resize (GTK_WIDGET (tree_view));
8503 }
8504
8505 /**
8506  * pspp_sheet_view_get_selection:
8507  * @tree_view: A #PsppSheetView.
8508  *
8509  * Gets the #PsppSheetSelection associated with @tree_view.
8510  *
8511  * Return value: A #PsppSheetSelection object.
8512  **/
8513 PsppSheetSelection *
8514 pspp_sheet_view_get_selection (PsppSheetView *tree_view)
8515 {
8516   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
8517
8518   return tree_view->priv->selection;
8519 }
8520
8521 /**
8522  * pspp_sheet_view_get_hadjustment:
8523  * @tree_view: A #PsppSheetView
8524  *
8525  * Gets the #GtkAdjustment currently being used for the horizontal aspect.
8526  *
8527  * Return value: A #GtkAdjustment object, or %NULL if none is currently being
8528  * used.
8529  **/
8530 GtkAdjustment *
8531 pspp_sheet_view_get_hadjustment (PsppSheetView *tree_view)
8532 {
8533   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
8534
8535   if (tree_view->priv->hadjustment == NULL)
8536     pspp_sheet_view_set_hadjustment (tree_view, NULL);
8537
8538   return tree_view->priv->hadjustment;
8539 }
8540
8541 /**
8542  * pspp_sheet_view_set_hadjustment:
8543  * @tree_view: A #PsppSheetView
8544  * @adjustment: (allow-none): The #GtkAdjustment to set, or %NULL
8545  *
8546  * Sets the #GtkAdjustment for the current horizontal aspect.
8547  **/
8548 void
8549 pspp_sheet_view_set_hadjustment (PsppSheetView   *tree_view,
8550                                GtkAdjustment *adjustment)
8551 {
8552   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
8553
8554   pspp_sheet_view_set_adjustments (tree_view,
8555                                  adjustment,
8556                                  tree_view->priv->vadjustment);
8557
8558   g_object_notify (G_OBJECT (tree_view), "hadjustment");
8559 }
8560
8561 /**
8562  * pspp_sheet_view_get_vadjustment:
8563  * @tree_view: A #PsppSheetView
8564  *
8565  * Gets the #GtkAdjustment currently being used for the vertical aspect.
8566  *
8567  * Return value: A #GtkAdjustment object, or %NULL if none is currently being
8568  * used.
8569  **/
8570 GtkAdjustment *
8571 pspp_sheet_view_get_vadjustment (PsppSheetView *tree_view)
8572 {
8573   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
8574
8575   if (tree_view->priv->vadjustment == NULL)
8576     pspp_sheet_view_set_vadjustment (tree_view, NULL);
8577
8578   return tree_view->priv->vadjustment;
8579 }
8580
8581 /**
8582  * pspp_sheet_view_set_vadjustment:
8583  * @tree_view: A #PsppSheetView
8584  * @adjustment: (allow-none): The #GtkAdjustment to set, or %NULL
8585  *
8586  * Sets the #GtkAdjustment for the current vertical aspect.
8587  **/
8588 void
8589 pspp_sheet_view_set_vadjustment (PsppSheetView   *tree_view,
8590                                GtkAdjustment *adjustment)
8591 {
8592   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
8593
8594   pspp_sheet_view_set_adjustments (tree_view,
8595                                  tree_view->priv->hadjustment,
8596                                  adjustment);
8597
8598   g_object_notify (G_OBJECT (tree_view), "vadjustment");
8599 }
8600
8601 /* Column and header operations */
8602
8603 /**
8604  * pspp_sheet_view_get_headers_visible:
8605  * @tree_view: A #PsppSheetView.
8606  *
8607  * Returns %TRUE if the headers on the @tree_view are visible.
8608  *
8609  * Return value: Whether the headers are visible or not.
8610  **/
8611 gboolean
8612 pspp_sheet_view_get_headers_visible (PsppSheetView *tree_view)
8613 {
8614   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
8615
8616   return PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_HEADERS_VISIBLE);
8617 }
8618
8619 /**
8620  * pspp_sheet_view_set_headers_visible:
8621  * @tree_view: A #PsppSheetView.
8622  * @headers_visible: %TRUE if the headers are visible
8623  *
8624  * Sets the visibility state of the headers.
8625  **/
8626 void
8627 pspp_sheet_view_set_headers_visible (PsppSheetView *tree_view,
8628                                    gboolean     headers_visible)
8629 {
8630   gint x, y;
8631   GList *list;
8632   PsppSheetViewColumn *column;
8633
8634   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
8635
8636   headers_visible = !! headers_visible;
8637
8638   if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_HEADERS_VISIBLE) == headers_visible)
8639     return;
8640
8641   if (headers_visible)
8642     PSPP_SHEET_VIEW_SET_FLAG (tree_view, PSPP_SHEET_VIEW_HEADERS_VISIBLE);
8643   else
8644     PSPP_SHEET_VIEW_UNSET_FLAG (tree_view, PSPP_SHEET_VIEW_HEADERS_VISIBLE);
8645
8646   if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
8647     {
8648       gdk_window_get_position (tree_view->priv->bin_window, &x, &y);
8649       if (headers_visible)
8650         {
8651           gdk_window_move_resize (tree_view->priv->bin_window, x, y  + TREE_VIEW_HEADER_HEIGHT (tree_view), tree_view->priv->width, GTK_WIDGET (tree_view)->allocation.height -  + TREE_VIEW_HEADER_HEIGHT (tree_view));
8652
8653           if (gtk_widget_get_mapped (GTK_WIDGET (tree_view)))
8654             pspp_sheet_view_map_buttons (tree_view);
8655         }
8656       else
8657         {
8658           gdk_window_move_resize (tree_view->priv->bin_window, x, y, tree_view->priv->width, tree_view->priv->height);
8659
8660           for (list = tree_view->priv->columns; list; list = list->next)
8661             {
8662               column = list->data;
8663               gtk_widget_unmap (column->button);
8664             }
8665           gdk_window_hide (tree_view->priv->header_window);
8666         }
8667     }
8668
8669   tree_view->priv->vadjustment->page_size = GTK_WIDGET (tree_view)->allocation.height - TREE_VIEW_HEADER_HEIGHT (tree_view);
8670   tree_view->priv->vadjustment->page_increment = (GTK_WIDGET (tree_view)->allocation.height - TREE_VIEW_HEADER_HEIGHT (tree_view)) / 2;
8671   tree_view->priv->vadjustment->lower = 0;
8672   tree_view->priv->vadjustment->upper = tree_view->priv->height;
8673   gtk_adjustment_changed (tree_view->priv->vadjustment);
8674
8675   gtk_widget_queue_resize (GTK_WIDGET (tree_view));
8676
8677   g_object_notify (G_OBJECT (tree_view), "headers-visible");
8678 }
8679
8680 /**
8681  * pspp_sheet_view_columns_autosize:
8682  * @tree_view: A #PsppSheetView.
8683  *
8684  * Resizes all columns to their optimal width. Only works after the
8685  * treeview has been realized.
8686  **/
8687 void
8688 pspp_sheet_view_columns_autosize (PsppSheetView *tree_view)
8689 {
8690   gboolean dirty = FALSE;
8691   GList *list;
8692   PsppSheetViewColumn *column;
8693
8694   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
8695
8696   for (list = tree_view->priv->columns; list; list = list->next)
8697     {
8698       column = list->data;
8699       _pspp_sheet_view_column_cell_set_dirty (column);
8700       dirty = TRUE;
8701     }
8702
8703   if (dirty)
8704     gtk_widget_queue_resize (GTK_WIDGET (tree_view));
8705 }
8706
8707 /**
8708  * pspp_sheet_view_set_headers_clickable:
8709  * @tree_view: A #PsppSheetView.
8710  * @setting: %TRUE if the columns are clickable.
8711  *
8712  * Allow the column title buttons to be clicked.
8713  **/
8714 void
8715 pspp_sheet_view_set_headers_clickable (PsppSheetView *tree_view,
8716                                      gboolean   setting)
8717 {
8718   GList *list;
8719
8720   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
8721
8722   for (list = tree_view->priv->columns; list; list = list->next)
8723     pspp_sheet_view_column_set_clickable (PSPP_SHEET_VIEW_COLUMN (list->data), setting);
8724
8725   g_object_notify (G_OBJECT (tree_view), "headers-clickable");
8726 }
8727
8728
8729 /**
8730  * pspp_sheet_view_get_headers_clickable:
8731  * @tree_view: A #PsppSheetView.
8732  *
8733  * Returns whether all header columns are clickable.
8734  *
8735  * Return value: %TRUE if all header columns are clickable, otherwise %FALSE
8736  *
8737  * Since: 2.10
8738  **/
8739 gboolean 
8740 pspp_sheet_view_get_headers_clickable (PsppSheetView *tree_view)
8741 {
8742   GList *list;
8743   
8744   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
8745
8746   for (list = tree_view->priv->columns; list; list = list->next)
8747     if (!PSPP_SHEET_VIEW_COLUMN (list->data)->clickable)
8748       return FALSE;
8749
8750   return TRUE;
8751 }
8752
8753 /**
8754  * pspp_sheet_view_set_rules_hint
8755  * @tree_view: a #PsppSheetView
8756  * @setting: %TRUE if the tree requires reading across rows
8757  *
8758  * This function tells GTK+ that the user interface for your
8759  * application requires users to read across tree rows and associate
8760  * cells with one another. By default, GTK+ will then render the tree
8761  * with alternating row colors. Do <emphasis>not</emphasis> use it
8762  * just because you prefer the appearance of the ruled tree; that's a
8763  * question for the theme. Some themes will draw tree rows in
8764  * alternating colors even when rules are turned off, and users who
8765  * prefer that appearance all the time can choose those themes. You
8766  * should call this function only as a <emphasis>semantic</emphasis>
8767  * hint to the theme engine that your tree makes alternating colors
8768  * useful from a functional standpoint (since it has lots of columns,
8769  * generally).
8770  *
8771  **/
8772 void
8773 pspp_sheet_view_set_rules_hint (PsppSheetView  *tree_view,
8774                               gboolean      setting)
8775 {
8776   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
8777
8778   setting = setting != FALSE;
8779
8780   if (tree_view->priv->has_rules != setting)
8781     {
8782       tree_view->priv->has_rules = setting;
8783       gtk_widget_queue_draw (GTK_WIDGET (tree_view));
8784     }
8785
8786   g_object_notify (G_OBJECT (tree_view), "rules-hint");
8787 }
8788
8789 /**
8790  * pspp_sheet_view_get_rules_hint
8791  * @tree_view: a #PsppSheetView
8792  *
8793  * Gets the setting set by pspp_sheet_view_set_rules_hint().
8794  *
8795  * Return value: %TRUE if rules are useful for the user of this tree
8796  **/
8797 gboolean
8798 pspp_sheet_view_get_rules_hint (PsppSheetView  *tree_view)
8799 {
8800   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
8801
8802   return tree_view->priv->has_rules;
8803 }
8804
8805 /* Public Column functions
8806  */
8807
8808 /**
8809  * pspp_sheet_view_append_column:
8810  * @tree_view: A #PsppSheetView.
8811  * @column: The #PsppSheetViewColumn to add.
8812  *
8813  * Appends @column to the list of columns.
8814  *
8815  * Return value: The number of columns in @tree_view after appending.
8816  **/
8817 gint
8818 pspp_sheet_view_append_column (PsppSheetView       *tree_view,
8819                              PsppSheetViewColumn *column)
8820 {
8821   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), -1);
8822   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (column), -1);
8823   g_return_val_if_fail (column->tree_view == NULL, -1);
8824
8825   return pspp_sheet_view_insert_column (tree_view, column, -1);
8826 }
8827
8828
8829 /**
8830  * pspp_sheet_view_remove_column:
8831  * @tree_view: A #PsppSheetView.
8832  * @column: The #PsppSheetViewColumn to remove.
8833  *
8834  * Removes @column from @tree_view.
8835  *
8836  * Return value: The number of columns in @tree_view after removing.
8837  **/
8838 gint
8839 pspp_sheet_view_remove_column (PsppSheetView       *tree_view,
8840                              PsppSheetViewColumn *column)
8841 {
8842   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), -1);
8843   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (column), -1);
8844   g_return_val_if_fail (column->tree_view == GTK_WIDGET (tree_view), -1);
8845
8846   if (tree_view->priv->focus_column == column)
8847     tree_view->priv->focus_column = NULL;
8848
8849   if (tree_view->priv->edited_column == column)
8850     {
8851       pspp_sheet_view_stop_editing (tree_view, TRUE);
8852
8853       /* no need to, but just to be sure ... */
8854       tree_view->priv->edited_column = NULL;
8855     }
8856
8857   _pspp_sheet_view_column_unset_tree_view (column);
8858
8859   tree_view->priv->columns = g_list_remove (tree_view->priv->columns, column);
8860   tree_view->priv->n_columns--;
8861
8862   if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
8863     {
8864       GList *list;
8865
8866       _pspp_sheet_view_column_unrealize_button (column);
8867       for (list = tree_view->priv->columns; list; list = list->next)
8868         {
8869           PsppSheetViewColumn *tmp_column;
8870
8871           tmp_column = PSPP_SHEET_VIEW_COLUMN (list->data);
8872           if (tmp_column->visible)
8873             _pspp_sheet_view_column_cell_set_dirty (tmp_column);
8874         }
8875
8876       if (tree_view->priv->n_columns == 0 &&
8877           pspp_sheet_view_get_headers_visible (tree_view))
8878         gdk_window_hide (tree_view->priv->header_window);
8879
8880       gtk_widget_queue_resize (GTK_WIDGET (tree_view));
8881     }
8882
8883   g_object_unref (column);
8884   g_signal_emit (tree_view, tree_view_signals[COLUMNS_CHANGED], 0);
8885
8886   return tree_view->priv->n_columns;
8887 }
8888
8889 /**
8890  * pspp_sheet_view_insert_column:
8891  * @tree_view: A #PsppSheetView.
8892  * @column: The #PsppSheetViewColumn to be inserted.
8893  * @position: The position to insert @column in.
8894  *
8895  * This inserts the @column into the @tree_view at @position.  If @position is
8896  * -1, then the column is inserted at the end.
8897  *
8898  * Return value: The number of columns in @tree_view after insertion.
8899  **/
8900 gint
8901 pspp_sheet_view_insert_column (PsppSheetView       *tree_view,
8902                              PsppSheetViewColumn *column,
8903                              gint               position)
8904 {
8905   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), -1);
8906   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (column), -1);
8907   g_return_val_if_fail (column->tree_view == NULL, -1);
8908
8909   g_object_ref_sink (column);
8910
8911   if (tree_view->priv->n_columns == 0 &&
8912       gtk_widget_get_realized (GTK_WIDGET (tree_view)) &&
8913       pspp_sheet_view_get_headers_visible (tree_view))
8914     {
8915       gdk_window_show (tree_view->priv->header_window);
8916     }
8917
8918   tree_view->priv->columns = g_list_insert (tree_view->priv->columns,
8919                                             column, position);
8920   tree_view->priv->n_columns++;
8921
8922   _pspp_sheet_view_column_set_tree_view (column, tree_view);
8923
8924   if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
8925     {
8926       GList *list;
8927
8928       _pspp_sheet_view_column_realize_button (column);
8929
8930       for (list = tree_view->priv->columns; list; list = list->next)
8931         {
8932           column = PSPP_SHEET_VIEW_COLUMN (list->data);
8933           if (column->visible)
8934             _pspp_sheet_view_column_cell_set_dirty (column);
8935         }
8936       gtk_widget_queue_resize (GTK_WIDGET (tree_view));
8937     }
8938
8939   g_signal_emit (tree_view, tree_view_signals[COLUMNS_CHANGED], 0);
8940
8941   return tree_view->priv->n_columns;
8942 }
8943
8944 /**
8945  * pspp_sheet_view_insert_column_with_attributes:
8946  * @tree_view: A #PsppSheetView
8947  * @position: The position to insert the new column in.
8948  * @title: The title to set the header to.
8949  * @cell: The #GtkCellRenderer.
8950  * @Varargs: A %NULL-terminated list of attributes.
8951  *
8952  * Creates a new #PsppSheetViewColumn and inserts it into the @tree_view at
8953  * @position.  If @position is -1, then the newly created column is inserted at
8954  * the end.  The column is initialized with the attributes given.
8955  *
8956  * Return value: The number of columns in @tree_view after insertion.
8957  **/
8958 gint
8959 pspp_sheet_view_insert_column_with_attributes (PsppSheetView     *tree_view,
8960                                              gint             position,
8961                                              const gchar     *title,
8962                                              GtkCellRenderer *cell,
8963                                              ...)
8964 {
8965   PsppSheetViewColumn *column;
8966   gchar *attribute;
8967   va_list args;
8968   gint column_id;
8969
8970   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), -1);
8971
8972   column = pspp_sheet_view_column_new ();
8973   pspp_sheet_view_column_set_title (column, title);
8974   pspp_sheet_view_column_pack_start (column, cell, TRUE);
8975
8976   va_start (args, cell);
8977
8978   attribute = va_arg (args, gchar *);
8979
8980   while (attribute != NULL)
8981     {
8982       column_id = va_arg (args, gint);
8983       pspp_sheet_view_column_add_attribute (column, cell, attribute, column_id);
8984       attribute = va_arg (args, gchar *);
8985     }
8986
8987   va_end (args);
8988
8989   pspp_sheet_view_insert_column (tree_view, column, position);
8990
8991   return tree_view->priv->n_columns;
8992 }
8993
8994 /**
8995  * pspp_sheet_view_insert_column_with_data_func:
8996  * @tree_view: a #PsppSheetView
8997  * @position: Position to insert, -1 for append
8998  * @title: column title
8999  * @cell: cell renderer for column
9000  * @func: function to set attributes of cell renderer
9001  * @data: data for @func
9002  * @dnotify: destroy notifier for @data
9003  *
9004  * Convenience function that inserts a new column into the #PsppSheetView
9005  * with the given cell renderer and a #GtkCellDataFunc to set cell renderer
9006  * attributes (normally using data from the model). See also
9007  * pspp_sheet_view_column_set_cell_data_func(), pspp_sheet_view_column_pack_start().
9008  *
9009  * Return value: number of columns in the tree view post-insert
9010  **/
9011 gint
9012 pspp_sheet_view_insert_column_with_data_func  (PsppSheetView               *tree_view,
9013                                              gint                       position,
9014                                              const gchar               *title,
9015                                              GtkCellRenderer           *cell,
9016                                              PsppSheetCellDataFunc        func,
9017                                              gpointer                   data,
9018                                              GDestroyNotify             dnotify)
9019 {
9020   PsppSheetViewColumn *column;
9021
9022   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), -1);
9023
9024   column = pspp_sheet_view_column_new ();
9025   pspp_sheet_view_column_set_title (column, title);
9026   pspp_sheet_view_column_pack_start (column, cell, TRUE);
9027   pspp_sheet_view_column_set_cell_data_func (column, cell, func, data, dnotify);
9028
9029   pspp_sheet_view_insert_column (tree_view, column, position);
9030
9031   return tree_view->priv->n_columns;
9032 }
9033
9034 /**
9035  * pspp_sheet_view_get_column:
9036  * @tree_view: A #PsppSheetView.
9037  * @n: The position of the column, counting from 0.
9038  *
9039  * Gets the #PsppSheetViewColumn at the given position in the #tree_view.
9040  *
9041  * Return value: The #PsppSheetViewColumn, or %NULL if the position is outside the
9042  * range of columns.
9043  **/
9044 PsppSheetViewColumn *
9045 pspp_sheet_view_get_column (PsppSheetView *tree_view,
9046                           gint         n)
9047 {
9048   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
9049
9050   if (n < 0 || n >= tree_view->priv->n_columns)
9051     return NULL;
9052
9053   if (tree_view->priv->columns == NULL)
9054     return NULL;
9055
9056   return PSPP_SHEET_VIEW_COLUMN (g_list_nth (tree_view->priv->columns, n)->data);
9057 }
9058
9059 /**
9060  * pspp_sheet_view_get_columns:
9061  * @tree_view: A #PsppSheetView
9062  *
9063  * Returns a #GList of all the #PsppSheetViewColumn s currently in @tree_view.
9064  * The returned list must be freed with g_list_free ().
9065  *
9066  * Return value: (element-type PsppSheetViewColumn) (transfer container): A list of #PsppSheetViewColumn s
9067  **/
9068 GList *
9069 pspp_sheet_view_get_columns (PsppSheetView *tree_view)
9070 {
9071   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
9072
9073   return g_list_copy (tree_view->priv->columns);
9074 }
9075
9076 /**
9077  * pspp_sheet_view_move_column_after:
9078  * @tree_view: A #PsppSheetView
9079  * @column: The #PsppSheetViewColumn to be moved.
9080  * @base_column: (allow-none): The #PsppSheetViewColumn to be moved relative to, or %NULL.
9081  *
9082  * Moves @column to be after to @base_column.  If @base_column is %NULL, then
9083  * @column is placed in the first position.
9084  **/
9085 void
9086 pspp_sheet_view_move_column_after (PsppSheetView       *tree_view,
9087                                  PsppSheetViewColumn *column,
9088                                  PsppSheetViewColumn *base_column)
9089 {
9090   GList *column_list_el, *base_el = NULL;
9091
9092   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9093
9094   column_list_el = g_list_find (tree_view->priv->columns, column);
9095   g_return_if_fail (column_list_el != NULL);
9096
9097   if (base_column)
9098     {
9099       base_el = g_list_find (tree_view->priv->columns, base_column);
9100       g_return_if_fail (base_el != NULL);
9101     }
9102
9103   if (column_list_el->prev == base_el)
9104     return;
9105
9106   tree_view->priv->columns = g_list_remove_link (tree_view->priv->columns, column_list_el);
9107   if (base_el == NULL)
9108     {
9109       column_list_el->prev = NULL;
9110       column_list_el->next = tree_view->priv->columns;
9111       if (column_list_el->next)
9112         column_list_el->next->prev = column_list_el;
9113       tree_view->priv->columns = column_list_el;
9114     }
9115   else
9116     {
9117       column_list_el->prev = base_el;
9118       column_list_el->next = base_el->next;
9119       if (column_list_el->next)
9120         column_list_el->next->prev = column_list_el;
9121       base_el->next = column_list_el;
9122     }
9123
9124   if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
9125     {
9126       gtk_widget_queue_resize (GTK_WIDGET (tree_view));
9127       pspp_sheet_view_size_allocate_columns (GTK_WIDGET (tree_view), NULL);
9128     }
9129
9130   g_signal_emit (tree_view, tree_view_signals[COLUMNS_CHANGED], 0);
9131 }
9132
9133 /**
9134  * pspp_sheet_view_set_column_drag_function:
9135  * @tree_view: A #PsppSheetView.
9136  * @func: (allow-none): A function to determine which columns are reorderable, or %NULL.
9137  * @user_data: (allow-none): User data to be passed to @func, or %NULL
9138  * @destroy: (allow-none): Destroy notifier for @user_data, or %NULL
9139  *
9140  * Sets a user function for determining where a column may be dropped when
9141  * dragged.  This function is called on every column pair in turn at the
9142  * beginning of a column drag to determine where a drop can take place.  The
9143  * arguments passed to @func are: the @tree_view, the #PsppSheetViewColumn being
9144  * dragged, the two #PsppSheetViewColumn s determining the drop spot, and
9145  * @user_data.  If either of the #PsppSheetViewColumn arguments for the drop spot
9146  * are %NULL, then they indicate an edge.  If @func is set to be %NULL, then
9147  * @tree_view reverts to the default behavior of allowing all columns to be
9148  * dropped everywhere.
9149  **/
9150 void
9151 pspp_sheet_view_set_column_drag_function (PsppSheetView               *tree_view,
9152                                         PsppSheetViewColumnDropFunc  func,
9153                                         gpointer                   user_data,
9154                                         GDestroyNotify             destroy)
9155 {
9156   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9157
9158   if (tree_view->priv->column_drop_func_data_destroy)
9159     tree_view->priv->column_drop_func_data_destroy (tree_view->priv->column_drop_func_data);
9160
9161   tree_view->priv->column_drop_func = func;
9162   tree_view->priv->column_drop_func_data = user_data;
9163   tree_view->priv->column_drop_func_data_destroy = destroy;
9164 }
9165
9166 /**
9167  * pspp_sheet_view_scroll_to_point:
9168  * @tree_view: a #PsppSheetView
9169  * @tree_x: X coordinate of new top-left pixel of visible area, or -1
9170  * @tree_y: Y coordinate of new top-left pixel of visible area, or -1
9171  *
9172  * Scrolls the tree view such that the top-left corner of the visible
9173  * area is @tree_x, @tree_y, where @tree_x and @tree_y are specified
9174  * in tree coordinates.  The @tree_view must be realized before
9175  * this function is called.  If it isn't, you probably want to be
9176  * using pspp_sheet_view_scroll_to_cell().
9177  *
9178  * If either @tree_x or @tree_y are -1, then that direction isn't scrolled.
9179  **/
9180 void
9181 pspp_sheet_view_scroll_to_point (PsppSheetView *tree_view,
9182                                gint         tree_x,
9183                                gint         tree_y)
9184 {
9185   GtkAdjustment *hadj;
9186   GtkAdjustment *vadj;
9187
9188   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9189   g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (tree_view)));
9190
9191   hadj = tree_view->priv->hadjustment;
9192   vadj = tree_view->priv->vadjustment;
9193
9194   if (tree_x != -1)
9195     gtk_adjustment_set_value (hadj, CLAMP (tree_x, hadj->lower, hadj->upper - hadj->page_size));
9196   if (tree_y != -1)
9197     gtk_adjustment_set_value (vadj, CLAMP (tree_y, vadj->lower, vadj->upper - vadj->page_size));
9198 }
9199
9200 /**
9201  * pspp_sheet_view_scroll_to_cell:
9202  * @tree_view: A #PsppSheetView.
9203  * @path: (allow-none): The path of the row to move to, or %NULL.
9204  * @column: (allow-none): The #PsppSheetViewColumn to move horizontally to, or %NULL.
9205  * @use_align: whether to use alignment arguments, or %FALSE.
9206  * @row_align: The vertical alignment of the row specified by @path.
9207  * @col_align: The horizontal alignment of the column specified by @column.
9208  *
9209  * Moves the alignments of @tree_view to the position specified by @column and
9210  * @path.  If @column is %NULL, then no horizontal scrolling occurs.  Likewise,
9211  * if @path is %NULL no vertical scrolling occurs.  At a minimum, one of @column
9212  * or @path need to be non-%NULL.  @row_align determines where the row is
9213  * placed, and @col_align determines where @column is placed.  Both are expected
9214  * to be between 0.0 and 1.0. 0.0 means left/top alignment, 1.0 means
9215  * right/bottom alignment, 0.5 means center.
9216  *
9217  * If @use_align is %FALSE, then the alignment arguments are ignored, and the
9218  * tree does the minimum amount of work to scroll the cell onto the screen.
9219  * This means that the cell will be scrolled to the edge closest to its current
9220  * position.  If the cell is currently visible on the screen, nothing is done.
9221  *
9222  * This function only works if the model is set, and @path is a valid row on the
9223  * model.  If the model changes before the @tree_view is realized, the centered
9224  * path will be modified to reflect this change.
9225  **/
9226 void
9227 pspp_sheet_view_scroll_to_cell (PsppSheetView       *tree_view,
9228                               GtkTreePath       *path,
9229                               PsppSheetViewColumn *column,
9230                               gboolean           use_align,
9231                               gfloat             row_align,
9232                               gfloat             col_align)
9233 {
9234   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9235   g_return_if_fail (tree_view->priv->model != NULL);
9236   g_return_if_fail (row_align >= 0.0 && row_align <= 1.0);
9237   g_return_if_fail (col_align >= 0.0 && col_align <= 1.0);
9238   g_return_if_fail (path != NULL || column != NULL);
9239
9240 #if 0
9241   g_print ("pspp_sheet_view_scroll_to_cell:\npath: %s\ncolumn: %s\nuse_align: %d\nrow_align: %f\ncol_align: %f\n",
9242            gtk_tree_path_to_string (path), column?"non-null":"null", use_align, row_align, col_align);
9243 #endif
9244   row_align = CLAMP (row_align, 0.0, 1.0);
9245   col_align = CLAMP (col_align, 0.0, 1.0);
9246
9247
9248   /* Note: Despite the benefits that come from having one code path for the
9249    * scrolling code, we short-circuit validate_visible_area's immplementation as
9250    * it is much slower than just going to the point.
9251    */
9252   if (!gtk_widget_get_visible (GTK_WIDGET (tree_view)) ||
9253       !gtk_widget_get_realized (GTK_WIDGET (tree_view))
9254       /* XXX || GTK_WIDGET_ALLOC_NEEDED (tree_view) */)
9255     {
9256       if (tree_view->priv->scroll_to_path)
9257         gtk_tree_row_reference_free (tree_view->priv->scroll_to_path);
9258
9259       tree_view->priv->scroll_to_path = NULL;
9260       tree_view->priv->scroll_to_column = NULL;
9261
9262       if (path)
9263         tree_view->priv->scroll_to_path = gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view), tree_view->priv->model, path);
9264       if (column)
9265         tree_view->priv->scroll_to_column = column;
9266       tree_view->priv->scroll_to_use_align = use_align;
9267       tree_view->priv->scroll_to_row_align = row_align;
9268       tree_view->priv->scroll_to_col_align = col_align;
9269
9270       install_presize_handler (tree_view);
9271     }
9272   else
9273     {
9274       GdkRectangle cell_rect;
9275       GdkRectangle vis_rect;
9276       gint dest_x, dest_y;
9277
9278       pspp_sheet_view_get_background_area (tree_view, path, column, &cell_rect);
9279       pspp_sheet_view_get_visible_rect (tree_view, &vis_rect);
9280
9281       cell_rect.y = TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, cell_rect.y);
9282
9283       dest_x = vis_rect.x;
9284       dest_y = vis_rect.y;
9285
9286       if (column)
9287         {
9288           if (use_align)
9289             {
9290               dest_x = cell_rect.x - ((vis_rect.width - cell_rect.width) * col_align);
9291             }
9292           else
9293             {
9294               if (cell_rect.x < vis_rect.x)
9295                 dest_x = cell_rect.x;
9296               if (cell_rect.x + cell_rect.width > vis_rect.x + vis_rect.width)
9297                 dest_x = cell_rect.x + cell_rect.width - vis_rect.width;
9298             }
9299         }
9300
9301       if (path)
9302         {
9303           if (use_align)
9304             {
9305               dest_y = cell_rect.y - ((vis_rect.height - cell_rect.height) * row_align);
9306               dest_y = MAX (dest_y, 0);
9307             }
9308           else
9309             {
9310               if (cell_rect.y < vis_rect.y)
9311                 dest_y = cell_rect.y;
9312               if (cell_rect.y + cell_rect.height > vis_rect.y + vis_rect.height)
9313                 dest_y = cell_rect.y + cell_rect.height - vis_rect.height;
9314             }
9315         }
9316
9317       pspp_sheet_view_scroll_to_point (tree_view, dest_x, dest_y);
9318     }
9319 }
9320
9321 /**
9322  * pspp_sheet_view_row_activated:
9323  * @tree_view: A #PsppSheetView
9324  * @path: The #GtkTreePath to be activated.
9325  * @column: The #PsppSheetViewColumn to be activated.
9326  *
9327  * Activates the cell determined by @path and @column.
9328  **/
9329 void
9330 pspp_sheet_view_row_activated (PsppSheetView       *tree_view,
9331                              GtkTreePath       *path,
9332                              PsppSheetViewColumn *column)
9333 {
9334   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9335
9336   g_signal_emit (tree_view, tree_view_signals[ROW_ACTIVATED], 0, path, column);
9337 }
9338
9339
9340 /**
9341  * pspp_sheet_view_get_reorderable:
9342  * @tree_view: a #PsppSheetView
9343  *
9344  * Retrieves whether the user can reorder the tree via drag-and-drop. See
9345  * pspp_sheet_view_set_reorderable().
9346  *
9347  * Return value: %TRUE if the tree can be reordered.
9348  **/
9349 gboolean
9350 pspp_sheet_view_get_reorderable (PsppSheetView *tree_view)
9351 {
9352   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
9353
9354   return tree_view->priv->reorderable;
9355 }
9356
9357 /**
9358  * pspp_sheet_view_set_reorderable:
9359  * @tree_view: A #PsppSheetView.
9360  * @reorderable: %TRUE, if the tree can be reordered.
9361  *
9362  * This function is a convenience function to allow you to reorder
9363  * models that support the #GtkDragSourceIface and the
9364  * #GtkDragDestIface.  Both #GtkTreeStore and #GtkListStore support
9365  * these.  If @reorderable is %TRUE, then the user can reorder the
9366  * model by dragging and dropping rows. The developer can listen to
9367  * these changes by connecting to the model's row_inserted and
9368  * row_deleted signals. The reordering is implemented by setting up
9369  * the tree view as a drag source and destination. Therefore, drag and
9370  * drop can not be used in a reorderable view for any other purpose.
9371  *
9372  * This function does not give you any degree of control over the order -- any
9373  * reordering is allowed.  If more control is needed, you should probably
9374  * handle drag and drop manually.
9375  **/
9376 void
9377 pspp_sheet_view_set_reorderable (PsppSheetView *tree_view,
9378                                gboolean     reorderable)
9379 {
9380   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9381
9382   reorderable = reorderable != FALSE;
9383
9384   if (tree_view->priv->reorderable == reorderable)
9385     return;
9386
9387   if (reorderable)
9388     {
9389       const GtkTargetEntry row_targets[] = {
9390         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, 0 }
9391       };
9392
9393       pspp_sheet_view_enable_model_drag_source (tree_view,
9394                                               GDK_BUTTON1_MASK,
9395                                               row_targets,
9396                                               G_N_ELEMENTS (row_targets),
9397                                               GDK_ACTION_MOVE);
9398       pspp_sheet_view_enable_model_drag_dest (tree_view,
9399                                             row_targets,
9400                                             G_N_ELEMENTS (row_targets),
9401                                             GDK_ACTION_MOVE);
9402     }
9403   else
9404     {
9405       pspp_sheet_view_unset_rows_drag_source (tree_view);
9406       pspp_sheet_view_unset_rows_drag_dest (tree_view);
9407     }
9408
9409   tree_view->priv->reorderable = reorderable;
9410
9411   g_object_notify (G_OBJECT (tree_view), "reorderable");
9412 }
9413
9414 static void
9415 pspp_sheet_view_real_set_cursor (PsppSheetView     *tree_view,
9416                                GtkTreePath     *path,
9417                                gboolean         clear_and_select,
9418                                gboolean         clamp_node)
9419 {
9420   int node = -1;
9421
9422   if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
9423     {
9424       GtkTreePath *cursor_path;
9425       cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
9426       pspp_sheet_view_queue_draw_path (tree_view, cursor_path, NULL);
9427       gtk_tree_path_free (cursor_path);
9428     }
9429
9430   gtk_tree_row_reference_free (tree_view->priv->cursor);
9431   tree_view->priv->cursor = NULL;
9432
9433   _pspp_sheet_view_find_node (tree_view, path, &node);
9434   tree_view->priv->cursor =
9435     gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view),
9436                                       tree_view->priv->model,
9437                                       path);
9438
9439   if (tree_view->priv->row_count > 0)
9440     {
9441       int new_node = -1;
9442
9443       if (clear_and_select && !tree_view->priv->ctrl_pressed)
9444         {
9445           GtkTreeSelectMode mode = 0;
9446
9447           if (tree_view->priv->ctrl_pressed)
9448             mode |= GTK_TREE_SELECT_MODE_TOGGLE;
9449           if (tree_view->priv->shift_pressed)
9450             mode |= GTK_TREE_SELECT_MODE_EXTEND;
9451
9452           _pspp_sheet_selection_internal_select_node (tree_view->priv->selection,
9453                                                     node, path, mode,
9454                                                     FALSE);
9455         }
9456
9457       /* We have to re-find tree and node here again, somebody might have
9458        * cleared the node or the whole tree in the PsppSheetSelection::changed
9459        * callback. If the nodes differ we bail out here.
9460        */
9461       _pspp_sheet_view_find_node (tree_view, path, &new_node);
9462
9463       if (node != new_node)
9464         return;
9465
9466       if (clamp_node)
9467         {
9468           pspp_sheet_view_clamp_node_visible (tree_view, node);
9469           _pspp_sheet_view_queue_draw_node (tree_view, node, NULL);
9470         }
9471     }
9472
9473   g_signal_emit (tree_view, tree_view_signals[CURSOR_CHANGED], 0);
9474 }
9475
9476 /**
9477  * pspp_sheet_view_get_cursor:
9478  * @tree_view: A #PsppSheetView
9479  * @path: (allow-none): A pointer to be filled with the current cursor path, or %NULL
9480  * @focus_column: (allow-none): A pointer to be filled with the current focus column, or %NULL
9481  *
9482  * Fills in @path and @focus_column with the current path and focus column.  If
9483  * the cursor isn't currently set, then *@path will be %NULL.  If no column
9484  * currently has focus, then *@focus_column will be %NULL.
9485  *
9486  * The returned #GtkTreePath must be freed with gtk_tree_path_free() when
9487  * you are done with it.
9488  **/
9489 void
9490 pspp_sheet_view_get_cursor (PsppSheetView        *tree_view,
9491                           GtkTreePath       **path,
9492                           PsppSheetViewColumn **focus_column)
9493 {
9494   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9495
9496   if (path)
9497     {
9498       if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
9499         *path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
9500       else
9501         *path = NULL;
9502     }
9503
9504   if (focus_column)
9505     {
9506       *focus_column = tree_view->priv->focus_column;
9507     }
9508 }
9509
9510 /**
9511  * pspp_sheet_view_set_cursor:
9512  * @tree_view: A #PsppSheetView
9513  * @path: A #GtkTreePath
9514  * @focus_column: (allow-none): A #PsppSheetViewColumn, or %NULL
9515  * @start_editing: %TRUE if the specified cell should start being edited.
9516  *
9517  * Sets the current keyboard focus to be at @path, and selects it.  This is
9518  * useful when you want to focus the user's attention on a particular row.  If
9519  * @focus_column is not %NULL, then focus is given to the column specified by 
9520  * it. Additionally, if @focus_column is specified, and @start_editing is 
9521  * %TRUE, then editing should be started in the specified cell.  
9522  * This function is often followed by @gtk_widget_grab_focus (@tree_view) 
9523  * in order to give keyboard focus to the widget.  Please note that editing 
9524  * can only happen when the widget is realized.
9525  *
9526  * If @path is invalid for @model, the current cursor (if any) will be unset
9527  * and the function will return without failing.
9528  **/
9529 void
9530 pspp_sheet_view_set_cursor (PsppSheetView       *tree_view,
9531                           GtkTreePath       *path,
9532                           PsppSheetViewColumn *focus_column,
9533                           gboolean           start_editing)
9534 {
9535   pspp_sheet_view_set_cursor_on_cell (tree_view, path, focus_column,
9536                                     NULL, start_editing);
9537 }
9538
9539 /**
9540  * pspp_sheet_view_set_cursor_on_cell:
9541  * @tree_view: A #PsppSheetView
9542  * @path: A #GtkTreePath
9543  * @focus_column: (allow-none): A #PsppSheetViewColumn, or %NULL
9544  * @focus_cell: (allow-none): A #GtkCellRenderer, or %NULL
9545  * @start_editing: %TRUE if the specified cell should start being edited.
9546  *
9547  * Sets the current keyboard focus to be at @path, and selects it.  This is
9548  * useful when you want to focus the user's attention on a particular row.  If
9549  * @focus_column is not %NULL, then focus is given to the column specified by
9550  * it. If @focus_column and @focus_cell are not %NULL, and @focus_column
9551  * contains 2 or more editable or activatable cells, then focus is given to
9552  * the cell specified by @focus_cell. Additionally, if @focus_column is
9553  * specified, and @start_editing is %TRUE, then editing should be started in
9554  * the specified cell.  This function is often followed by
9555  * @gtk_widget_grab_focus (@tree_view) in order to give keyboard focus to the
9556  * widget.  Please note that editing can only happen when the widget is
9557  * realized.
9558  *
9559  * If @path is invalid for @model, the current cursor (if any) will be unset
9560  * and the function will return without failing.
9561  *
9562  * Since: 2.2
9563  **/
9564 void
9565 pspp_sheet_view_set_cursor_on_cell (PsppSheetView       *tree_view,
9566                                   GtkTreePath       *path,
9567                                   PsppSheetViewColumn *focus_column,
9568                                   GtkCellRenderer   *focus_cell,
9569                                   gboolean           start_editing)
9570 {
9571   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9572   g_return_if_fail (path != NULL);
9573   g_return_if_fail (focus_column == NULL || PSPP_IS_SHEET_VIEW_COLUMN (focus_column));
9574
9575   if (!tree_view->priv->model)
9576     return;
9577
9578   if (focus_cell)
9579     {
9580       g_return_if_fail (focus_column);
9581       g_return_if_fail (GTK_IS_CELL_RENDERER (focus_cell));
9582     }
9583
9584   /* cancel the current editing, if it exists */
9585   if (tree_view->priv->edited_column &&
9586       tree_view->priv->edited_column->editable_widget)
9587     pspp_sheet_view_stop_editing (tree_view, TRUE);
9588
9589   pspp_sheet_view_real_set_cursor (tree_view, path, TRUE, TRUE);
9590
9591   if (focus_column && focus_column->visible)
9592     {
9593       GList *list;
9594       gboolean column_in_tree = FALSE;
9595
9596       for (list = tree_view->priv->columns; list; list = list->next)
9597         if (list->data == focus_column)
9598           {
9599             column_in_tree = TRUE;
9600             break;
9601           }
9602       g_return_if_fail (column_in_tree);
9603       tree_view->priv->focus_column = focus_column;
9604       if (focus_cell)
9605         pspp_sheet_view_column_focus_cell (focus_column, focus_cell);
9606       if (start_editing)
9607         pspp_sheet_view_start_editing (tree_view, path);
9608     }
9609 }
9610
9611 /**
9612  * pspp_sheet_view_get_bin_window:
9613  * @tree_view: A #PsppSheetView
9614  * 
9615  * Returns the window that @tree_view renders to.  This is used primarily to
9616  * compare to <literal>event->window</literal> to confirm that the event on
9617  * @tree_view is on the right window.
9618  * 
9619  * Return value: A #GdkWindow, or %NULL when @tree_view hasn't been realized yet
9620  **/
9621 GdkWindow *
9622 pspp_sheet_view_get_bin_window (PsppSheetView *tree_view)
9623 {
9624   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
9625
9626   return tree_view->priv->bin_window;
9627 }
9628
9629 /**
9630  * pspp_sheet_view_get_path_at_pos:
9631  * @tree_view: A #PsppSheetView.
9632  * @x: The x position to be identified (relative to bin_window).
9633  * @y: The y position to be identified (relative to bin_window).
9634  * @path: (out) (allow-none): A pointer to a #GtkTreePath pointer to be filled in, or %NULL
9635  * @column: (out) (allow-none): A pointer to a #PsppSheetViewColumn pointer to be filled in, or %NULL
9636  * @cell_x: (out) (allow-none): A pointer where the X coordinate relative to the cell can be placed, or %NULL
9637  * @cell_y: (out) (allow-none): A pointer where the Y coordinate relative to the cell can be placed, or %NULL
9638  *
9639  * Finds the path at the point (@x, @y), relative to bin_window coordinates
9640  * (please see pspp_sheet_view_get_bin_window()).
9641  * That is, @x and @y are relative to an events coordinates. @x and @y must
9642  * come from an event on the @tree_view only where <literal>event->window ==
9643  * pspp_sheet_view_get_bin_window (<!-- -->)</literal>. It is primarily for
9644  * things like popup menus. If @path is non-%NULL, then it will be filled
9645  * with the #GtkTreePath at that point.  This path should be freed with
9646  * gtk_tree_path_free().  If @column is non-%NULL, then it will be filled
9647  * with the column at that point.  @cell_x and @cell_y return the coordinates
9648  * relative to the cell background (i.e. the @background_area passed to
9649  * gtk_cell_renderer_render()).  This function is only meaningful if
9650  * @tree_view is realized.  Therefore this function will always return %FALSE
9651  * if @tree_view is not realized or does not have a model.
9652  *
9653  * For converting widget coordinates (eg. the ones you get from
9654  * GtkWidget::query-tooltip), please see
9655  * pspp_sheet_view_convert_widget_to_bin_window_coords().
9656  *
9657  * Return value: %TRUE if a row exists at that coordinate.
9658  **/
9659 gboolean
9660 pspp_sheet_view_get_path_at_pos (PsppSheetView        *tree_view,
9661                                gint                x,
9662                                gint                y,
9663                                GtkTreePath       **path,
9664                                PsppSheetViewColumn **column,
9665                                gint               *cell_x,
9666                                gint               *cell_y)
9667 {
9668   int node;
9669   gint y_offset;
9670
9671   g_return_val_if_fail (tree_view != NULL, FALSE);
9672
9673   if (path)
9674     *path = NULL;
9675   if (column)
9676     *column = NULL;
9677
9678   if (tree_view->priv->bin_window == NULL)
9679     return FALSE;
9680
9681   if (tree_view->priv->row_count == 0)
9682     return FALSE;
9683
9684   if (x > tree_view->priv->hadjustment->upper)
9685     return FALSE;
9686
9687   if (x < 0 || y < 0)
9688     return FALSE;
9689
9690   if (column || cell_x)
9691     {
9692       PsppSheetViewColumn *tmp_column;
9693       PsppSheetViewColumn *last_column = NULL;
9694       GList *list;
9695       gint remaining_x = x;
9696       gboolean found = FALSE;
9697       gboolean rtl;
9698
9699       rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
9700       for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns));
9701            list;
9702            list = (rtl ? list->prev : list->next))
9703         {
9704           tmp_column = list->data;
9705
9706           if (tmp_column->visible == FALSE)
9707             continue;
9708
9709           last_column = tmp_column;
9710           if (remaining_x <= tmp_column->width)
9711             {
9712               found = TRUE;
9713
9714               if (column)
9715                 *column = tmp_column;
9716
9717               if (cell_x)
9718                 *cell_x = remaining_x;
9719
9720               break;
9721             }
9722           remaining_x -= tmp_column->width;
9723         }
9724
9725       /* If found is FALSE and there is a last_column, then it the remainder
9726        * space is in that area
9727        */
9728       if (!found)
9729         {
9730           if (last_column)
9731             {
9732               if (column)
9733                 *column = last_column;
9734               
9735               if (cell_x)
9736                 *cell_x = last_column->width + remaining_x;
9737             }
9738           else
9739             {
9740               return FALSE;
9741             }
9742         }
9743     }
9744
9745   y_offset = pspp_sheet_view_find_offset (tree_view,
9746                                           TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, y),
9747                                           &node);
9748
9749   if (node < 0)
9750     return FALSE;
9751
9752   if (cell_y)
9753     *cell_y = y_offset;
9754
9755   if (path)
9756     *path = _pspp_sheet_view_find_path (tree_view, node);
9757
9758   return TRUE;
9759 }
9760
9761
9762 /**
9763  * pspp_sheet_view_get_cell_area:
9764  * @tree_view: a #PsppSheetView
9765  * @path: (allow-none): a #GtkTreePath for the row, or %NULL to get only horizontal coordinates
9766  * @column: (allow-none): a #PsppSheetViewColumn for the column, or %NULL to get only vertical coordinates
9767  * @rect: rectangle to fill with cell rect
9768  *
9769  * Fills the bounding rectangle in bin_window coordinates for the cell at the
9770  * row specified by @path and the column specified by @column.  If @path is
9771  * %NULL, or points to a path not currently displayed, the @y and @height fields
9772  * of the rectangle will be filled with 0. If @column is %NULL, the @x and @width
9773  * fields will be filled with 0.  The sum of all cell rects does not cover the
9774  * entire tree; there are extra pixels in between rows, for example. The
9775  * returned rectangle is equivalent to the @cell_area passed to
9776  * gtk_cell_renderer_render().  This function is only valid if @tree_view is
9777  * realized.
9778  **/
9779 void
9780 pspp_sheet_view_get_cell_area (PsppSheetView        *tree_view,
9781                              GtkTreePath        *path,
9782                              PsppSheetViewColumn  *column,
9783                              GdkRectangle       *rect)
9784 {
9785   int node = -1;
9786   gint vertical_separator;
9787   gint horizontal_separator;
9788
9789   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9790   g_return_if_fail (column == NULL || PSPP_IS_SHEET_VIEW_COLUMN (column));
9791   g_return_if_fail (rect != NULL);
9792   g_return_if_fail (!column || column->tree_view == (GtkWidget *) tree_view);
9793   g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (tree_view)));
9794
9795   gtk_widget_style_get (GTK_WIDGET (tree_view),
9796                         "vertical-separator", &vertical_separator,
9797                         "horizontal-separator", &horizontal_separator,
9798                         NULL);
9799
9800   rect->x = 0;
9801   rect->y = 0;
9802   rect->width = 0;
9803   rect->height = 0;
9804
9805   if (column)
9806     {
9807       rect->x = column->button->allocation.x + horizontal_separator/2;
9808       rect->width = column->button->allocation.width - horizontal_separator;
9809     }
9810
9811   if (path)
9812     {
9813       _pspp_sheet_view_find_node (tree_view, path, &node);
9814
9815       /* Get vertical coords */
9816       if (node < 0)
9817         return;
9818
9819       rect->y = CELL_FIRST_PIXEL (tree_view, node, vertical_separator);
9820       rect->height = MAX (CELL_HEIGHT (tree_view, vertical_separator), tree_view->priv->expander_size - vertical_separator);
9821     }
9822 }
9823
9824 /**
9825  * pspp_sheet_view_get_background_area:
9826  * @tree_view: a #PsppSheetView
9827  * @path: (allow-none): a #GtkTreePath for the row, or %NULL to get only horizontal coordinates
9828  * @column: (allow-none): a #PsppSheetViewColumn for the column, or %NULL to get only vertical coordiantes
9829  * @rect: rectangle to fill with cell background rect
9830  *
9831  * Fills the bounding rectangle in bin_window coordinates for the cell at the
9832  * row specified by @path and the column specified by @column.  If @path is
9833  * %NULL, or points to a node not found in the tree, the @y and @height fields of
9834  * the rectangle will be filled with 0. If @column is %NULL, the @x and @width
9835  * fields will be filled with 0.  The returned rectangle is equivalent to the
9836  * @background_area passed to gtk_cell_renderer_render().  These background
9837  * areas tile to cover the entire bin window.  Contrast with the @cell_area,
9838  * returned by pspp_sheet_view_get_cell_area(), which returns only the cell
9839  * itself, excluding surrounding borders.
9840  *
9841  **/
9842 void
9843 pspp_sheet_view_get_background_area (PsppSheetView        *tree_view,
9844                                    GtkTreePath        *path,
9845                                    PsppSheetViewColumn  *column,
9846                                    GdkRectangle       *rect)
9847 {
9848   int node = -1;
9849
9850   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9851   g_return_if_fail (column == NULL || PSPP_IS_SHEET_VIEW_COLUMN (column));
9852   g_return_if_fail (rect != NULL);
9853
9854   rect->x = 0;
9855   rect->y = 0;
9856   rect->width = 0;
9857   rect->height = 0;
9858
9859   if (path)
9860     {
9861       /* Get vertical coords */
9862
9863       _pspp_sheet_view_find_node (tree_view, path, &node);
9864       if (node < 0)
9865         return;
9866
9867       rect->y = BACKGROUND_FIRST_PIXEL (tree_view, node);
9868
9869       rect->height = ROW_HEIGHT (tree_view);
9870     }
9871
9872   if (column)
9873     {
9874       gint x2 = 0;
9875
9876       pspp_sheet_view_get_background_xrange (tree_view, column, &rect->x, &x2);
9877       rect->width = x2 - rect->x;
9878     }
9879 }
9880
9881 /**
9882  * pspp_sheet_view_get_visible_rect:
9883  * @tree_view: a #PsppSheetView
9884  * @visible_rect: rectangle to fill
9885  *
9886  * Fills @visible_rect with the currently-visible region of the
9887  * buffer, in tree coordinates. Convert to bin_window coordinates with
9888  * pspp_sheet_view_convert_tree_to_bin_window_coords().
9889  * Tree coordinates start at 0,0 for row 0 of the tree, and cover the entire
9890  * scrollable area of the tree.
9891  **/
9892 void
9893 pspp_sheet_view_get_visible_rect (PsppSheetView  *tree_view,
9894                                 GdkRectangle *visible_rect)
9895 {
9896   GtkWidget *widget;
9897
9898   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9899
9900   widget = GTK_WIDGET (tree_view);
9901
9902   if (visible_rect)
9903     {
9904       visible_rect->x = tree_view->priv->hadjustment->value;
9905       visible_rect->y = tree_view->priv->vadjustment->value;
9906       visible_rect->width = widget->allocation.width;
9907       visible_rect->height = widget->allocation.height - TREE_VIEW_HEADER_HEIGHT (tree_view);
9908     }
9909 }
9910
9911 /**
9912  * pspp_sheet_view_widget_to_tree_coords:
9913  * @tree_view: a #PsppSheetView
9914  * @wx: X coordinate relative to bin_window
9915  * @wy: Y coordinate relative to bin_window
9916  * @tx: return location for tree X coordinate
9917  * @ty: return location for tree Y coordinate
9918  *
9919  * Converts bin_window coordinates to coordinates for the
9920  * tree (the full scrollable area of the tree).
9921  *
9922  * Deprecated: 2.12: Due to historial reasons the name of this function is
9923  * incorrect.  For converting coordinates relative to the widget to
9924  * bin_window coordinates, please see
9925  * pspp_sheet_view_convert_widget_to_bin_window_coords().
9926  *
9927  **/
9928 void
9929 pspp_sheet_view_widget_to_tree_coords (PsppSheetView *tree_view,
9930                                       gint         wx,
9931                                       gint         wy,
9932                                       gint        *tx,
9933                                       gint        *ty)
9934 {
9935   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9936
9937   if (tx)
9938     *tx = wx + tree_view->priv->hadjustment->value;
9939   if (ty)
9940     *ty = wy + tree_view->priv->dy;
9941 }
9942
9943 /**
9944  * pspp_sheet_view_tree_to_widget_coords:
9945  * @tree_view: a #PsppSheetView
9946  * @tx: tree X coordinate
9947  * @ty: tree Y coordinate
9948  * @wx: return location for X coordinate relative to bin_window
9949  * @wy: return location for Y coordinate relative to bin_window
9950  *
9951  * Converts tree coordinates (coordinates in full scrollable area of the tree)
9952  * to bin_window coordinates.
9953  *
9954  * Deprecated: 2.12: Due to historial reasons the name of this function is
9955  * incorrect.  For converting bin_window coordinates to coordinates relative
9956  * to bin_window, please see
9957  * pspp_sheet_view_convert_bin_window_to_widget_coords().
9958  *
9959  **/
9960 void
9961 pspp_sheet_view_tree_to_widget_coords (PsppSheetView *tree_view,
9962                                      gint         tx,
9963                                      gint         ty,
9964                                      gint        *wx,
9965                                      gint        *wy)
9966 {
9967   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9968
9969   if (wx)
9970     *wx = tx - tree_view->priv->hadjustment->value;
9971   if (wy)
9972     *wy = ty - tree_view->priv->dy;
9973 }
9974
9975
9976 /**
9977  * pspp_sheet_view_convert_widget_to_tree_coords:
9978  * @tree_view: a #PsppSheetView
9979  * @wx: X coordinate relative to the widget
9980  * @wy: Y coordinate relative to the widget
9981  * @tx: return location for tree X coordinate
9982  * @ty: return location for tree Y coordinate
9983  *
9984  * Converts widget coordinates to coordinates for the
9985  * tree (the full scrollable area of the tree).
9986  *
9987  * Since: 2.12
9988  **/
9989 void
9990 pspp_sheet_view_convert_widget_to_tree_coords (PsppSheetView *tree_view,
9991                                              gint         wx,
9992                                              gint         wy,
9993                                              gint        *tx,
9994                                              gint        *ty)
9995 {
9996   gint x, y;
9997
9998   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9999
10000   pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view,
10001                                                      wx, wy,
10002                                                      &x, &y);
10003   pspp_sheet_view_convert_bin_window_to_tree_coords (tree_view,
10004                                                    x, y,
10005                                                    tx, ty);
10006 }
10007
10008 /**
10009  * pspp_sheet_view_convert_tree_to_widget_coords:
10010  * @tree_view: a #PsppSheetView
10011  * @tx: X coordinate relative to the tree
10012  * @ty: Y coordinate relative to the tree
10013  * @wx: return location for widget X coordinate
10014  * @wy: return location for widget Y coordinate
10015  *
10016  * Converts tree coordinates (coordinates in full scrollable area of the tree)
10017  * to widget coordinates.
10018  *
10019  * Since: 2.12
10020  **/
10021 void
10022 pspp_sheet_view_convert_tree_to_widget_coords (PsppSheetView *tree_view,
10023                                              gint         tx,
10024                                              gint         ty,
10025                                              gint        *wx,
10026                                              gint        *wy)
10027 {
10028   gint x, y;
10029
10030   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10031
10032   pspp_sheet_view_convert_tree_to_bin_window_coords (tree_view,
10033                                                    tx, ty,
10034                                                    &x, &y);
10035   pspp_sheet_view_convert_bin_window_to_widget_coords (tree_view,
10036                                                      x, y,
10037                                                      wx, wy);
10038 }
10039
10040 /**
10041  * pspp_sheet_view_convert_widget_to_bin_window_coords:
10042  * @tree_view: a #PsppSheetView
10043  * @wx: X coordinate relative to the widget
10044  * @wy: Y coordinate relative to the widget
10045  * @bx: return location for bin_window X coordinate
10046  * @by: return location for bin_window Y coordinate
10047  *
10048  * Converts widget coordinates to coordinates for the bin_window
10049  * (see pspp_sheet_view_get_bin_window()).
10050  *
10051  * Since: 2.12
10052  **/
10053 void
10054 pspp_sheet_view_convert_widget_to_bin_window_coords (PsppSheetView *tree_view,
10055                                                    gint         wx,
10056                                                    gint         wy,
10057                                                    gint        *bx,
10058                                                    gint        *by)
10059 {
10060   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10061
10062   if (bx)
10063     *bx = wx + tree_view->priv->hadjustment->value;
10064   if (by)
10065     *by = wy - TREE_VIEW_HEADER_HEIGHT (tree_view);
10066 }
10067
10068 /**
10069  * pspp_sheet_view_convert_bin_window_to_widget_coords:
10070  * @tree_view: a #PsppSheetView
10071  * @bx: bin_window X coordinate
10072  * @by: bin_window Y coordinate
10073  * @wx: return location for widget X coordinate
10074  * @wy: return location for widget Y coordinate
10075  *
10076  * Converts bin_window coordinates (see pspp_sheet_view_get_bin_window())
10077  * to widget relative coordinates.
10078  *
10079  * Since: 2.12
10080  **/
10081 void
10082 pspp_sheet_view_convert_bin_window_to_widget_coords (PsppSheetView *tree_view,
10083                                                    gint         bx,
10084                                                    gint         by,
10085                                                    gint        *wx,
10086                                                    gint        *wy)
10087 {
10088   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10089
10090   if (wx)
10091     *wx = bx - tree_view->priv->hadjustment->value;
10092   if (wy)
10093     *wy = by + TREE_VIEW_HEADER_HEIGHT (tree_view);
10094 }
10095
10096 /**
10097  * pspp_sheet_view_convert_tree_to_bin_window_coords:
10098  * @tree_view: a #PsppSheetView
10099  * @tx: tree X coordinate
10100  * @ty: tree Y coordinate
10101  * @bx: return location for X coordinate relative to bin_window
10102  * @by: return location for Y coordinate relative to bin_window
10103  *
10104  * Converts tree coordinates (coordinates in full scrollable area of the tree)
10105  * to bin_window coordinates.
10106  *
10107  * Since: 2.12
10108  **/
10109 void
10110 pspp_sheet_view_convert_tree_to_bin_window_coords (PsppSheetView *tree_view,
10111                                                  gint         tx,
10112                                                  gint         ty,
10113                                                  gint        *bx,
10114                                                  gint        *by)
10115 {
10116   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10117
10118   if (bx)
10119     *bx = tx;
10120   if (by)
10121     *by = ty - tree_view->priv->dy;
10122 }
10123
10124 /**
10125  * pspp_sheet_view_convert_bin_window_to_tree_coords:
10126  * @tree_view: a #PsppSheetView
10127  * @bx: X coordinate relative to bin_window
10128  * @by: Y coordinate relative to bin_window
10129  * @tx: return location for tree X coordinate
10130  * @ty: return location for tree Y coordinate
10131  *
10132  * Converts bin_window coordinates to coordinates for the
10133  * tree (the full scrollable area of the tree).
10134  *
10135  * Since: 2.12
10136  **/
10137 void
10138 pspp_sheet_view_convert_bin_window_to_tree_coords (PsppSheetView *tree_view,
10139                                                  gint         bx,
10140                                                  gint         by,
10141                                                  gint        *tx,
10142                                                  gint        *ty)
10143 {
10144   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10145
10146   if (tx)
10147     *tx = bx;
10148   if (ty)
10149     *ty = by + tree_view->priv->dy;
10150 }
10151
10152
10153
10154 /**
10155  * pspp_sheet_view_get_visible_range:
10156  * @tree_view: A #PsppSheetView
10157  * @start_path: (allow-none): Return location for start of region, or %NULL.
10158  * @end_path: (allow-none): Return location for end of region, or %NULL.
10159  *
10160  * Sets @start_path and @end_path to be the first and last visible path.
10161  * Note that there may be invisible paths in between.
10162  *
10163  * The paths should be freed with gtk_tree_path_free() after use.
10164  *
10165  * Returns: %TRUE, if valid paths were placed in @start_path and @end_path.
10166  *
10167  * Since: 2.8
10168  **/
10169 gboolean
10170 pspp_sheet_view_get_visible_range (PsppSheetView  *tree_view,
10171                                  GtkTreePath **start_path,
10172                                  GtkTreePath **end_path)
10173 {
10174   int node;
10175   gboolean retval;
10176   
10177   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
10178
10179   if (!tree_view->priv->row_count)
10180     return FALSE;
10181
10182   retval = TRUE;
10183
10184   if (start_path)
10185     {
10186       pspp_sheet_view_find_offset (tree_view,
10187                                    TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, 0),
10188                                    &node);
10189       if (node >= 0)
10190         *start_path = _pspp_sheet_view_find_path (tree_view, node);
10191       else
10192         retval = FALSE;
10193     }
10194
10195   if (end_path)
10196     {
10197       gint y;
10198
10199       if (tree_view->priv->height < tree_view->priv->vadjustment->page_size)
10200         y = tree_view->priv->height - 1;
10201       else
10202         y = TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, tree_view->priv->vadjustment->page_size) - 1;
10203
10204       pspp_sheet_view_find_offset (tree_view, y, &node);
10205       if (node >= 0)
10206         *end_path = _pspp_sheet_view_find_path (tree_view, node);
10207       else
10208         retval = FALSE;
10209     }
10210
10211   return retval;
10212 }
10213
10214 static void
10215 unset_reorderable (PsppSheetView *tree_view)
10216 {
10217   if (tree_view->priv->reorderable)
10218     {
10219       tree_view->priv->reorderable = FALSE;
10220       g_object_notify (G_OBJECT (tree_view), "reorderable");
10221     }
10222 }
10223
10224 /**
10225  * pspp_sheet_view_enable_model_drag_source:
10226  * @tree_view: a #PsppSheetView
10227  * @start_button_mask: Mask of allowed buttons to start drag
10228  * @targets: the table of targets that the drag will support
10229  * @n_targets: the number of items in @targets
10230  * @actions: the bitmask of possible actions for a drag from this
10231  *    widget
10232  *
10233  * Turns @tree_view into a drag source for automatic DND. Calling this
10234  * method sets #PsppSheetView:reorderable to %FALSE.
10235  **/
10236 void
10237 pspp_sheet_view_enable_model_drag_source (PsppSheetView              *tree_view,
10238                                         GdkModifierType           start_button_mask,
10239                                         const GtkTargetEntry     *targets,
10240                                         gint                      n_targets,
10241                                         GdkDragAction             actions)
10242 {
10243   TreeViewDragInfo *di;
10244
10245   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10246
10247   gtk_drag_source_set (GTK_WIDGET (tree_view),
10248                        0,
10249                        targets,
10250                        n_targets,
10251                        actions);
10252
10253   di = ensure_info (tree_view);
10254
10255   di->start_button_mask = start_button_mask;
10256   di->source_actions = actions;
10257   di->source_set = TRUE;
10258
10259   unset_reorderable (tree_view);
10260 }
10261
10262 /**
10263  * pspp_sheet_view_enable_model_drag_dest:
10264  * @tree_view: a #PsppSheetView
10265  * @targets: the table of targets that the drag will support
10266  * @n_targets: the number of items in @targets
10267  * @actions: the bitmask of possible actions for a drag from this
10268  *    widget
10269  * 
10270  * Turns @tree_view into a drop destination for automatic DND. Calling
10271  * this method sets #PsppSheetView:reorderable to %FALSE.
10272  **/
10273 void
10274 pspp_sheet_view_enable_model_drag_dest (PsppSheetView              *tree_view,
10275                                       const GtkTargetEntry     *targets,
10276                                       gint                      n_targets,
10277                                       GdkDragAction             actions)
10278 {
10279   TreeViewDragInfo *di;
10280
10281   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10282
10283   gtk_drag_dest_set (GTK_WIDGET (tree_view),
10284                      0,
10285                      targets,
10286                      n_targets,
10287                      actions);
10288
10289   di = ensure_info (tree_view);
10290   di->dest_set = TRUE;
10291
10292   unset_reorderable (tree_view);
10293 }
10294
10295 /**
10296  * pspp_sheet_view_unset_rows_drag_source:
10297  * @tree_view: a #PsppSheetView
10298  *
10299  * Undoes the effect of
10300  * pspp_sheet_view_enable_model_drag_source(). Calling this method sets
10301  * #PsppSheetView:reorderable to %FALSE.
10302  **/
10303 void
10304 pspp_sheet_view_unset_rows_drag_source (PsppSheetView *tree_view)
10305 {
10306   TreeViewDragInfo *di;
10307
10308   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10309
10310   di = get_info (tree_view);
10311
10312   if (di)
10313     {
10314       if (di->source_set)
10315         {
10316           gtk_drag_source_unset (GTK_WIDGET (tree_view));
10317           di->source_set = FALSE;
10318         }
10319
10320       if (!di->dest_set && !di->source_set)
10321         remove_info (tree_view);
10322     }
10323   
10324   unset_reorderable (tree_view);
10325 }
10326
10327 /**
10328  * pspp_sheet_view_unset_rows_drag_dest:
10329  * @tree_view: a #PsppSheetView
10330  *
10331  * Undoes the effect of
10332  * pspp_sheet_view_enable_model_drag_dest(). Calling this method sets
10333  * #PsppSheetView:reorderable to %FALSE.
10334  **/
10335 void
10336 pspp_sheet_view_unset_rows_drag_dest (PsppSheetView *tree_view)
10337 {
10338   TreeViewDragInfo *di;
10339
10340   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10341
10342   di = get_info (tree_view);
10343
10344   if (di)
10345     {
10346       if (di->dest_set)
10347         {
10348           gtk_drag_dest_unset (GTK_WIDGET (tree_view));
10349           di->dest_set = FALSE;
10350         }
10351
10352       if (!di->dest_set && !di->source_set)
10353         remove_info (tree_view);
10354     }
10355
10356   unset_reorderable (tree_view);
10357 }
10358
10359 /**
10360  * pspp_sheet_view_set_drag_dest_row:
10361  * @tree_view: a #PsppSheetView
10362  * @path: (allow-none): The path of the row to highlight, or %NULL.
10363  * @pos: Specifies whether to drop before, after or into the row
10364  * 
10365  * Sets the row that is highlighted for feedback.
10366  **/
10367 void
10368 pspp_sheet_view_set_drag_dest_row (PsppSheetView            *tree_view,
10369                                  GtkTreePath            *path,
10370                                  PsppSheetViewDropPosition pos)
10371 {
10372   GtkTreePath *current_dest;
10373
10374   /* Note; this function is exported to allow a custom DND
10375    * implementation, so it can't touch TreeViewDragInfo
10376    */
10377
10378   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10379
10380   current_dest = NULL;
10381
10382   if (tree_view->priv->drag_dest_row)
10383     {
10384       current_dest = gtk_tree_row_reference_get_path (tree_view->priv->drag_dest_row);
10385       gtk_tree_row_reference_free (tree_view->priv->drag_dest_row);
10386     }
10387
10388   /* special case a drop on an empty model */
10389   tree_view->priv->empty_view_drop = 0;
10390
10391   if (pos == PSPP_SHEET_VIEW_DROP_BEFORE && path
10392       && gtk_tree_path_get_depth (path) == 1
10393       && gtk_tree_path_get_indices (path)[0] == 0)
10394     {
10395       gint n_children;
10396
10397       n_children = gtk_tree_model_iter_n_children (tree_view->priv->model,
10398                                                    NULL);
10399
10400       if (!n_children)
10401         tree_view->priv->empty_view_drop = 1;
10402     }
10403
10404   tree_view->priv->drag_dest_pos = pos;
10405
10406   if (path)
10407     {
10408       tree_view->priv->drag_dest_row =
10409         gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view), tree_view->priv->model, path);
10410       pspp_sheet_view_queue_draw_path (tree_view, path, NULL);
10411     }
10412   else
10413     tree_view->priv->drag_dest_row = NULL;
10414
10415   if (current_dest)
10416     {
10417       int node, new_node;
10418
10419       _pspp_sheet_view_find_node (tree_view, current_dest, &node);
10420       _pspp_sheet_view_queue_draw_node (tree_view, node, NULL);
10421
10422       if (node >= 0)
10423         {
10424           new_node = pspp_sheet_view_node_next (tree_view, node);
10425           if (new_node >= 0)
10426             _pspp_sheet_view_queue_draw_node (tree_view, new_node, NULL);
10427
10428           new_node = pspp_sheet_view_node_prev (tree_view, node);
10429           if (new_node >= 0)
10430             _pspp_sheet_view_queue_draw_node (tree_view, new_node, NULL);
10431         }
10432       gtk_tree_path_free (current_dest);
10433     }
10434 }
10435
10436 /**
10437  * pspp_sheet_view_get_drag_dest_row:
10438  * @tree_view: a #PsppSheetView
10439  * @path: (allow-none): Return location for the path of the highlighted row, or %NULL.
10440  * @pos: (allow-none): Return location for the drop position, or %NULL
10441  * 
10442  * Gets information about the row that is highlighted for feedback.
10443  **/
10444 void
10445 pspp_sheet_view_get_drag_dest_row (PsppSheetView              *tree_view,
10446                                  GtkTreePath             **path,
10447                                  PsppSheetViewDropPosition  *pos)
10448 {
10449   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10450
10451   if (path)
10452     {
10453       if (tree_view->priv->drag_dest_row)
10454         *path = gtk_tree_row_reference_get_path (tree_view->priv->drag_dest_row);
10455       else
10456         {
10457           if (tree_view->priv->empty_view_drop)
10458             *path = gtk_tree_path_new_from_indices (0, -1);
10459           else
10460             *path = NULL;
10461         }
10462     }
10463
10464   if (pos)
10465     *pos = tree_view->priv->drag_dest_pos;
10466 }
10467
10468 /**
10469  * pspp_sheet_view_get_dest_row_at_pos:
10470  * @tree_view: a #PsppSheetView
10471  * @drag_x: the position to determine the destination row for
10472  * @drag_y: the position to determine the destination row for
10473  * @path: (allow-none): Return location for the path of the highlighted row, or %NULL.
10474  * @pos: (allow-none): Return location for the drop position, or %NULL
10475  * 
10476  * Determines the destination row for a given position.  @drag_x and
10477  * @drag_y are expected to be in widget coordinates.  This function is only
10478  * meaningful if @tree_view is realized.  Therefore this function will always
10479  * return %FALSE if @tree_view is not realized or does not have a model.
10480  * 
10481  * Return value: whether there is a row at the given position, %TRUE if this
10482  * is indeed the case.
10483  **/
10484 gboolean
10485 pspp_sheet_view_get_dest_row_at_pos (PsppSheetView             *tree_view,
10486                                    gint                     drag_x,
10487                                    gint                     drag_y,
10488                                    GtkTreePath            **path,
10489                                    PsppSheetViewDropPosition *pos)
10490 {
10491   gint cell_y;
10492   gint bin_x, bin_y;
10493   gdouble offset_into_row;
10494   gdouble third;
10495   GdkRectangle cell;
10496   PsppSheetViewColumn *column = NULL;
10497   GtkTreePath *tmp_path = NULL;
10498
10499   /* Note; this function is exported to allow a custom DND
10500    * implementation, so it can't touch TreeViewDragInfo
10501    */
10502
10503   g_return_val_if_fail (tree_view != NULL, FALSE);
10504   g_return_val_if_fail (drag_x >= 0, FALSE);
10505   g_return_val_if_fail (drag_y >= 0, FALSE);
10506
10507   if (path)
10508     *path = NULL;
10509
10510   if (tree_view->priv->bin_window == NULL)
10511     return FALSE;
10512
10513   if (tree_view->priv->row_count == 0)
10514     return FALSE;
10515
10516   /* If in the top third of a row, we drop before that row; if
10517    * in the bottom third, drop after that row; if in the middle,
10518    * and the row has children, drop into the row.
10519    */
10520   pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view, drag_x, drag_y,
10521                                                      &bin_x, &bin_y);
10522
10523   if (!pspp_sheet_view_get_path_at_pos (tree_view,
10524                                       bin_x,
10525                                       bin_y,
10526                                       &tmp_path,
10527                                       &column,
10528                                       NULL,
10529                                       &cell_y))
10530     return FALSE;
10531
10532   pspp_sheet_view_get_background_area (tree_view, tmp_path, column,
10533                                      &cell);
10534
10535   offset_into_row = cell_y;
10536
10537   if (path)
10538     *path = tmp_path;
10539   else
10540     gtk_tree_path_free (tmp_path);
10541
10542   tmp_path = NULL;
10543
10544   third = cell.height / 3.0;
10545
10546   if (pos)
10547     {
10548       if (offset_into_row < third)
10549         {
10550           *pos = PSPP_SHEET_VIEW_DROP_BEFORE;
10551         }
10552       else if (offset_into_row < (cell.height / 2.0))
10553         {
10554           *pos = PSPP_SHEET_VIEW_DROP_INTO_OR_BEFORE;
10555         }
10556       else if (offset_into_row < third * 2.0)
10557         {
10558           *pos = PSPP_SHEET_VIEW_DROP_INTO_OR_AFTER;
10559         }
10560       else
10561         {
10562           *pos = PSPP_SHEET_VIEW_DROP_AFTER;
10563         }
10564     }
10565
10566   return TRUE;
10567 }
10568
10569
10570
10571 /* KEEP IN SYNC WITH PSPP_SHEET_VIEW_BIN_EXPOSE */
10572 /**
10573  * pspp_sheet_view_create_row_drag_icon:
10574  * @tree_view: a #PsppSheetView
10575  * @path: a #GtkTreePath in @tree_view
10576  *
10577  * Creates a #GdkPixmap representation of the row at @path.  
10578  * This image is used for a drag icon.
10579  *
10580  * Return value: a newly-allocated pixmap of the drag icon.
10581  **/
10582 GdkPixmap *
10583 pspp_sheet_view_create_row_drag_icon (PsppSheetView  *tree_view,
10584                                     GtkTreePath  *path)
10585 {
10586   GtkTreeIter   iter;
10587   int node;
10588   gint cell_offset;
10589   GList *list;
10590   GdkRectangle background_area;
10591   GdkRectangle expose_area;
10592   GtkWidget *widget;
10593   gint depth;
10594   /* start drawing inside the black outline */
10595   gint x = 1, y = 1;
10596   GdkDrawable *drawable;
10597   gint bin_window_width;
10598   gboolean rtl;
10599
10600   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
10601   g_return_val_if_fail (path != NULL, NULL);
10602
10603   widget = GTK_WIDGET (tree_view);
10604
10605   if (!gtk_widget_get_realized (widget))
10606     return NULL;
10607
10608   depth = gtk_tree_path_get_depth (path);
10609
10610   _pspp_sheet_view_find_node (tree_view,
10611                             path,
10612                             &node);
10613
10614   if (node < 0)
10615     return NULL;
10616
10617   if (!gtk_tree_model_get_iter (tree_view->priv->model,
10618                                 &iter,
10619                                 path))
10620     return NULL;
10621   
10622   cell_offset = x;
10623
10624   background_area.y = y;
10625   background_area.height = ROW_HEIGHT (tree_view);
10626
10627   gdk_drawable_get_size (tree_view->priv->bin_window,
10628                          &bin_window_width, NULL);
10629
10630   drawable = gdk_pixmap_new (tree_view->priv->bin_window,
10631                              bin_window_width + 2,
10632                              background_area.height + 2,
10633                              -1);
10634
10635   expose_area.x = 0;
10636   expose_area.y = 0;
10637   expose_area.width = bin_window_width + 2;
10638   expose_area.height = background_area.height + 2;
10639
10640   gdk_draw_rectangle (drawable,
10641                       widget->style->base_gc [gtk_widget_get_state (widget)],
10642                       TRUE,
10643                       0, 0,
10644                       bin_window_width + 2,
10645                       background_area.height + 2);
10646
10647   rtl = gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL;
10648
10649   for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns));
10650       list;
10651       list = (rtl ? list->prev : list->next))
10652     {
10653       PsppSheetViewColumn *column = list->data;
10654       GdkRectangle cell_area;
10655       gint vertical_separator;
10656
10657       if (!column->visible)
10658         continue;
10659
10660       pspp_sheet_view_column_cell_set_cell_data (column, tree_view->priv->model, &iter);
10661
10662       background_area.x = cell_offset;
10663       background_area.width = column->width;
10664
10665       gtk_widget_style_get (widget,
10666                             "vertical-separator", &vertical_separator,
10667                             NULL);
10668
10669       cell_area = background_area;
10670
10671       cell_area.y += vertical_separator / 2;
10672       cell_area.height -= vertical_separator;
10673
10674       if (pspp_sheet_view_column_cell_is_visible (column))
10675         _pspp_sheet_view_column_cell_render (column,
10676                                              drawable,
10677                                              &background_area,
10678                                              &cell_area,
10679                                              &expose_area,
10680                                              0);
10681       cell_offset += column->width;
10682     }
10683
10684   gdk_draw_rectangle (drawable,
10685                       widget->style->black_gc,
10686                       FALSE,
10687                       0, 0,
10688                       bin_window_width + 1,
10689                       background_area.height + 1);
10690
10691   return drawable;
10692 }
10693
10694
10695 /**
10696  * pspp_sheet_view_set_destroy_count_func:
10697  * @tree_view: A #PsppSheetView
10698  * @func: (allow-none): Function to be called when a view row is destroyed, or %NULL
10699  * @data: (allow-none): User data to be passed to @func, or %NULL
10700  * @destroy: (allow-none): Destroy notifier for @data, or %NULL
10701  *
10702  * This function should almost never be used.  It is meant for private use by
10703  * ATK for determining the number of visible children that are removed when a row is deleted.
10704  **/
10705 void
10706 pspp_sheet_view_set_destroy_count_func (PsppSheetView             *tree_view,
10707                                       PsppSheetDestroyCountFunc  func,
10708                                       gpointer                 data,
10709                                       GDestroyNotify           destroy)
10710 {
10711   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10712
10713   if (tree_view->priv->destroy_count_destroy)
10714     tree_view->priv->destroy_count_destroy (tree_view->priv->destroy_count_data);
10715
10716   tree_view->priv->destroy_count_func = func;
10717   tree_view->priv->destroy_count_data = data;
10718   tree_view->priv->destroy_count_destroy = destroy;
10719 }
10720
10721
10722 /*
10723  * Interactive search
10724  */
10725
10726 /**
10727  * pspp_sheet_view_set_enable_search:
10728  * @tree_view: A #PsppSheetView
10729  * @enable_search: %TRUE, if the user can search interactively
10730  *
10731  * If @enable_search is set, then the user can type in text to search through
10732  * the tree interactively (this is sometimes called "typeahead find").
10733  * 
10734  * Note that even if this is %FALSE, the user can still initiate a search 
10735  * using the "start-interactive-search" key binding.
10736  */
10737 void
10738 pspp_sheet_view_set_enable_search (PsppSheetView *tree_view,
10739                                  gboolean     enable_search)
10740 {
10741   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10742
10743   enable_search = !!enable_search;
10744   
10745   if (tree_view->priv->enable_search != enable_search)
10746     {
10747        tree_view->priv->enable_search = enable_search;
10748        g_object_notify (G_OBJECT (tree_view), "enable-search");
10749     }
10750 }
10751
10752 /**
10753  * pspp_sheet_view_get_enable_search:
10754  * @tree_view: A #PsppSheetView
10755  *
10756  * Returns whether or not the tree allows to start interactive searching 
10757  * by typing in text.
10758  *
10759  * Return value: whether or not to let the user search interactively
10760  */
10761 gboolean
10762 pspp_sheet_view_get_enable_search (PsppSheetView *tree_view)
10763 {
10764   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
10765
10766   return tree_view->priv->enable_search;
10767 }
10768
10769
10770 /**
10771  * pspp_sheet_view_get_search_column:
10772  * @tree_view: A #PsppSheetView
10773  *
10774  * Gets the column searched on by the interactive search code.
10775  *
10776  * Return value: the column the interactive search code searches in.
10777  */
10778 gint
10779 pspp_sheet_view_get_search_column (PsppSheetView *tree_view)
10780 {
10781   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), -1);
10782
10783   return (tree_view->priv->search_column);
10784 }
10785
10786 /**
10787  * pspp_sheet_view_set_search_column:
10788  * @tree_view: A #PsppSheetView
10789  * @column: the column of the model to search in, or -1 to disable searching
10790  *
10791  * Sets @column as the column where the interactive search code should
10792  * search in for the current model. 
10793  * 
10794  * If the search column is set, users can use the "start-interactive-search"
10795  * key binding to bring up search popup. The enable-search property controls
10796  * whether simply typing text will also start an interactive search.
10797  *
10798  * Note that @column refers to a column of the current model. The search 
10799  * column is reset to -1 when the model is changed.
10800  */
10801 void
10802 pspp_sheet_view_set_search_column (PsppSheetView *tree_view,
10803                                  gint         column)
10804 {
10805   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10806   g_return_if_fail (column >= -1);
10807
10808   if (tree_view->priv->search_column == column)
10809     return;
10810
10811   tree_view->priv->search_column = column;
10812   g_object_notify (G_OBJECT (tree_view), "search-column");
10813 }
10814
10815 /**
10816  * pspp_sheet_view_get_search_equal_func:
10817  * @tree_view: A #PsppSheetView
10818  *
10819  * Returns the compare function currently in use.
10820  *
10821  * Return value: the currently used compare function for the search code.
10822  */
10823
10824 PsppSheetViewSearchEqualFunc
10825 pspp_sheet_view_get_search_equal_func (PsppSheetView *tree_view)
10826 {
10827   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
10828
10829   return tree_view->priv->search_equal_func;
10830 }
10831
10832 /**
10833  * pspp_sheet_view_set_search_equal_func:
10834  * @tree_view: A #PsppSheetView
10835  * @search_equal_func: the compare function to use during the search
10836  * @search_user_data: (allow-none): user data to pass to @search_equal_func, or %NULL
10837  * @search_destroy: (allow-none): Destroy notifier for @search_user_data, or %NULL
10838  *
10839  * Sets the compare function for the interactive search capabilities; note
10840  * that somewhat like strcmp() returning 0 for equality
10841  * #PsppSheetViewSearchEqualFunc returns %FALSE on matches.
10842  **/
10843 void
10844 pspp_sheet_view_set_search_equal_func (PsppSheetView                *tree_view,
10845                                      PsppSheetViewSearchEqualFunc  search_equal_func,
10846                                      gpointer                    search_user_data,
10847                                      GDestroyNotify              search_destroy)
10848 {
10849   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10850   g_return_if_fail (search_equal_func != NULL);
10851
10852   if (tree_view->priv->search_destroy)
10853     tree_view->priv->search_destroy (tree_view->priv->search_user_data);
10854
10855   tree_view->priv->search_equal_func = search_equal_func;
10856   tree_view->priv->search_user_data = search_user_data;
10857   tree_view->priv->search_destroy = search_destroy;
10858   if (tree_view->priv->search_equal_func == NULL)
10859     tree_view->priv->search_equal_func = pspp_sheet_view_search_equal_func;
10860 }
10861
10862 /**
10863  * pspp_sheet_view_get_search_entry:
10864  * @tree_view: A #PsppSheetView
10865  *
10866  * Returns the #GtkEntry which is currently in use as interactive search
10867  * entry for @tree_view.  In case the built-in entry is being used, %NULL
10868  * will be returned.
10869  *
10870  * Return value: the entry currently in use as search entry.
10871  *
10872  * Since: 2.10
10873  */
10874 GtkEntry *
10875 pspp_sheet_view_get_search_entry (PsppSheetView *tree_view)
10876 {
10877   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
10878
10879   if (tree_view->priv->search_custom_entry_set)
10880     return GTK_ENTRY (tree_view->priv->search_entry);
10881
10882   return NULL;
10883 }
10884
10885 /**
10886  * pspp_sheet_view_set_search_entry:
10887  * @tree_view: A #PsppSheetView
10888  * @entry: (allow-none): the entry the interactive search code of @tree_view should use or %NULL
10889  *
10890  * Sets the entry which the interactive search code will use for this
10891  * @tree_view.  This is useful when you want to provide a search entry
10892  * in our interface at all time at a fixed position.  Passing %NULL for
10893  * @entry will make the interactive search code use the built-in popup
10894  * entry again.
10895  *
10896  * Since: 2.10
10897  */
10898 void
10899 pspp_sheet_view_set_search_entry (PsppSheetView *tree_view,
10900                                 GtkEntry    *entry)
10901 {
10902   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10903   g_return_if_fail (entry == NULL || GTK_IS_ENTRY (entry));
10904
10905   if (tree_view->priv->search_custom_entry_set)
10906     {
10907       if (tree_view->priv->search_entry_changed_id)
10908         {
10909           g_signal_handler_disconnect (tree_view->priv->search_entry,
10910                                        tree_view->priv->search_entry_changed_id);
10911           tree_view->priv->search_entry_changed_id = 0;
10912         }
10913       g_signal_handlers_disconnect_by_func (tree_view->priv->search_entry,
10914                                             G_CALLBACK (pspp_sheet_view_search_key_press_event),
10915                                             tree_view);
10916
10917       g_object_unref (tree_view->priv->search_entry);
10918     }
10919   else if (tree_view->priv->search_window)
10920     {
10921       gtk_widget_destroy (tree_view->priv->search_window);
10922
10923       tree_view->priv->search_window = NULL;
10924     }
10925
10926   if (entry)
10927     {
10928       tree_view->priv->search_entry = g_object_ref (entry);
10929       tree_view->priv->search_custom_entry_set = TRUE;
10930
10931       if (tree_view->priv->search_entry_changed_id == 0)
10932         {
10933           tree_view->priv->search_entry_changed_id =
10934             g_signal_connect (tree_view->priv->search_entry, "changed",
10935                               G_CALLBACK (pspp_sheet_view_search_init),
10936                               tree_view);
10937         }
10938       
10939         g_signal_connect (tree_view->priv->search_entry, "key-press-event",
10940                           G_CALLBACK (pspp_sheet_view_search_key_press_event),
10941                           tree_view);
10942
10943         pspp_sheet_view_search_init (tree_view->priv->search_entry, tree_view);
10944     }
10945   else
10946     {
10947       tree_view->priv->search_entry = NULL;
10948       tree_view->priv->search_custom_entry_set = FALSE;
10949     }
10950 }
10951
10952 /**
10953  * pspp_sheet_view_set_search_position_func:
10954  * @tree_view: A #PsppSheetView
10955  * @func: (allow-none): the function to use to position the search dialog, or %NULL
10956  *    to use the default search position function
10957  * @data: (allow-none): user data to pass to @func, or %NULL
10958  * @destroy: (allow-none): Destroy notifier for @data, or %NULL
10959  *
10960  * Sets the function to use when positioning the search dialog.
10961  *
10962  * Since: 2.10
10963  **/
10964 void
10965 pspp_sheet_view_set_search_position_func (PsppSheetView                   *tree_view,
10966                                         PsppSheetViewSearchPositionFunc  func,
10967                                         gpointer                       user_data,
10968                                         GDestroyNotify                 destroy)
10969 {
10970   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10971
10972   if (tree_view->priv->search_position_destroy)
10973     tree_view->priv->search_position_destroy (tree_view->priv->search_position_user_data);
10974
10975   tree_view->priv->search_position_func = func;
10976   tree_view->priv->search_position_user_data = user_data;
10977   tree_view->priv->search_position_destroy = destroy;
10978   if (tree_view->priv->search_position_func == NULL)
10979     tree_view->priv->search_position_func = pspp_sheet_view_search_position_func;
10980 }
10981
10982 /**
10983  * pspp_sheet_view_get_search_position_func:
10984  * @tree_view: A #PsppSheetView
10985  *
10986  * Returns the positioning function currently in use.
10987  *
10988  * Return value: the currently used function for positioning the search dialog.
10989  *
10990  * Since: 2.10
10991  */
10992 PsppSheetViewSearchPositionFunc
10993 pspp_sheet_view_get_search_position_func (PsppSheetView *tree_view)
10994 {
10995   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
10996
10997   return tree_view->priv->search_position_func;
10998 }
10999
11000
11001 static void
11002 pspp_sheet_view_search_dialog_hide (GtkWidget   *search_dialog,
11003                                   PsppSheetView *tree_view)
11004 {
11005   if (tree_view->priv->disable_popdown)
11006     return;
11007
11008   if (tree_view->priv->search_entry_changed_id)
11009     {
11010       g_signal_handler_disconnect (tree_view->priv->search_entry,
11011                                    tree_view->priv->search_entry_changed_id);
11012       tree_view->priv->search_entry_changed_id = 0;
11013     }
11014   if (tree_view->priv->typeselect_flush_timeout)
11015     {
11016       g_source_remove (tree_view->priv->typeselect_flush_timeout);
11017       tree_view->priv->typeselect_flush_timeout = 0;
11018     }
11019         
11020   if (gtk_widget_get_visible (search_dialog))
11021     {
11022       /* send focus-in event */
11023       send_focus_change (GTK_WIDGET (tree_view->priv->search_entry), FALSE);
11024       gtk_widget_hide (search_dialog);
11025       gtk_entry_set_text (GTK_ENTRY (tree_view->priv->search_entry), "");
11026       send_focus_change (GTK_WIDGET (tree_view), TRUE);
11027     }
11028 }
11029
11030 static void
11031 pspp_sheet_view_search_position_func (PsppSheetView *tree_view,
11032                                     GtkWidget   *search_dialog,
11033                                     gpointer     user_data)
11034 {
11035   gint x, y;
11036   gint tree_x, tree_y;
11037   gint tree_width, tree_height;
11038   GdkWindow *tree_window = GTK_WIDGET (tree_view)->window;
11039   GdkScreen *screen = gdk_drawable_get_screen (tree_window);
11040   GtkRequisition requisition;
11041   gint monitor_num;
11042   GdkRectangle monitor;
11043
11044   monitor_num = gdk_screen_get_monitor_at_window (screen, tree_window);
11045   gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
11046
11047   gtk_widget_realize (search_dialog);
11048
11049   gdk_window_get_origin (tree_window, &tree_x, &tree_y);
11050   gdk_drawable_get_size (tree_window,
11051                          &tree_width,
11052                          &tree_height);
11053   gtk_widget_size_request (search_dialog, &requisition);
11054
11055   if (tree_x + tree_width > gdk_screen_get_width (screen))
11056     x = gdk_screen_get_width (screen) - requisition.width;
11057   else if (tree_x + tree_width - requisition.width < 0)
11058     x = 0;
11059   else
11060     x = tree_x + tree_width - requisition.width;
11061
11062   if (tree_y + tree_height + requisition.height > gdk_screen_get_height (screen))
11063     y = gdk_screen_get_height (screen) - requisition.height;
11064   else if (tree_y + tree_height < 0) /* isn't really possible ... */
11065     y = 0;
11066   else
11067     y = tree_y + tree_height;
11068
11069   gtk_window_move (GTK_WINDOW (search_dialog), x, y);
11070 }
11071
11072 static void
11073 pspp_sheet_view_search_disable_popdown (GtkEntry *entry,
11074                                       GtkMenu  *menu,
11075                                       gpointer  data)
11076 {
11077   PsppSheetView *tree_view = (PsppSheetView *)data;
11078
11079   tree_view->priv->disable_popdown = 1;
11080   g_signal_connect (menu, "hide",
11081                     G_CALLBACK (pspp_sheet_view_search_enable_popdown), data);
11082 }
11083
11084 /* Because we're visible but offscreen, we just set a flag in the preedit
11085  * callback.
11086  */
11087 static void
11088 pspp_sheet_view_search_preedit_changed (GtkIMContext *im_context,
11089                                       PsppSheetView  *tree_view)
11090 {
11091   tree_view->priv->imcontext_changed = 1;
11092   if (tree_view->priv->typeselect_flush_timeout)
11093     {
11094       g_source_remove (tree_view->priv->typeselect_flush_timeout);
11095       tree_view->priv->typeselect_flush_timeout =
11096         gdk_threads_add_timeout (PSPP_SHEET_VIEW_SEARCH_DIALOG_TIMEOUT,
11097                        (GSourceFunc) pspp_sheet_view_search_entry_flush_timeout,
11098                        tree_view);
11099     }
11100
11101 }
11102
11103 static void
11104 pspp_sheet_view_search_activate (GtkEntry    *entry,
11105                                PsppSheetView *tree_view)
11106 {
11107   GtkTreePath *path;
11108   int node;
11109
11110   pspp_sheet_view_search_dialog_hide (tree_view->priv->search_window,
11111                                     tree_view);
11112
11113   /* If we have a row selected and it's the cursor row, we activate
11114    * the row XXX */
11115   if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
11116     {
11117       path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
11118       
11119       _pspp_sheet_view_find_node (tree_view, path, &node);
11120       
11121       if (node >= 0 && pspp_sheet_view_node_is_selected (tree_view, node))
11122         pspp_sheet_view_row_activated (tree_view, path, tree_view->priv->focus_column);
11123       
11124       gtk_tree_path_free (path);
11125     }
11126 }
11127
11128 static gboolean
11129 pspp_sheet_view_real_search_enable_popdown (gpointer data)
11130 {
11131   PsppSheetView *tree_view = (PsppSheetView *)data;
11132
11133   tree_view->priv->disable_popdown = 0;
11134
11135   return FALSE;
11136 }
11137
11138 static void
11139 pspp_sheet_view_search_enable_popdown (GtkWidget *widget,
11140                                      gpointer   data)
11141 {
11142   gdk_threads_add_timeout_full (G_PRIORITY_HIGH, 200, pspp_sheet_view_real_search_enable_popdown, g_object_ref (data), g_object_unref);
11143 }
11144
11145 static gboolean
11146 pspp_sheet_view_search_delete_event (GtkWidget *widget,
11147                                    GdkEventAny *event,
11148                                    PsppSheetView *tree_view)
11149 {
11150   g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
11151
11152   pspp_sheet_view_search_dialog_hide (widget, tree_view);
11153
11154   return TRUE;
11155 }
11156
11157 static gboolean
11158 pspp_sheet_view_search_button_press_event (GtkWidget *widget,
11159                                          GdkEventButton *event,
11160                                          PsppSheetView *tree_view)
11161 {
11162   g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
11163
11164   pspp_sheet_view_search_dialog_hide (widget, tree_view);
11165
11166   if (event->window == tree_view->priv->bin_window)
11167     pspp_sheet_view_button_press (GTK_WIDGET (tree_view), event);
11168
11169   return TRUE;
11170 }
11171
11172 static gboolean
11173 pspp_sheet_view_search_scroll_event (GtkWidget *widget,
11174                                    GdkEventScroll *event,
11175                                    PsppSheetView *tree_view)
11176 {
11177   gboolean retval = FALSE;
11178
11179   if (event->direction == GDK_SCROLL_UP)
11180     {
11181       pspp_sheet_view_search_move (widget, tree_view, TRUE);
11182       retval = TRUE;
11183     }
11184   else if (event->direction == GDK_SCROLL_DOWN)
11185     {
11186       pspp_sheet_view_search_move (widget, tree_view, FALSE);
11187       retval = TRUE;
11188     }
11189
11190   /* renew the flush timeout */
11191   if (retval && tree_view->priv->typeselect_flush_timeout
11192       && !tree_view->priv->search_custom_entry_set)
11193     {
11194       g_source_remove (tree_view->priv->typeselect_flush_timeout);
11195       tree_view->priv->typeselect_flush_timeout =
11196         gdk_threads_add_timeout (PSPP_SHEET_VIEW_SEARCH_DIALOG_TIMEOUT,
11197                        (GSourceFunc) pspp_sheet_view_search_entry_flush_timeout,
11198                        tree_view);
11199     }
11200
11201   return retval;
11202 }
11203
11204 static gboolean
11205 pspp_sheet_view_search_key_press_event (GtkWidget *widget,
11206                                       GdkEventKey *event,
11207                                       PsppSheetView *tree_view)
11208 {
11209   gboolean retval = FALSE;
11210
11211   g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
11212   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
11213
11214   /* close window and cancel the search */
11215   if (!tree_view->priv->search_custom_entry_set
11216       && (event->keyval == GDK_Escape ||
11217           event->keyval == GDK_Tab ||
11218             event->keyval == GDK_KP_Tab ||
11219             event->keyval == GDK_ISO_Left_Tab))
11220     {
11221       pspp_sheet_view_search_dialog_hide (widget, tree_view);
11222       return TRUE;
11223     }
11224
11225   /* select previous matching iter */
11226   if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
11227     {
11228       if (!pspp_sheet_view_search_move (widget, tree_view, TRUE))
11229         gtk_widget_error_bell (widget);
11230
11231       retval = TRUE;
11232     }
11233
11234   if (((event->state & (GTK_DEFAULT_ACCEL_MOD_MASK | GDK_SHIFT_MASK)) == (GTK_DEFAULT_ACCEL_MOD_MASK | GDK_SHIFT_MASK))
11235       && (event->keyval == GDK_g || event->keyval == GDK_G))
11236     {
11237       if (!pspp_sheet_view_search_move (widget, tree_view, TRUE))
11238         gtk_widget_error_bell (widget);
11239
11240       retval = TRUE;
11241     }
11242
11243   /* select next matching iter */
11244   if (event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
11245     {
11246       if (!pspp_sheet_view_search_move (widget, tree_view, FALSE))
11247         gtk_widget_error_bell (widget);
11248
11249       retval = TRUE;
11250     }
11251
11252   if (((event->state & (GTK_DEFAULT_ACCEL_MOD_MASK | GDK_SHIFT_MASK)) == GTK_DEFAULT_ACCEL_MOD_MASK)
11253       && (event->keyval == GDK_g || event->keyval == GDK_G))
11254     {
11255       if (!pspp_sheet_view_search_move (widget, tree_view, FALSE))
11256         gtk_widget_error_bell (widget);
11257
11258       retval = TRUE;
11259     }
11260
11261   /* renew the flush timeout */
11262   if (retval && tree_view->priv->typeselect_flush_timeout
11263       && !tree_view->priv->search_custom_entry_set)
11264     {
11265       g_source_remove (tree_view->priv->typeselect_flush_timeout);
11266       tree_view->priv->typeselect_flush_timeout =
11267         gdk_threads_add_timeout (PSPP_SHEET_VIEW_SEARCH_DIALOG_TIMEOUT,
11268                        (GSourceFunc) pspp_sheet_view_search_entry_flush_timeout,
11269                        tree_view);
11270     }
11271
11272   return retval;
11273 }
11274
11275 /*  this function returns FALSE if there is a search string but
11276  *  nothing was found, and TRUE otherwise.
11277  */
11278 static gboolean
11279 pspp_sheet_view_search_move (GtkWidget   *window,
11280                            PsppSheetView *tree_view,
11281                            gboolean     up)
11282 {
11283   gboolean ret;
11284   gint len;
11285   gint count = 0;
11286   const gchar *text;
11287   GtkTreeIter iter;
11288   GtkTreeModel *model;
11289   PsppSheetSelection *selection;
11290
11291   text = gtk_entry_get_text (GTK_ENTRY (tree_view->priv->search_entry));
11292
11293   g_return_val_if_fail (text != NULL, FALSE);
11294
11295   len = strlen (text);
11296
11297   if (up && tree_view->priv->selected_iter == 1)
11298     return strlen (text) < 1;
11299
11300   len = strlen (text);
11301
11302   if (len < 1)
11303     return TRUE;
11304
11305   model = pspp_sheet_view_get_model (tree_view);
11306   selection = pspp_sheet_view_get_selection (tree_view);
11307
11308   /* search */
11309   pspp_sheet_selection_unselect_all (selection);
11310   if (!gtk_tree_model_get_iter_first (model, &iter))
11311     return TRUE;
11312
11313   ret = pspp_sheet_view_search_iter (model, selection, &iter, text,
11314                                    &count, up?((tree_view->priv->selected_iter) - 1):((tree_view->priv->selected_iter + 1)));
11315
11316   if (ret)
11317     {
11318       /* found */
11319       tree_view->priv->selected_iter += up?(-1):(1);
11320       return TRUE;
11321     }
11322   else
11323     {
11324       /* return to old iter */
11325       count = 0;
11326       gtk_tree_model_get_iter_first (model, &iter);
11327       pspp_sheet_view_search_iter (model, selection,
11328                                  &iter, text,
11329                                  &count, tree_view->priv->selected_iter);
11330       return FALSE;
11331     }
11332 }
11333
11334 static gboolean
11335 pspp_sheet_view_search_equal_func (GtkTreeModel *model,
11336                                  gint          column,
11337                                  const gchar  *key,
11338                                  GtkTreeIter  *iter,
11339                                  gpointer      search_data)
11340 {
11341   gboolean retval = TRUE;
11342   const gchar *str;
11343   gchar *normalized_string;
11344   gchar *normalized_key;
11345   gchar *case_normalized_string = NULL;
11346   gchar *case_normalized_key = NULL;
11347   GValue value = {0,};
11348   GValue transformed = {0,};
11349
11350   gtk_tree_model_get_value (model, iter, column, &value);
11351
11352   g_value_init (&transformed, G_TYPE_STRING);
11353
11354   if (!g_value_transform (&value, &transformed))
11355     {
11356       g_value_unset (&value);
11357       return TRUE;
11358     }
11359
11360   g_value_unset (&value);
11361
11362   str = g_value_get_string (&transformed);
11363   if (!str)
11364     {
11365       g_value_unset (&transformed);
11366       return TRUE;
11367     }
11368
11369   normalized_string = g_utf8_normalize (str, -1, G_NORMALIZE_ALL);
11370   normalized_key = g_utf8_normalize (key, -1, G_NORMALIZE_ALL);
11371
11372   if (normalized_string && normalized_key)
11373     {
11374       case_normalized_string = g_utf8_casefold (normalized_string, -1);
11375       case_normalized_key = g_utf8_casefold (normalized_key, -1);
11376
11377       if (strncmp (case_normalized_key, case_normalized_string, strlen (case_normalized_key)) == 0)
11378         retval = FALSE;
11379     }
11380
11381   g_value_unset (&transformed);
11382   g_free (normalized_key);
11383   g_free (normalized_string);
11384   g_free (case_normalized_key);
11385   g_free (case_normalized_string);
11386
11387   return retval;
11388 }
11389
11390 static gboolean
11391 pspp_sheet_view_search_iter (GtkTreeModel     *model,
11392                              PsppSheetSelection *selection,
11393                              GtkTreeIter      *iter,
11394                              const gchar      *text,
11395                              gint             *count,
11396                              gint              n)
11397 {
11398   int node = -1;
11399   GtkTreePath *path;
11400
11401   PsppSheetView *tree_view = pspp_sheet_selection_get_tree_view (selection);
11402
11403   path = gtk_tree_model_get_path (model, iter);
11404   _pspp_sheet_view_find_node (tree_view, path, &node);
11405
11406   do
11407     {
11408       gboolean done = FALSE;
11409
11410       if (! tree_view->priv->search_equal_func (model, tree_view->priv->search_column, text, iter, tree_view->priv->search_user_data))
11411         {
11412           (*count)++;
11413           if (*count == n)
11414             {
11415               pspp_sheet_view_scroll_to_cell (tree_view, path, NULL,
11416                                               TRUE, 0.5, 0.0);
11417               pspp_sheet_selection_select_iter (selection, iter);
11418               pspp_sheet_view_real_set_cursor (tree_view, path, FALSE, TRUE);
11419
11420               if (path)
11421                 gtk_tree_path_free (path);
11422
11423               return TRUE;
11424             }
11425         }
11426
11427
11428       do
11429         {
11430           node = pspp_sheet_view_node_next (tree_view, node);
11431
11432           if (node >= 0)
11433             {
11434               gboolean has_next;
11435
11436               has_next = gtk_tree_model_iter_next (model, iter);
11437
11438               done = TRUE;
11439               gtk_tree_path_next (path);
11440
11441               /* sanity check */
11442               TREE_VIEW_INTERNAL_ASSERT (has_next, FALSE);
11443             }
11444           else
11445             {
11446               if (path)
11447                 gtk_tree_path_free (path);
11448
11449               /* we've run out of tree, done with this func */
11450               return FALSE;
11451             }
11452         }
11453       while (!done);
11454     }
11455   while (1);
11456
11457   return FALSE;
11458 }
11459
11460 static void
11461 pspp_sheet_view_search_init (GtkWidget   *entry,
11462                            PsppSheetView *tree_view)
11463 {
11464   gint ret;
11465   gint count = 0;
11466   const gchar *text;
11467   GtkTreeIter iter;
11468   GtkTreeModel *model;
11469   PsppSheetSelection *selection;
11470
11471   g_return_if_fail (GTK_IS_ENTRY (entry));
11472   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11473
11474   text = gtk_entry_get_text (GTK_ENTRY (entry));
11475
11476   model = pspp_sheet_view_get_model (tree_view);
11477   selection = pspp_sheet_view_get_selection (tree_view);
11478
11479   /* search */
11480   pspp_sheet_selection_unselect_all (selection);
11481   if (tree_view->priv->typeselect_flush_timeout
11482       && !tree_view->priv->search_custom_entry_set)
11483     {
11484       g_source_remove (tree_view->priv->typeselect_flush_timeout);
11485       tree_view->priv->typeselect_flush_timeout =
11486         gdk_threads_add_timeout (PSPP_SHEET_VIEW_SEARCH_DIALOG_TIMEOUT,
11487                        (GSourceFunc) pspp_sheet_view_search_entry_flush_timeout,
11488                        tree_view);
11489     }
11490
11491   if (*text == '\0')
11492     return;
11493
11494   if (!gtk_tree_model_get_iter_first (model, &iter))
11495     return;
11496
11497   ret = pspp_sheet_view_search_iter (model, selection,
11498                                    &iter, text,
11499                                    &count, 1);
11500
11501   if (ret)
11502     tree_view->priv->selected_iter = 1;
11503 }
11504
11505 static void
11506 pspp_sheet_view_remove_widget (GtkCellEditable *cell_editable,
11507                              PsppSheetView     *tree_view)
11508 {
11509   if (tree_view->priv->edited_column == NULL)
11510     return;
11511
11512   _pspp_sheet_view_column_stop_editing (tree_view->priv->edited_column);
11513   tree_view->priv->edited_column = NULL;
11514
11515   if (gtk_widget_has_focus (GTK_WIDGET (cell_editable)))
11516     gtk_widget_grab_focus (GTK_WIDGET (tree_view));
11517
11518   g_signal_handlers_disconnect_by_func (cell_editable,
11519                                         pspp_sheet_view_remove_widget,
11520                                         tree_view);
11521
11522   gtk_container_remove (GTK_CONTAINER (tree_view),
11523                         GTK_WIDGET (cell_editable));  
11524
11525   /* FIXME should only redraw a single node */
11526   gtk_widget_queue_draw (GTK_WIDGET (tree_view));
11527 }
11528
11529 static gboolean
11530 pspp_sheet_view_start_editing (PsppSheetView *tree_view,
11531                              GtkTreePath *cursor_path)
11532 {
11533   GtkTreeIter iter;
11534   GdkRectangle background_area;
11535   GdkRectangle cell_area;
11536   GtkCellEditable *editable_widget = NULL;
11537   gchar *path_string;
11538   guint flags = 0; /* can be 0, as the flags are primarily for rendering */
11539   gint retval = FALSE;
11540   int cursor_node;
11541
11542   g_assert (tree_view->priv->focus_column);
11543
11544   if (!gtk_widget_get_realized (GTK_WIDGET (tree_view)))
11545     return FALSE;
11546
11547   _pspp_sheet_view_find_node (tree_view, cursor_path, &cursor_node);
11548   if (cursor_node < 0)
11549     return FALSE;
11550
11551   path_string = gtk_tree_path_to_string (cursor_path);
11552   gtk_tree_model_get_iter (tree_view->priv->model, &iter, cursor_path);
11553
11554   pspp_sheet_view_column_cell_set_cell_data (tree_view->priv->focus_column,
11555                                            tree_view->priv->model,
11556                                            &iter);
11557   pspp_sheet_view_get_background_area (tree_view,
11558                                      cursor_path,
11559                                      tree_view->priv->focus_column,
11560                                      &background_area);
11561   pspp_sheet_view_get_cell_area (tree_view,
11562                                cursor_path,
11563                                tree_view->priv->focus_column,
11564                                &cell_area);
11565
11566   if (_pspp_sheet_view_column_cell_event (tree_view->priv->focus_column,
11567                                         &editable_widget,
11568                                         NULL,
11569                                         path_string,
11570                                         &background_area,
11571                                         &cell_area,
11572                                         flags))
11573     {
11574       retval = TRUE;
11575       if (editable_widget != NULL)
11576         {
11577           gint left, right;
11578           GdkRectangle area;
11579           GtkCellRenderer *cell;
11580
11581           area = cell_area;
11582           cell = _pspp_sheet_view_column_get_edited_cell (tree_view->priv->focus_column);
11583
11584           _pspp_sheet_view_column_get_neighbor_sizes (tree_view->priv->focus_column, cell, &left, &right);
11585
11586           area.x += left;
11587           area.width -= right + left;
11588
11589           pspp_sheet_view_real_start_editing (tree_view,
11590                                             tree_view->priv->focus_column,
11591                                             cursor_path,
11592                                             editable_widget,
11593                                             &area,
11594                                             NULL,
11595                                             flags);
11596         }
11597
11598     }
11599   g_free (path_string);
11600   return retval;
11601 }
11602
11603 static void
11604 pspp_sheet_view_real_start_editing (PsppSheetView       *tree_view,
11605                                   PsppSheetViewColumn *column,
11606                                   GtkTreePath       *path,
11607                                   GtkCellEditable   *cell_editable,
11608                                   GdkRectangle      *cell_area,
11609                                   GdkEvent          *event,
11610                                   guint              flags)
11611 {
11612   gint pre_val = tree_view->priv->vadjustment->value;
11613   GtkRequisition requisition;
11614
11615   tree_view->priv->edited_column = column;
11616   _pspp_sheet_view_column_start_editing (column, GTK_CELL_EDITABLE (cell_editable));
11617
11618   pspp_sheet_view_real_set_cursor (tree_view, path, FALSE, TRUE);
11619   cell_area->y += pre_val - (int)tree_view->priv->vadjustment->value;
11620
11621   gtk_widget_size_request (GTK_WIDGET (cell_editable), &requisition);
11622
11623   PSPP_SHEET_VIEW_SET_FLAG (tree_view, PSPP_SHEET_VIEW_DRAW_KEYFOCUS);
11624
11625   if (requisition.height < cell_area->height)
11626     {
11627       gint diff = cell_area->height - requisition.height;
11628       pspp_sheet_view_put (tree_view,
11629                          GTK_WIDGET (cell_editable),
11630                          cell_area->x, cell_area->y + diff/2,
11631                          cell_area->width, requisition.height);
11632     }
11633   else
11634     {
11635       pspp_sheet_view_put (tree_view,
11636                          GTK_WIDGET (cell_editable),
11637                          cell_area->x, cell_area->y,
11638                          cell_area->width, cell_area->height);
11639     }
11640
11641   gtk_cell_editable_start_editing (GTK_CELL_EDITABLE (cell_editable),
11642                                    (GdkEvent *)event);
11643
11644   gtk_widget_grab_focus (GTK_WIDGET (cell_editable));
11645   g_signal_connect (cell_editable, "remove-widget",
11646                     G_CALLBACK (pspp_sheet_view_remove_widget), tree_view);
11647 }
11648
11649 static void
11650 pspp_sheet_view_stop_editing (PsppSheetView *tree_view,
11651                             gboolean     cancel_editing)
11652 {
11653   PsppSheetViewColumn *column;
11654   GtkCellRenderer *cell;
11655
11656   if (tree_view->priv->edited_column == NULL)
11657     return;
11658
11659   /*
11660    * This is very evil. We need to do this, because
11661    * gtk_cell_editable_editing_done may trigger pspp_sheet_view_row_changed
11662    * later on. If pspp_sheet_view_row_changed notices
11663    * tree_view->priv->edited_column != NULL, it'll call
11664    * pspp_sheet_view_stop_editing again. Bad things will happen then.
11665    *
11666    * Please read that again if you intend to modify anything here.
11667    */
11668
11669   column = tree_view->priv->edited_column;
11670   tree_view->priv->edited_column = NULL;
11671
11672   cell = _pspp_sheet_view_column_get_edited_cell (column);
11673   gtk_cell_renderer_stop_editing (cell, cancel_editing);
11674
11675   if (!cancel_editing)
11676     gtk_cell_editable_editing_done (column->editable_widget);
11677
11678   tree_view->priv->edited_column = column;
11679
11680   gtk_cell_editable_remove_widget (column->editable_widget);
11681 }
11682
11683
11684 /**
11685  * pspp_sheet_view_set_hover_selection:
11686  * @tree_view: a #PsppSheetView
11687  * @hover: %TRUE to enable hover selection mode
11688  *
11689  * Enables of disables the hover selection mode of @tree_view.
11690  * Hover selection makes the selected row follow the pointer.
11691  * Currently, this works only for the selection modes 
11692  * %GTK_SELECTION_SINGLE and %GTK_SELECTION_BROWSE.
11693  * 
11694  * Since: 2.6
11695  **/
11696 void     
11697 pspp_sheet_view_set_hover_selection (PsppSheetView *tree_view,
11698                                    gboolean     hover)
11699 {
11700   hover = hover != FALSE;
11701
11702   if (hover != tree_view->priv->hover_selection)
11703     {
11704       tree_view->priv->hover_selection = hover;
11705
11706       g_object_notify (G_OBJECT (tree_view), "hover-selection");
11707     }
11708 }
11709
11710 /**
11711  * pspp_sheet_view_get_hover_selection:
11712  * @tree_view: a #PsppSheetView
11713  * 
11714  * Returns whether hover selection mode is turned on for @tree_view.
11715  * 
11716  * Return value: %TRUE if @tree_view is in hover selection mode
11717  *
11718  * Since: 2.6 
11719  **/
11720 gboolean 
11721 pspp_sheet_view_get_hover_selection (PsppSheetView *tree_view)
11722 {
11723   return tree_view->priv->hover_selection;
11724 }
11725
11726 /**
11727  * pspp_sheet_view_set_rubber_banding:
11728  * @tree_view: a #PsppSheetView
11729  * @enable: %TRUE to enable rubber banding
11730  *
11731  * Enables or disables rubber banding in @tree_view.  If the selection mode
11732  * is #GTK_SELECTION_MULTIPLE, rubber banding will allow the user to select
11733  * multiple rows by dragging the mouse.
11734  * 
11735  * Since: 2.10
11736  **/
11737 void
11738 pspp_sheet_view_set_rubber_banding (PsppSheetView *tree_view,
11739                                   gboolean     enable)
11740 {
11741   enable = enable != FALSE;
11742
11743   if (enable != tree_view->priv->rubber_banding_enable)
11744     {
11745       tree_view->priv->rubber_banding_enable = enable;
11746
11747       g_object_notify (G_OBJECT (tree_view), "rubber-banding");
11748     }
11749 }
11750
11751 /**
11752  * pspp_sheet_view_get_rubber_banding:
11753  * @tree_view: a #PsppSheetView
11754  * 
11755  * Returns whether rubber banding is turned on for @tree_view.  If the
11756  * selection mode is #GTK_SELECTION_MULTIPLE, rubber banding will allow the
11757  * user to select multiple rows by dragging the mouse.
11758  * 
11759  * Return value: %TRUE if rubber banding in @tree_view is enabled.
11760  *
11761  * Since: 2.10
11762  **/
11763 gboolean
11764 pspp_sheet_view_get_rubber_banding (PsppSheetView *tree_view)
11765 {
11766   return tree_view->priv->rubber_banding_enable;
11767 }
11768
11769 /**
11770  * pspp_sheet_view_is_rubber_banding_active:
11771  * @tree_view: a #PsppSheetView
11772  * 
11773  * Returns whether a rubber banding operation is currently being done
11774  * in @tree_view.
11775  *
11776  * Return value: %TRUE if a rubber banding operation is currently being
11777  * done in @tree_view.
11778  *
11779  * Since: 2.12
11780  **/
11781 gboolean
11782 pspp_sheet_view_is_rubber_banding_active (PsppSheetView *tree_view)
11783 {
11784   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
11785
11786   if (tree_view->priv->rubber_banding_enable
11787       && tree_view->priv->rubber_band_status == RUBBER_BAND_ACTIVE)
11788     return TRUE;
11789
11790   return FALSE;
11791 }
11792
11793 static void
11794 pspp_sheet_view_grab_notify (GtkWidget *widget,
11795                            gboolean   was_grabbed)
11796 {
11797   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
11798
11799   tree_view->priv->in_grab = !was_grabbed;
11800
11801   if (!was_grabbed)
11802     {
11803       tree_view->priv->pressed_button = -1;
11804
11805       if (tree_view->priv->rubber_band_status)
11806         pspp_sheet_view_stop_rubber_band (tree_view);
11807     }
11808 }
11809
11810 static void
11811 pspp_sheet_view_state_changed (GtkWidget      *widget,
11812                              GtkStateType    previous_state)
11813 {
11814   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
11815
11816   if (gtk_widget_get_realized (widget))
11817     {
11818       gdk_window_set_back_pixmap (widget->window, NULL, FALSE);
11819       gdk_window_set_background (tree_view->priv->bin_window, &widget->style->base[widget->state]);
11820     }
11821
11822   gtk_widget_queue_draw (widget);
11823 }
11824
11825 /**
11826  * pspp_sheet_view_get_grid_lines:
11827  * @tree_view: a #PsppSheetView
11828  *
11829  * Returns which grid lines are enabled in @tree_view.
11830  *
11831  * Return value: a #PsppSheetViewGridLines value indicating which grid lines
11832  * are enabled.
11833  *
11834  * Since: 2.10
11835  */
11836 PsppSheetViewGridLines
11837 pspp_sheet_view_get_grid_lines (PsppSheetView *tree_view)
11838 {
11839   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), 0);
11840
11841   return tree_view->priv->grid_lines;
11842 }
11843
11844 /**
11845  * pspp_sheet_view_set_grid_lines:
11846  * @tree_view: a #PsppSheetView
11847  * @grid_lines: a #PsppSheetViewGridLines value indicating which grid lines to
11848  * enable.
11849  *
11850  * Sets which grid lines to draw in @tree_view.
11851  *
11852  * Since: 2.10
11853  */
11854 void
11855 pspp_sheet_view_set_grid_lines (PsppSheetView           *tree_view,
11856                               PsppSheetViewGridLines   grid_lines)
11857 {
11858   PsppSheetViewPrivate *priv;
11859   GtkWidget *widget;
11860   PsppSheetViewGridLines old_grid_lines;
11861
11862   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11863
11864   priv = tree_view->priv;
11865   widget = GTK_WIDGET (tree_view);
11866
11867   old_grid_lines = priv->grid_lines;
11868   priv->grid_lines = grid_lines;
11869   
11870   if (gtk_widget_get_realized (widget))
11871     {
11872       if (grid_lines == PSPP_SHEET_VIEW_GRID_LINES_NONE &&
11873           priv->grid_line_gc)
11874         {
11875           g_object_unref (priv->grid_line_gc);
11876           priv->grid_line_gc = NULL;
11877         }
11878       
11879       if (grid_lines != PSPP_SHEET_VIEW_GRID_LINES_NONE && 
11880           !priv->grid_line_gc)
11881         {
11882           gint line_width;
11883           gint8 *dash_list;
11884
11885           gtk_widget_style_get (widget,
11886                                 "grid-line-width", &line_width,
11887                                 "grid-line-pattern", (gchar *)&dash_list,
11888                                 NULL);
11889       
11890           priv->grid_line_gc = gdk_gc_new (widget->window);
11891           gdk_gc_copy (priv->grid_line_gc, widget->style->black_gc);
11892           
11893           gdk_gc_set_line_attributes (priv->grid_line_gc, line_width,
11894                                       GDK_LINE_ON_OFF_DASH,
11895                                       GDK_CAP_BUTT, GDK_JOIN_MITER);
11896           gdk_gc_set_dashes (priv->grid_line_gc, 0, dash_list, 2);
11897
11898           g_free (dash_list);
11899         }      
11900     }
11901
11902   if (old_grid_lines != grid_lines)
11903     {
11904       gtk_widget_queue_draw (GTK_WIDGET (tree_view));
11905       
11906       g_object_notify (G_OBJECT (tree_view), "enable-grid-lines");
11907     }
11908 }
11909
11910 /**
11911  * pspp_sheet_view_set_tooltip_row:
11912  * @tree_view: a #PsppSheetView
11913  * @tooltip: a #GtkTooltip
11914  * @path: a #GtkTreePath
11915  *
11916  * Sets the tip area of @tooltip to be the area covered by the row at @path.
11917  * See also pspp_sheet_view_set_tooltip_column() for a simpler alternative.
11918  * See also gtk_tooltip_set_tip_area().
11919  *
11920  * Since: 2.12
11921  */
11922 void
11923 pspp_sheet_view_set_tooltip_row (PsppSheetView *tree_view,
11924                                GtkTooltip  *tooltip,
11925                                GtkTreePath *path)
11926 {
11927   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11928   g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
11929
11930   pspp_sheet_view_set_tooltip_cell (tree_view, tooltip, path, NULL, NULL);
11931 }
11932
11933 /**
11934  * pspp_sheet_view_set_tooltip_cell:
11935  * @tree_view: a #PsppSheetView
11936  * @tooltip: a #GtkTooltip
11937  * @path: (allow-none): a #GtkTreePath or %NULL
11938  * @column: (allow-none): a #PsppSheetViewColumn or %NULL
11939  * @cell: (allow-none): a #GtkCellRenderer or %NULL
11940  *
11941  * Sets the tip area of @tooltip to the area @path, @column and @cell have
11942  * in common.  For example if @path is %NULL and @column is set, the tip
11943  * area will be set to the full area covered by @column.  See also
11944  * gtk_tooltip_set_tip_area().
11945  *
11946  * See also pspp_sheet_view_set_tooltip_column() for a simpler alternative.
11947  *
11948  * Since: 2.12
11949  */
11950 void
11951 pspp_sheet_view_set_tooltip_cell (PsppSheetView       *tree_view,
11952                                 GtkTooltip        *tooltip,
11953                                 GtkTreePath       *path,
11954                                 PsppSheetViewColumn *column,
11955                                 GtkCellRenderer   *cell)
11956 {
11957   GdkRectangle rect;
11958
11959   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11960   g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
11961   g_return_if_fail (column == NULL || PSPP_IS_SHEET_VIEW_COLUMN (column));
11962   g_return_if_fail (cell == NULL || GTK_IS_CELL_RENDERER (cell));
11963
11964   /* Determine x values. */
11965   if (column && cell)
11966     {
11967       GdkRectangle tmp;
11968       gint start, width;
11969
11970       pspp_sheet_view_get_cell_area (tree_view, path, column, &tmp);
11971       pspp_sheet_view_column_cell_get_position (column, cell, &start, &width);
11972
11973       pspp_sheet_view_convert_bin_window_to_widget_coords (tree_view,
11974                                                          tmp.x + start, 0,
11975                                                          &rect.x, NULL);
11976       rect.width = width;
11977     }
11978   else if (column)
11979     {
11980       GdkRectangle tmp;
11981
11982       pspp_sheet_view_get_background_area (tree_view, NULL, column, &tmp);
11983       pspp_sheet_view_convert_bin_window_to_widget_coords (tree_view,
11984                                                          tmp.x, 0,
11985                                                          &rect.x, NULL);
11986       rect.width = tmp.width;
11987     }
11988   else
11989     {
11990       rect.x = 0;
11991       rect.width = GTK_WIDGET (tree_view)->allocation.width;
11992     }
11993
11994   /* Determine y values. */
11995   if (path)
11996     {
11997       GdkRectangle tmp;
11998
11999       pspp_sheet_view_get_background_area (tree_view, path, NULL, &tmp);
12000       pspp_sheet_view_convert_bin_window_to_widget_coords (tree_view,
12001                                                          0, tmp.y,
12002                                                          NULL, &rect.y);
12003       rect.height = tmp.height;
12004     }
12005   else
12006     {
12007       rect.y = 0;
12008       rect.height = tree_view->priv->vadjustment->page_size;
12009     }
12010
12011   gtk_tooltip_set_tip_area (tooltip, &rect);
12012 }
12013
12014 /**
12015  * pspp_sheet_view_get_tooltip_context:
12016  * @tree_view: a #PsppSheetView
12017  * @x: the x coordinate (relative to widget coordinates)
12018  * @y: the y coordinate (relative to widget coordinates)
12019  * @keyboard_tip: whether this is a keyboard tooltip or not
12020  * @model: (allow-none): a pointer to receive a #GtkTreeModel or %NULL
12021  * @path: (allow-none): a pointer to receive a #GtkTreePath or %NULL
12022  * @iter: (allow-none): a pointer to receive a #GtkTreeIter or %NULL
12023  *
12024  * This function is supposed to be used in a #GtkWidget::query-tooltip
12025  * signal handler for #PsppSheetView.  The @x, @y and @keyboard_tip values
12026  * which are received in the signal handler, should be passed to this
12027  * function without modification.
12028  *
12029  * The return value indicates whether there is a tree view row at the given
12030  * coordinates (%TRUE) or not (%FALSE) for mouse tooltips.  For keyboard
12031  * tooltips the row returned will be the cursor row.  When %TRUE, then any of
12032  * @model, @path and @iter which have been provided will be set to point to
12033  * that row and the corresponding model.  @x and @y will always be converted
12034  * to be relative to @tree_view's bin_window if @keyboard_tooltip is %FALSE.
12035  *
12036  * Return value: whether or not the given tooltip context points to a row.
12037  *
12038  * Since: 2.12
12039  */
12040 gboolean
12041 pspp_sheet_view_get_tooltip_context (PsppSheetView   *tree_view,
12042                                    gint          *x,
12043                                    gint          *y,
12044                                    gboolean       keyboard_tip,
12045                                    GtkTreeModel **model,
12046                                    GtkTreePath  **path,
12047                                    GtkTreeIter   *iter)
12048 {
12049   GtkTreePath *tmppath = NULL;
12050
12051   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
12052   g_return_val_if_fail (x != NULL, FALSE);
12053   g_return_val_if_fail (y != NULL, FALSE);
12054
12055   if (keyboard_tip)
12056     {
12057       pspp_sheet_view_get_cursor (tree_view, &tmppath, NULL);
12058
12059       if (!tmppath)
12060         return FALSE;
12061     }
12062   else
12063     {
12064       pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view, *x, *y,
12065                                                          x, y);
12066
12067       if (!pspp_sheet_view_get_path_at_pos (tree_view, *x, *y,
12068                                           &tmppath, NULL, NULL, NULL))
12069         return FALSE;
12070     }
12071
12072   if (model)
12073     *model = pspp_sheet_view_get_model (tree_view);
12074
12075   if (iter)
12076     gtk_tree_model_get_iter (pspp_sheet_view_get_model (tree_view),
12077                              iter, tmppath);
12078
12079   if (path)
12080     *path = tmppath;
12081   else
12082     gtk_tree_path_free (tmppath);
12083
12084   return TRUE;
12085 }
12086
12087 static gboolean
12088 pspp_sheet_view_set_tooltip_query_cb (GtkWidget  *widget,
12089                                     gint        x,
12090                                     gint        y,
12091                                     gboolean    keyboard_tip,
12092                                     GtkTooltip *tooltip,
12093                                     gpointer    data)
12094 {
12095   GValue value = { 0, };
12096   GValue transformed = { 0, };
12097   GtkTreeIter iter;
12098   GtkTreePath *path;
12099   GtkTreeModel *model;
12100   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
12101
12102   if (!pspp_sheet_view_get_tooltip_context (PSPP_SHEET_VIEW (widget),
12103                                           &x, &y,
12104                                           keyboard_tip,
12105                                           &model, &path, &iter))
12106     return FALSE;
12107
12108   gtk_tree_model_get_value (model, &iter,
12109                             tree_view->priv->tooltip_column, &value);
12110
12111   g_value_init (&transformed, G_TYPE_STRING);
12112
12113   if (!g_value_transform (&value, &transformed))
12114     {
12115       g_value_unset (&value);
12116       gtk_tree_path_free (path);
12117
12118       return FALSE;
12119     }
12120
12121   g_value_unset (&value);
12122
12123   if (!g_value_get_string (&transformed))
12124     {
12125       g_value_unset (&transformed);
12126       gtk_tree_path_free (path);
12127
12128       return FALSE;
12129     }
12130
12131   gtk_tooltip_set_markup (tooltip, g_value_get_string (&transformed));
12132   pspp_sheet_view_set_tooltip_row (tree_view, tooltip, path);
12133
12134   gtk_tree_path_free (path);
12135   g_value_unset (&transformed);
12136
12137   return TRUE;
12138 }
12139
12140 /**
12141  * pspp_sheet_view_set_tooltip_column:
12142  * @tree_view: a #PsppSheetView
12143  * @column: an integer, which is a valid column number for @tree_view's model
12144  *
12145  * If you only plan to have simple (text-only) tooltips on full rows, you
12146  * can use this function to have #PsppSheetView handle these automatically
12147  * for you. @column should be set to the column in @tree_view's model
12148  * containing the tooltip texts, or -1 to disable this feature.
12149  *
12150  * When enabled, #GtkWidget::has-tooltip will be set to %TRUE and
12151  * @tree_view will connect a #GtkWidget::query-tooltip signal handler.
12152  *
12153  * Note that the signal handler sets the text with gtk_tooltip_set_markup(),
12154  * so &amp;, &lt;, etc have to be escaped in the text.
12155  *
12156  * Since: 2.12
12157  */
12158 void
12159 pspp_sheet_view_set_tooltip_column (PsppSheetView *tree_view,
12160                                   gint         column)
12161 {
12162   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
12163
12164   if (column == tree_view->priv->tooltip_column)
12165     return;
12166
12167   if (column == -1)
12168     {
12169       g_signal_handlers_disconnect_by_func (tree_view,
12170                                             pspp_sheet_view_set_tooltip_query_cb,
12171                                             NULL);
12172       gtk_widget_set_has_tooltip (GTK_WIDGET (tree_view), FALSE);
12173     }
12174   else
12175     {
12176       if (tree_view->priv->tooltip_column == -1)
12177         {
12178           g_signal_connect (tree_view, "query-tooltip",
12179                             G_CALLBACK (pspp_sheet_view_set_tooltip_query_cb), NULL);
12180           gtk_widget_set_has_tooltip (GTK_WIDGET (tree_view), TRUE);
12181         }
12182     }
12183
12184   tree_view->priv->tooltip_column = column;
12185   g_object_notify (G_OBJECT (tree_view), "tooltip-column");
12186 }
12187
12188 /**
12189  * pspp_sheet_view_get_tooltip_column:
12190  * @tree_view: a #PsppSheetView
12191  *
12192  * Returns the column of @tree_view's model which is being used for
12193  * displaying tooltips on @tree_view's rows.
12194  *
12195  * Return value: the index of the tooltip column that is currently being
12196  * used, or -1 if this is disabled.
12197  *
12198  * Since: 2.12
12199  */
12200 gint
12201 pspp_sheet_view_get_tooltip_column (PsppSheetView *tree_view)
12202 {
12203   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), 0);
12204
12205   return tree_view->priv->tooltip_column;
12206 }
12207
12208 gboolean
12209 _gtk_boolean_handled_accumulator (GSignalInvocationHint *ihint,
12210                                   GValue                *return_accu,
12211                                   const GValue          *handler_return,
12212                                   gpointer               dummy)
12213 {
12214   gboolean continue_emission;
12215   gboolean signal_handled;
12216   
12217   signal_handled = g_value_get_boolean (handler_return);
12218   g_value_set_boolean (return_accu, signal_handled);
12219   continue_emission = !signal_handled;
12220   
12221   return continue_emission;
12222 }
12223
12224 GType
12225 pspp_sheet_view_grid_lines_get_type (void)
12226 {
12227     static GType etype = 0;
12228     if (G_UNLIKELY(etype == 0)) {
12229         static const GEnumValue values[] = {
12230             { PSPP_SHEET_VIEW_GRID_LINES_NONE, "PSPP_SHEET_VIEW_GRID_LINES_NONE", "none" },
12231             { PSPP_SHEET_VIEW_GRID_LINES_HORIZONTAL, "PSPP_SHEET_VIEW_GRID_LINES_HORIZONTAL", "horizontal" },
12232             { PSPP_SHEET_VIEW_GRID_LINES_VERTICAL, "PSPP_SHEET_VIEW_GRID_LINES_VERTICAL", "vertical" },
12233             { PSPP_SHEET_VIEW_GRID_LINES_BOTH, "PSPP_SHEET_VIEW_GRID_LINES_BOTH", "both" },
12234             { 0, NULL, NULL }
12235         };
12236         etype = g_enum_register_static (g_intern_static_string ("PsppSheetViewGridLines"), values);
12237     }
12238     return etype;
12239 }