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