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