Sheet Grid Lines: Correct positioning.
[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 x = 0;
3832
3833   if (tree_view->priv->grid_lines != PSPP_SHEET_VIEW_GRID_LINES_VERTICAL
3834       && tree_view->priv->grid_lines != PSPP_SHEET_VIEW_GRID_LINES_BOTH)
3835     return;
3836
3837   /* Only draw the lines for visible rows and columns */
3838   gboolean rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
3839
3840   for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns));
3841        list;
3842        list = (rtl ? list->prev : list->next))
3843     {
3844       PsppSheetViewColumn *column = list->data;
3845
3846       if (! column->visible)
3847         continue;
3848
3849       x += column->width;
3850
3851       cairo_set_line_width (cr, 1.0);
3852       cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
3853       cairo_move_to (cr, x + 0.5, min_y);
3854       cairo_line_to (cr, x + 0.5, max_y - min_y - 0.5);
3855       cairo_stroke (cr);
3856     }
3857 }
3858
3859 /* Warning: Very scary function.
3860  * Modify at your own risk
3861  *
3862  * KEEP IN SYNC WITH pspp_sheet_view_create_row_drag_icon()!
3863  * FIXME: It's not...
3864  */
3865 static void
3866 pspp_sheet_view_draw_bin (GtkWidget      *widget,
3867                           cairo_t *cr)
3868 {
3869   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
3870   GtkTreePath *path;
3871   GList *list;
3872   int node;
3873   int cursor = -1;
3874   int drag_highlight = -1;
3875   GtkTreeIter iter;
3876   gint new_y;
3877   gint y_offset, cell_offset;
3878   gint max_height;
3879   GdkRectangle background_area;
3880   GdkRectangle cell_area;
3881   guint flags;
3882   gint bin_window_width;
3883   gint bin_window_height;
3884   GtkTreePath *cursor_path;
3885   GtkTreePath *drag_dest_path;
3886   GList *first_column, *last_column;
3887   gint vertical_separator;
3888   gint horizontal_separator;
3889   gint focus_line_width;
3890   gboolean allow_rules;
3891   gboolean rtl;
3892   gint n_visible_columns;
3893   gint grid_line_width;
3894   gboolean row_ending_details;
3895   gboolean draw_vgrid_lines, draw_hgrid_lines;
3896   gint min_y, max_y;
3897   GtkStyleContext *context;
3898   context = gtk_widget_get_style_context (widget);
3899
3900   GdkRectangle Zarea;
3901   GtkAllocation allocation;
3902   gtk_widget_get_allocation (widget, &allocation);
3903
3904   GdkRectangle exposed_rect;
3905   gdk_cairo_get_clip_rectangle (cr, &exposed_rect);
3906   
3907   Zarea.x =      0;
3908   Zarea.y =      0;
3909   Zarea.height = allocation.height;
3910
3911   rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
3912
3913   gtk_widget_style_get (widget,
3914                         "horizontal-separator", &horizontal_separator,
3915                         "vertical-separator", &vertical_separator,
3916                         "allow-rules", &allow_rules,
3917                         "focus-line-width", &focus_line_width,
3918                         "row-ending-details", &row_ending_details,
3919                         NULL);
3920
3921   if (tree_view->priv->row_count == 0)
3922     {
3923       draw_empty_focus (tree_view);
3924       return;
3925     }
3926
3927 #if GTK3_TRANSITION
3928   /* clip event->area to the visible area */
3929   if (Zarea.height < 0.5)
3930     return;
3931 #endif
3932
3933   validate_visible_area (tree_view);
3934
3935   new_y = TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, Zarea.y);
3936
3937   if (new_y < 0)
3938     new_y = 0;
3939   y_offset = -pspp_sheet_view_find_offset (tree_view, new_y, &node);
3940   bin_window_width = 
3941     gdk_window_get_width (tree_view->priv->bin_window);
3942
3943   bin_window_height = 
3944     gdk_window_get_height (tree_view->priv->bin_window);
3945
3946
3947   if (tree_view->priv->height < bin_window_height)
3948     {
3949       gtk_paint_flat_box (gtk_widget_get_style (widget),
3950                           cr,
3951                           gtk_widget_get_state (widget),
3952                           GTK_SHADOW_NONE,
3953                           widget,
3954                           "cell_even",
3955                           0, tree_view->priv->height,
3956                           bin_window_width,
3957                           bin_window_height - tree_view->priv->height);
3958     }
3959
3960   if (node < 0)
3961     return;
3962
3963   /* find the path for the node */
3964   path = _pspp_sheet_view_find_path ((PsppSheetView *)widget, node);
3965   gtk_tree_model_get_iter (tree_view->priv->model,
3966                            &iter,
3967                            path);
3968   gtk_tree_path_free (path);
3969   
3970   cursor_path = NULL;
3971   drag_dest_path = NULL;
3972
3973   if (tree_view->priv->cursor)
3974     cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
3975
3976   if (cursor_path)
3977     _pspp_sheet_view_find_node (tree_view, cursor_path, &cursor);
3978
3979   if (tree_view->priv->drag_dest_row)
3980     drag_dest_path = gtk_tree_row_reference_get_path (tree_view->priv->drag_dest_row);
3981
3982   if (drag_dest_path)
3983     _pspp_sheet_view_find_node (tree_view, drag_dest_path,
3984                                 &drag_highlight);
3985
3986   draw_vgrid_lines =
3987     tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_VERTICAL
3988     || tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_BOTH;
3989   draw_hgrid_lines =
3990     tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_HORIZONTAL
3991     || tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_BOTH;
3992
3993   if (draw_vgrid_lines || draw_hgrid_lines)
3994     gtk_widget_style_get (widget, "grid-line-width", &grid_line_width, NULL);
3995   
3996   n_visible_columns = 0;
3997   for (list = tree_view->priv->columns; list; list = list->next)
3998     {
3999       if (! PSPP_SHEET_VIEW_COLUMN (list->data)->visible)
4000         continue;
4001       n_visible_columns ++;
4002     }
4003
4004   /* Find the last column */
4005   for (last_column = g_list_last (tree_view->priv->columns);
4006        last_column && !(PSPP_SHEET_VIEW_COLUMN (last_column->data)->visible);
4007        last_column = last_column->prev)
4008     ;
4009
4010   /* and the first */
4011   for (first_column = g_list_first (tree_view->priv->columns);
4012        first_column && !(PSPP_SHEET_VIEW_COLUMN (first_column->data)->visible);
4013        first_column = first_column->next)
4014     ;
4015
4016   /* Actually process the expose event.  To do this, we want to
4017    * start at the first node of the event, and walk the tree in
4018    * order, drawing each successive node.
4019    */
4020
4021   min_y = y_offset;
4022   do
4023     {
4024       gboolean parity;
4025       gboolean is_first = FALSE;
4026       gboolean is_last = FALSE;
4027       gboolean done = FALSE;
4028       gboolean selected;
4029
4030       max_height = ROW_HEIGHT (tree_view);
4031
4032       cell_offset = 0;
4033
4034       background_area.y = y_offset + Zarea.y;
4035       background_area.height = max_height;
4036       max_y = background_area.y + max_height;
4037
4038       flags = 0;
4039
4040       if (node == tree_view->priv->prelight_node)
4041         flags |= GTK_CELL_RENDERER_PRELIT;
4042
4043       selected = pspp_sheet_view_node_is_selected (tree_view, node);
4044
4045       parity = node % 2;
4046
4047       for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns));
4048            list;
4049            list = (rtl ? list->prev : list->next))
4050         {
4051           PsppSheetViewColumn *column = list->data;
4052           const gchar *detail = NULL;
4053           gboolean selected_column;
4054           GtkStateType state;
4055
4056           if (!column->visible)
4057             continue;
4058
4059           if (tree_view->priv->selection->type == PSPP_SHEET_SELECTION_RECTANGLE)
4060             selected_column = column->selected && column->selectable;
4061           else
4062             selected_column = TRUE;
4063
4064 #if GTK3_TRANSITION
4065           if (cell_offset > Zarea.x + Zarea.width ||
4066               cell_offset + column->width < Zarea.x)
4067             {
4068               cell_offset += column->width;
4069               continue;
4070             }
4071 #endif
4072
4073           if (selected && selected_column)
4074             flags |= GTK_CELL_RENDERER_SELECTED;
4075           else
4076             flags &= ~GTK_CELL_RENDERER_SELECTED;
4077
4078           if (column->show_sort_indicator)
4079             flags |= GTK_CELL_RENDERER_SORTED;
4080           else
4081             flags &= ~GTK_CELL_RENDERER_SORTED;
4082
4083           if (cursor == node)
4084             flags |= GTK_CELL_RENDERER_FOCUSED;
4085           else
4086             flags &= ~GTK_CELL_RENDERER_FOCUSED;
4087
4088           background_area.x = cell_offset;
4089           background_area.width = column->width;
4090
4091           cell_area = background_area;
4092           cell_area.y += vertical_separator / 2;
4093           cell_area.x += horizontal_separator / 2;
4094           cell_area.height -= vertical_separator;
4095           cell_area.width -= horizontal_separator;
4096
4097           if (draw_vgrid_lines)
4098             {
4099               if (list == first_column)
4100                 {
4101                   cell_area.width -= grid_line_width / 2;
4102                 }
4103               else if (list == last_column)
4104                 {
4105                   cell_area.x += grid_line_width / 2;
4106                   cell_area.width -= grid_line_width / 2;
4107                 }
4108               else
4109                 {
4110                   cell_area.x += grid_line_width / 2;
4111                   cell_area.width -= grid_line_width;
4112                 }
4113             }
4114
4115           if (draw_hgrid_lines)
4116             {
4117               cell_area.y += grid_line_width / 2;
4118               cell_area.height -= grid_line_width;
4119             }
4120
4121 #if GTK3_TRANSITION
4122           if (gdk_region_rect_in (event->region, &background_area) == GDK_OVERLAP_RECTANGLE_OUT)
4123 #else
4124           if (!gdk_rectangle_intersect (&background_area, &exposed_rect, NULL))
4125 #endif
4126             {
4127               cell_offset += column->width;
4128               continue;
4129             }
4130
4131
4132           pspp_sheet_view_column_cell_set_cell_data (column,
4133                                                      tree_view->priv->model,
4134                                                      &iter);
4135
4136           /* Select the detail for drawing the cell.  relevant
4137            * factors are parity, sortedness, and whether to
4138            * display rules.
4139            */
4140           if (allow_rules && tree_view->priv->has_rules)
4141             {
4142               if ((flags & GTK_CELL_RENDERER_SORTED) &&
4143                   n_visible_columns >= 3)
4144                 {
4145                   if (parity)
4146                     detail = "cell_odd_ruled_sorted";
4147                   else
4148                     detail = "cell_even_ruled_sorted";
4149                 }
4150               else
4151                 {
4152                   if (parity)
4153                     detail = "cell_odd_ruled";
4154                   else
4155                     detail = "cell_even_ruled";
4156                 }
4157             }
4158           else
4159             {
4160               if ((flags & GTK_CELL_RENDERER_SORTED) &&
4161                   n_visible_columns >= 3)
4162                 {
4163                   if (parity)
4164                     detail = "cell_odd_sorted";
4165                   else
4166                     detail = "cell_even_sorted";
4167                 }
4168               else
4169                 {
4170                   if (parity)
4171                     detail = "cell_odd";
4172                   else
4173                     detail = "cell_even";
4174                 }
4175             }
4176
4177           g_assert (detail);
4178
4179           gtk_style_context_save (context);
4180           state = gtk_cell_renderer_get_state (NULL, widget, flags);
4181           gtk_style_context_set_state (context, state);
4182           gtk_style_context_add_class (context, GTK_STYLE_CLASS_CELL);
4183
4184           /* Draw background */
4185           gtk_render_background (context, cr,
4186                                  background_area.x,
4187                                  background_area.y,
4188                                  background_area.width,
4189                                  background_area.height);
4190
4191           /* Draw frame */
4192           gtk_render_frame (context, cr,
4193                             background_area.x,
4194                             background_area.y,
4195                             background_area.width,
4196                             background_area.height);
4197
4198           if (draw_hgrid_lines)
4199             {
4200               cairo_set_line_width (cr, 1.0);
4201               cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
4202
4203               if (background_area.y >= 0)
4204                 {
4205 #if GTK3_TRANSITION
4206                   gdk_draw_line (event->window,
4207                                  tree_view->priv->grid_line_gc[widget->state],
4208                                  background_area.x, background_area.y,
4209                                  background_area.x + background_area.width,
4210                                  background_area.y);
4211 #else
4212                   cairo_move_to (cr, background_area.x, background_area.y - 0.5);
4213                   cairo_line_to (cr, background_area.x + background_area.width,
4214                                  background_area.y - 0.5);
4215 #endif
4216                 }
4217
4218               if (y_offset + max_height <= Zarea.height - 0.5)
4219                 {
4220 #if GTK3_TRANSITION
4221                   gdk_draw_line (event->window,
4222                                  tree_view->priv->grid_line_gc[widget->state],
4223                                  background_area.x, background_area.y + max_height,
4224                                  background_area.x + background_area.width,
4225                                  background_area.y + max_height);
4226 #else
4227
4228                   cairo_move_to (cr, background_area.x, background_area.y + max_height - 0.5);
4229                   cairo_line_to (cr, background_area.x + background_area.width,
4230                                  background_area.y + max_height - 0.5);
4231 #endif
4232                 }
4233               cairo_stroke (cr);
4234             }
4235
4236           _pspp_sheet_view_column_cell_render (column,
4237                                                cr,
4238                                                &background_area,
4239                                                &cell_area,
4240                                                flags);
4241
4242
4243           cell_offset += column->width;
4244           gtk_style_context_restore (context);
4245         }
4246
4247       if (node == drag_highlight)
4248         {
4249           /* Draw indicator for the drop
4250            */
4251           gint highlight_y = -1;
4252           int node = -1;
4253           gint width;
4254
4255           switch (tree_view->priv->drag_dest_pos)
4256             {
4257             case PSPP_SHEET_VIEW_DROP_BEFORE:
4258               highlight_y = background_area.y - 1;
4259               if (highlight_y < 0)
4260                       highlight_y = 0;
4261               break;
4262
4263             case PSPP_SHEET_VIEW_DROP_AFTER:
4264               highlight_y = background_area.y + background_area.height - 1;
4265               break;
4266
4267             case PSPP_SHEET_VIEW_DROP_INTO_OR_BEFORE:
4268             case PSPP_SHEET_VIEW_DROP_INTO_OR_AFTER:
4269               _pspp_sheet_view_find_node (tree_view, drag_dest_path, &node);
4270
4271               if (node < 0)
4272                 break;
4273               width = gdk_window_get_width (tree_view->priv->bin_window);
4274
4275               if (row_ending_details)
4276                 gtk_paint_focus (gtk_widget_get_style (widget),
4277                                  cr,
4278                                  gtk_widget_get_state (widget),
4279                                  widget,
4280                                  (is_first
4281                                   ? (is_last ? "treeview-drop-indicator" : "treeview-drop-indicator-left" )
4282                                   : (is_last ? "treeview-drop-indicator-right" : "tree-view-drop-indicator-middle" )),
4283                                  0, BACKGROUND_FIRST_PIXEL (tree_view, node)
4284                                  - focus_line_width / 2,
4285                                  width, ROW_HEIGHT (tree_view)
4286                                - focus_line_width + 1);
4287               else
4288                 gtk_paint_focus (gtk_widget_get_style (widget),
4289                                  cr,
4290                                  gtk_widget_get_state (widget),
4291                                  widget,
4292                                  "treeview-drop-indicator",
4293                                  0, BACKGROUND_FIRST_PIXEL (tree_view, node)
4294                                  - focus_line_width / 2,
4295                                  width, ROW_HEIGHT (tree_view)
4296                                  - focus_line_width + 1);
4297               break;
4298             }
4299
4300 #if GTK3_TRANSITION
4301           if (highlight_y >= 0)
4302             {
4303               gdk_draw_line (event->window,
4304                              widget->style->fg_gc[gtk_widget_get_state (widget)],
4305                              0,
4306                              highlight_y,
4307                              rtl ? 0 : bin_window_width,
4308                              highlight_y);
4309             }
4310 #endif
4311         }
4312
4313       y_offset += max_height;
4314
4315       do
4316         {
4317           node = pspp_sheet_view_node_next (tree_view, node);
4318           if (node >= 0)
4319             {
4320               gboolean has_next = gtk_tree_model_iter_next (tree_view->priv->model, &iter);
4321               done = TRUE;
4322
4323               /* Sanity Check! */
4324               TREE_VIEW_INTERNAL_ASSERT_VOID (has_next);
4325             }
4326           else
4327             goto done;
4328         }
4329       while (!done);
4330     }
4331   while (y_offset < Zarea.height);
4332
4333 done:
4334   pspp_sheet_view_draw_vertical_grid_lines (tree_view, cr, n_visible_columns,
4335                                    min_y, max_y);
4336
4337 #if GTK3_TRANSITION
4338  if (tree_view->priv->rubber_band_status == RUBBER_BAND_ACTIVE)
4339    {
4340      GdkRectangle *rectangles;
4341      gint n_rectangles;
4342
4343      gdk_region_get_rectangles (event->region,
4344                                 &rectangles,
4345                                 &n_rectangles);
4346
4347      while (n_rectangles--)
4348        pspp_sheet_view_paint_rubber_band (tree_view, &rectangles[n_rectangles]);
4349
4350      g_free (rectangles);
4351    }
4352 #endif
4353
4354   if (cursor_path)
4355     gtk_tree_path_free (cursor_path);
4356
4357   if (drag_dest_path)
4358     gtk_tree_path_free (drag_dest_path);
4359
4360   return;
4361 }
4362
4363
4364 static gboolean
4365 pspp_sheet_view_draw (GtkWidget      *widget,
4366                       cairo_t *cr)
4367 {
4368   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
4369   GtkStyleContext *context;
4370
4371   context = gtk_widget_get_style_context (widget);
4372
4373   if (gtk_cairo_should_draw_window (cr, tree_view->priv->bin_window))
4374     {
4375       GList *tmp_list;
4376
4377       cairo_save (cr);
4378       gtk_cairo_transform_to_window(cr,widget,tree_view->priv->bin_window);
4379       pspp_sheet_view_draw_bin (widget, cr);
4380       cairo_restore (cr);
4381
4382       /* We can't just chain up to Container::expose as it will try to send the
4383        * event to the headers, so we handle propagating it to our children
4384        * (eg. widgets being edited) ourselves.
4385        */
4386       tmp_list = tree_view->priv->children;
4387       while (tmp_list)
4388         {
4389           PsppSheetViewChild *child = tmp_list->data;
4390           tmp_list = tmp_list->next;
4391
4392           gtk_container_propagate_draw (GTK_CONTAINER (tree_view), child->widget, cr);
4393         }
4394     }
4395   else
4396     {
4397       gtk_render_background (context, cr,
4398                              0, 0,
4399                              gtk_widget_get_allocated_width (widget),
4400                              gtk_widget_get_allocated_height (widget));
4401     }
4402
4403   gtk_style_context_save (context);
4404   gtk_style_context_remove_class (context, GTK_STYLE_CLASS_VIEW);
4405
4406   if (gtk_cairo_should_draw_window (cr, tree_view->priv->header_window))
4407     {
4408       gint n_visible_columns;
4409       GList *list;
4410
4411       for (list = tree_view->priv->columns; list != NULL; list = list->next)
4412         {
4413           PsppSheetViewColumn *column = list->data;
4414
4415           if (column == tree_view->priv->drag_column || !column->visible)
4416             continue;
4417
4418           if (span_intersects (column->allocation.x, column->allocation.width,
4419                                (int) gtk_adjustment_get_value (tree_view->priv->hadjustment),
4420                                (int) gtk_widget_get_allocated_width (widget))
4421               && column->button != NULL)
4422             gtk_container_propagate_draw (GTK_CONTAINER (tree_view),
4423                                           column->button, cr);
4424         }
4425
4426       n_visible_columns = 0;
4427       for (list = tree_view->priv->columns; list; list = list->next)
4428         {
4429           if (! PSPP_SHEET_VIEW_COLUMN (list->data)->visible)
4430             continue;
4431           n_visible_columns ++;
4432         }
4433       cairo_save (cr);
4434       gtk_cairo_transform_to_window(cr,widget,tree_view->priv->header_window);
4435       pspp_sheet_view_draw_vertical_grid_lines (tree_view,
4436                                                 cr,
4437                                                 n_visible_columns,
4438                                                 0,
4439                                                 TREE_VIEW_HEADER_HEIGHT (tree_view));
4440       cairo_restore (cr);
4441     }
4442   if (tree_view->priv->drag_window &&
4443       gtk_cairo_should_draw_window (cr, tree_view->priv->drag_window))
4444     {
4445       gtk_container_propagate_draw (GTK_CONTAINER (tree_view),
4446                                     tree_view->priv->drag_column->button,
4447                                     cr);
4448     }
4449
4450   gtk_style_context_restore (context);
4451   return FALSE;
4452 }
4453
4454 enum
4455 {
4456   DROP_HOME,
4457   DROP_RIGHT,
4458   DROP_LEFT,
4459   DROP_END
4460 };
4461
4462 /* returns 0x1 when no column has been found -- yes it's hackish */
4463 static PsppSheetViewColumn *
4464 pspp_sheet_view_get_drop_column (PsppSheetView       *tree_view,
4465                                PsppSheetViewColumn *column,
4466                                gint               drop_position)
4467 {
4468   PsppSheetViewColumn *left_column = NULL;
4469   PsppSheetViewColumn *cur_column = NULL;
4470   GList *tmp_list;
4471
4472   if (!column->reorderable)
4473     return (PsppSheetViewColumn *)0x1;
4474
4475   switch (drop_position)
4476     {
4477       case DROP_HOME:
4478         /* find first column where we can drop */
4479         tmp_list = tree_view->priv->columns;
4480         if (column == PSPP_SHEET_VIEW_COLUMN (tmp_list->data))
4481           return (PsppSheetViewColumn *)0x1;
4482
4483         while (tmp_list)
4484           {
4485             g_assert (tmp_list);
4486
4487             cur_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
4488             tmp_list = tmp_list->next;
4489
4490             if (left_column && left_column->visible == FALSE)
4491               continue;
4492
4493             if (!tree_view->priv->column_drop_func)
4494               return left_column;
4495
4496             if (!tree_view->priv->column_drop_func (tree_view, column, left_column, cur_column, tree_view->priv->column_drop_func_data))
4497               {
4498                 left_column = cur_column;
4499                 continue;
4500               }
4501
4502             return left_column;
4503           }
4504
4505         if (!tree_view->priv->column_drop_func)
4506           return left_column;
4507
4508         if (tree_view->priv->column_drop_func (tree_view, column, left_column, NULL, tree_view->priv->column_drop_func_data))
4509           return left_column;
4510         else
4511           return (PsppSheetViewColumn *)0x1;
4512         break;
4513
4514       case DROP_RIGHT:
4515         /* find first column after column where we can drop */
4516         tmp_list = tree_view->priv->columns;
4517
4518         for (; tmp_list; tmp_list = tmp_list->next)
4519           if (PSPP_SHEET_VIEW_COLUMN (tmp_list->data) == column)
4520             break;
4521
4522         if (!tmp_list || !tmp_list->next)
4523           return (PsppSheetViewColumn *)0x1;
4524
4525         tmp_list = tmp_list->next;
4526         left_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
4527         tmp_list = tmp_list->next;
4528
4529         while (tmp_list)
4530           {
4531             g_assert (tmp_list);
4532
4533             cur_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
4534             tmp_list = tmp_list->next;
4535
4536             if (left_column && left_column->visible == FALSE)
4537               {
4538                 left_column = cur_column;
4539                 if (tmp_list)
4540                   tmp_list = tmp_list->next;
4541                 continue;
4542               }
4543
4544             if (!tree_view->priv->column_drop_func)
4545               return left_column;
4546
4547             if (!tree_view->priv->column_drop_func (tree_view, column, left_column, cur_column, tree_view->priv->column_drop_func_data))
4548               {
4549                 left_column = cur_column;
4550                 continue;
4551               }
4552
4553             return left_column;
4554           }
4555
4556         if (!tree_view->priv->column_drop_func)
4557           return left_column;
4558
4559         if (tree_view->priv->column_drop_func (tree_view, column, left_column, NULL, tree_view->priv->column_drop_func_data))
4560           return left_column;
4561         else
4562           return (PsppSheetViewColumn *)0x1;
4563         break;
4564
4565       case DROP_LEFT:
4566         /* find first column before column where we can drop */
4567         tmp_list = tree_view->priv->columns;
4568
4569         for (; tmp_list; tmp_list = tmp_list->next)
4570           if (PSPP_SHEET_VIEW_COLUMN (tmp_list->data) == column)
4571             break;
4572
4573         if (!tmp_list || !tmp_list->prev)
4574           return (PsppSheetViewColumn *)0x1;
4575
4576         tmp_list = tmp_list->prev;
4577         cur_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
4578         tmp_list = tmp_list->prev;
4579
4580         while (tmp_list)
4581           {
4582             g_assert (tmp_list);
4583
4584             left_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
4585
4586             if (left_column && !left_column->visible)
4587               {
4588                 /*if (!tmp_list->prev)
4589                   return (PsppSheetViewColumn *)0x1;
4590                   */
4591 /*
4592                 cur_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->prev->data);
4593                 tmp_list = tmp_list->prev->prev;
4594                 continue;*/
4595
4596                 cur_column = left_column;
4597                 if (tmp_list)
4598                   tmp_list = tmp_list->prev;
4599                 continue;
4600               }
4601
4602             if (!tree_view->priv->column_drop_func)
4603               return left_column;
4604
4605             if (tree_view->priv->column_drop_func (tree_view, column, left_column, cur_column, tree_view->priv->column_drop_func_data))
4606               return left_column;
4607
4608             cur_column = left_column;
4609             tmp_list = tmp_list->prev;
4610           }
4611
4612         if (!tree_view->priv->column_drop_func)
4613           return NULL;
4614
4615         if (tree_view->priv->column_drop_func (tree_view, column, NULL, cur_column, tree_view->priv->column_drop_func_data))
4616           return NULL;
4617         else
4618           return (PsppSheetViewColumn *)0x1;
4619         break;
4620
4621       case DROP_END:
4622         /* same as DROP_HOME case, but doing it backwards */
4623         tmp_list = g_list_last (tree_view->priv->columns);
4624         cur_column = NULL;
4625
4626         if (column == PSPP_SHEET_VIEW_COLUMN (tmp_list->data))
4627           return (PsppSheetViewColumn *)0x1;
4628
4629         while (tmp_list)
4630           {
4631             g_assert (tmp_list);
4632
4633             left_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
4634
4635             if (left_column && !left_column->visible)
4636               {
4637                 cur_column = left_column;
4638                 tmp_list = tmp_list->prev;
4639               }
4640
4641             if (!tree_view->priv->column_drop_func)
4642               return left_column;
4643
4644             if (tree_view->priv->column_drop_func (tree_view, column, left_column, cur_column, tree_view->priv->column_drop_func_data))
4645               return left_column;
4646
4647             cur_column = left_column;
4648             tmp_list = tmp_list->prev;
4649           }
4650
4651         if (!tree_view->priv->column_drop_func)
4652           return NULL;
4653
4654         if (tree_view->priv->column_drop_func (tree_view, column, NULL, cur_column, tree_view->priv->column_drop_func_data))
4655           return NULL;
4656         else
4657           return (PsppSheetViewColumn *)0x1;
4658         break;
4659     }
4660
4661   return (PsppSheetViewColumn *)0x1;
4662 }
4663
4664 static gboolean
4665 pspp_sheet_view_key_press (GtkWidget   *widget,
4666                          GdkEventKey *event)
4667 {
4668   PsppSheetView *tree_view = (PsppSheetView *) widget;
4669
4670   if (tree_view->priv->rubber_band_status)
4671     {
4672       if (event->keyval == GDK_Escape)
4673         pspp_sheet_view_stop_rubber_band (tree_view);
4674
4675       return TRUE;
4676     }
4677
4678   if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_DRAG))
4679     {
4680       if (event->keyval == GDK_Escape)
4681         {
4682           tree_view->priv->cur_reorder = NULL;
4683           pspp_sheet_view_button_release_drag_column (widget, NULL);
4684         }
4685       return TRUE;
4686     }
4687
4688   if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_HEADERS_VISIBLE))
4689     {
4690       GList *focus_column;
4691       gboolean rtl;
4692
4693       rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
4694
4695       for (focus_column = tree_view->priv->columns;
4696            focus_column;
4697            focus_column = focus_column->next)
4698         {
4699           PsppSheetViewColumn *column = PSPP_SHEET_VIEW_COLUMN (focus_column->data);
4700
4701           if (column->button && gtk_widget_has_focus (column->button))
4702             break;
4703         }
4704
4705       if (focus_column &&
4706           (event->state & GDK_SHIFT_MASK) && (event->state & GDK_MOD1_MASK) &&
4707           (event->keyval == GDK_Left || event->keyval == GDK_KP_Left
4708            || event->keyval == GDK_Right || event->keyval == GDK_KP_Right))
4709         {
4710           PsppSheetViewColumn *column = PSPP_SHEET_VIEW_COLUMN (focus_column->data);
4711
4712           if (!column->resizable)
4713             {
4714               gtk_widget_error_bell (widget);
4715               return TRUE;
4716             }
4717
4718           if (event->keyval == (rtl ? GDK_Right : GDK_Left)
4719               || event->keyval == (rtl ? GDK_KP_Right : GDK_KP_Left))
4720             {
4721               gint old_width = column->resized_width;
4722
4723               column->resized_width = MAX (column->resized_width,
4724                                            column->width);
4725               column->resized_width -= 2;
4726               if (column->resized_width < 0)
4727                 column->resized_width = 0;
4728
4729               if (column->min_width == -1)
4730                 column->resized_width = MAX (column->button_request,
4731                                              column->resized_width);
4732               else
4733                 column->resized_width = MAX (column->min_width,
4734                                              column->resized_width);
4735
4736               if (column->max_width != -1)
4737                 column->resized_width = MIN (column->resized_width,
4738                                              column->max_width);
4739
4740               column->use_resized_width = TRUE;
4741
4742               if (column->resized_width != old_width)
4743                 gtk_widget_queue_resize (widget);
4744               else
4745                 gtk_widget_error_bell (widget);
4746             }
4747           else if (event->keyval == (rtl ? GDK_Left : GDK_Right)
4748                    || event->keyval == (rtl ? GDK_KP_Left : GDK_KP_Right))
4749             {
4750               gint old_width = column->resized_width;
4751
4752               column->resized_width = MAX (column->resized_width,
4753                                            column->width);
4754               column->resized_width += 2;
4755
4756               if (column->max_width != -1)
4757                 column->resized_width = MIN (column->resized_width,
4758                                              column->max_width);
4759
4760               column->use_resized_width = TRUE;
4761
4762               if (column->resized_width != old_width)
4763                 gtk_widget_queue_resize (widget);
4764               else
4765                 gtk_widget_error_bell (widget);
4766             }
4767
4768           return TRUE;
4769         }
4770
4771       if (focus_column &&
4772           (event->state & GDK_MOD1_MASK) &&
4773           (event->keyval == GDK_Left || event->keyval == GDK_KP_Left
4774            || event->keyval == GDK_Right || event->keyval == GDK_KP_Right
4775            || event->keyval == GDK_Home || event->keyval == GDK_KP_Home
4776            || event->keyval == GDK_End || event->keyval == GDK_KP_End))
4777         {
4778           PsppSheetViewColumn *column = PSPP_SHEET_VIEW_COLUMN (focus_column->data);
4779
4780           if (event->keyval == (rtl ? GDK_Right : GDK_Left)
4781               || event->keyval == (rtl ? GDK_KP_Right : GDK_KP_Left))
4782             {
4783               PsppSheetViewColumn *col;
4784               col = pspp_sheet_view_get_drop_column (tree_view, column, DROP_LEFT);
4785               if (col != (PsppSheetViewColumn *)0x1)
4786                 pspp_sheet_view_move_column_after (tree_view, column, col);
4787               else
4788                 gtk_widget_error_bell (widget);
4789             }
4790           else if (event->keyval == (rtl ? GDK_Left : GDK_Right)
4791                    || event->keyval == (rtl ? GDK_KP_Left : GDK_KP_Right))
4792             {
4793               PsppSheetViewColumn *col;
4794               col = pspp_sheet_view_get_drop_column (tree_view, column, DROP_RIGHT);
4795               if (col != (PsppSheetViewColumn *)0x1)
4796                 pspp_sheet_view_move_column_after (tree_view, column, col);
4797               else
4798                 gtk_widget_error_bell (widget);
4799             }
4800           else if (event->keyval == GDK_Home || event->keyval == GDK_KP_Home)
4801             {
4802               PsppSheetViewColumn *col;
4803               col = pspp_sheet_view_get_drop_column (tree_view, column, DROP_HOME);
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_End || event->keyval == GDK_KP_End)
4810             {
4811               PsppSheetViewColumn *col;
4812               col = pspp_sheet_view_get_drop_column (tree_view, column, DROP_END);
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
4819           return TRUE;
4820         }
4821     }
4822
4823   /* Chain up to the parent class.  It handles the keybindings. */
4824   if (GTK_WIDGET_CLASS (pspp_sheet_view_parent_class)->key_press_event (widget, event))
4825     return TRUE;
4826
4827   if (tree_view->priv->search_entry_avoid_unhandled_binding)
4828     {
4829       tree_view->priv->search_entry_avoid_unhandled_binding = FALSE;
4830       return FALSE;
4831     }
4832
4833   /* We pass the event to the search_entry.  If its text changes, then we start
4834    * the typeahead find capabilities. */
4835   if (gtk_widget_has_focus (GTK_WIDGET (tree_view))
4836       && tree_view->priv->enable_search
4837       && !tree_view->priv->search_custom_entry_set)
4838     {
4839       GdkEvent *new_event;
4840       char *old_text;
4841       const char *new_text;
4842       gboolean retval;
4843       GdkScreen *screen;
4844       gboolean text_modified;
4845       gulong popup_menu_id;
4846
4847       pspp_sheet_view_ensure_interactive_directory (tree_view);
4848
4849       /* Make a copy of the current text */
4850       old_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (tree_view->priv->search_entry)));
4851       new_event = gdk_event_copy ((GdkEvent *) event);
4852       g_object_unref (((GdkEventKey *) new_event)->window);
4853       ((GdkEventKey *) new_event)->window = g_object_ref (gtk_widget_get_window (tree_view->priv->search_window));
4854       gtk_widget_realize (tree_view->priv->search_window);
4855
4856       popup_menu_id = g_signal_connect (tree_view->priv->search_entry, 
4857                                         "popup-menu", G_CALLBACK (gtk_true),
4858                                         NULL);
4859
4860       /* Move the entry off screen */
4861       screen = gtk_widget_get_screen (GTK_WIDGET (tree_view));
4862       gtk_window_move (GTK_WINDOW (tree_view->priv->search_window),
4863                        gdk_screen_get_width (screen) + 1,
4864                        gdk_screen_get_height (screen) + 1);
4865       gtk_widget_show (tree_view->priv->search_window);
4866
4867       /* Send the event to the window.  If the preedit_changed signal is emitted
4868        * during this event, we will set priv->imcontext_changed  */
4869       tree_view->priv->imcontext_changed = FALSE;
4870       retval = gtk_widget_event (tree_view->priv->search_window, new_event);
4871       gdk_event_free (new_event);
4872       gtk_widget_hide (tree_view->priv->search_window);
4873
4874       g_signal_handler_disconnect (tree_view->priv->search_entry, 
4875                                    popup_menu_id);
4876
4877       /* We check to make sure that the entry tried to handle the text, and that
4878        * the text has changed.
4879        */
4880       new_text = gtk_entry_get_text (GTK_ENTRY (tree_view->priv->search_entry));
4881       text_modified = strcmp (old_text, new_text) != 0;
4882       g_free (old_text);
4883       if (tree_view->priv->imcontext_changed ||    /* we're in a preedit */
4884           (retval && text_modified))               /* ...or the text was modified */
4885         {
4886           if (pspp_sheet_view_real_start_interactive_search (tree_view, FALSE))
4887             {
4888               gtk_widget_grab_focus (GTK_WIDGET (tree_view));
4889               return TRUE;
4890             }
4891           else
4892             {
4893               gtk_entry_set_text (GTK_ENTRY (tree_view->priv->search_entry), "");
4894               return FALSE;
4895             }
4896         }
4897     }
4898
4899   return FALSE;
4900 }
4901
4902 static gboolean
4903 pspp_sheet_view_key_release (GtkWidget   *widget,
4904                            GdkEventKey *event)
4905 {
4906   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
4907
4908   if (tree_view->priv->rubber_band_status)
4909     return TRUE;
4910
4911   return GTK_WIDGET_CLASS (pspp_sheet_view_parent_class)->key_release_event (widget, event);
4912 }
4913
4914 /* FIXME Is this function necessary? Can I get an enter_notify event
4915  * w/o either an expose event or a mouse motion event?
4916  */
4917 static gboolean
4918 pspp_sheet_view_enter_notify (GtkWidget        *widget,
4919                             GdkEventCrossing *event)
4920 {
4921   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
4922   int node;
4923   gint new_y;
4924
4925   /* Sanity check it */
4926   if (event->window != tree_view->priv->bin_window)
4927     return FALSE;
4928
4929   if (tree_view->priv->row_count == 0)
4930     return FALSE;
4931
4932   if (event->mode == GDK_CROSSING_GRAB ||
4933       event->mode == GDK_CROSSING_GTK_GRAB ||
4934       event->mode == GDK_CROSSING_GTK_UNGRAB ||
4935       event->mode == GDK_CROSSING_STATE_CHANGED)
4936     return TRUE;
4937
4938   /* find the node internally */
4939   new_y = TREE_WINDOW_Y_TO_RBTREE_Y(tree_view, event->y);
4940   if (new_y < 0)
4941     new_y = 0;
4942   pspp_sheet_view_find_offset (tree_view, new_y, &node);
4943
4944   tree_view->priv->event_last_x = event->x;
4945   tree_view->priv->event_last_y = event->y;
4946
4947   prelight_or_select (tree_view, node, event->x, event->y);
4948
4949   return TRUE;
4950 }
4951
4952 static gboolean
4953 pspp_sheet_view_leave_notify (GtkWidget        *widget,
4954                             GdkEventCrossing *event)
4955 {
4956   PsppSheetView *tree_view;
4957
4958   if (event->mode == GDK_CROSSING_GRAB)
4959     return TRUE;
4960
4961   tree_view = PSPP_SHEET_VIEW (widget);
4962
4963   if (tree_view->priv->prelight_node >= 0)
4964     _pspp_sheet_view_queue_draw_node (tree_view,
4965                                    tree_view->priv->prelight_node,
4966                                    NULL);
4967
4968   tree_view->priv->event_last_x = -10000;
4969   tree_view->priv->event_last_y = -10000;
4970
4971   prelight_or_select (tree_view,
4972                       -1,
4973                       -1000, -1000); /* coords not possibly over an arrow */
4974
4975   return TRUE;
4976 }
4977
4978
4979 static gint
4980 pspp_sheet_view_focus_out (GtkWidget     *widget,
4981                          GdkEventFocus *event)
4982 {
4983   PsppSheetView *tree_view;
4984
4985   tree_view = PSPP_SHEET_VIEW (widget);
4986
4987   gtk_widget_queue_draw (widget);
4988
4989   /* destroy interactive search dialog */
4990   if (tree_view->priv->search_window)
4991     pspp_sheet_view_search_dialog_hide (tree_view->priv->search_window, tree_view);
4992
4993   return FALSE;
4994 }
4995
4996
4997 /* Incremental Reflow
4998  */
4999
5000 static void
5001 pspp_sheet_view_node_queue_redraw (PsppSheetView *tree_view,
5002                                  int node)
5003 {
5004   GtkAllocation allocation;
5005   gint y = pspp_sheet_view_node_find_offset (tree_view, node)
5006     - gtk_adjustment_get_value (tree_view->priv->vadjustment)
5007     + TREE_VIEW_HEADER_HEIGHT (tree_view);
5008
5009   gtk_widget_get_allocation (GTK_WIDGET (tree_view), &allocation);
5010
5011   gtk_widget_queue_draw_area (GTK_WIDGET (tree_view),
5012                               0, y,
5013                               allocation.width,
5014                               tree_view->priv->fixed_height);
5015 }
5016
5017 static gboolean
5018 node_is_visible (PsppSheetView *tree_view,
5019                  int node)
5020 {
5021   int y;
5022   int height;
5023
5024   y = pspp_sheet_view_node_find_offset (tree_view, node);
5025   height = ROW_HEIGHT (tree_view);
5026
5027   if (y >= gtk_adjustment_get_value (tree_view->priv->vadjustment) &&
5028       y + height <= (gtk_adjustment_get_value (tree_view->priv->vadjustment)
5029                      + gtk_adjustment_get_page_size (tree_view->priv->vadjustment)))
5030     return TRUE;
5031
5032   return FALSE;
5033 }
5034
5035 /* Returns the row height. */
5036 static gint
5037 validate_row (PsppSheetView *tree_view,
5038               int node,
5039               GtkTreeIter *iter,
5040               GtkTreePath *path)
5041 {
5042   PsppSheetViewColumn *column;
5043   GList *list, *first_column, *last_column;
5044   gint height = 0;
5045   gint horizontal_separator;
5046   gint vertical_separator;
5047   gint focus_line_width;
5048   gboolean draw_vgrid_lines, draw_hgrid_lines;
5049   gint focus_pad;
5050   gint grid_line_width;
5051   gboolean wide_separators;
5052   gint separator_height;
5053
5054   gtk_widget_style_get (GTK_WIDGET (tree_view),
5055                         "focus-padding", &focus_pad,
5056                         "focus-line-width", &focus_line_width,
5057                         "horizontal-separator", &horizontal_separator,
5058                         "vertical-separator", &vertical_separator,
5059                         "grid-line-width", &grid_line_width,
5060                         "wide-separators",  &wide_separators,
5061                         "separator-height", &separator_height,
5062                         NULL);
5063   
5064   draw_vgrid_lines =
5065     tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_VERTICAL
5066     || tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_BOTH;
5067   draw_hgrid_lines =
5068     tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_HORIZONTAL
5069     || tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_BOTH;
5070
5071   for (last_column = g_list_last (tree_view->priv->columns);
5072        last_column && !(PSPP_SHEET_VIEW_COLUMN (last_column->data)->visible);
5073        last_column = last_column->prev)
5074     ;
5075
5076   for (first_column = g_list_first (tree_view->priv->columns);
5077        first_column && !(PSPP_SHEET_VIEW_COLUMN (first_column->data)->visible);
5078        first_column = first_column->next)
5079     ;
5080
5081   for (list = tree_view->priv->columns; list; list = list->next)
5082     {
5083       gint tmp_width;
5084       gint tmp_height;
5085
5086       column = list->data;
5087
5088       if (! column->visible)
5089         continue;
5090
5091       pspp_sheet_view_column_cell_set_cell_data (column, tree_view->priv->model, iter);
5092       pspp_sheet_view_column_cell_get_size (column,
5093                                           NULL, NULL, NULL,
5094                                           &tmp_width, &tmp_height);
5095
5096       tmp_height += vertical_separator;
5097       height = MAX (height, tmp_height);
5098
5099       tmp_width = tmp_width + horizontal_separator;
5100
5101       if (draw_vgrid_lines)
5102         {
5103           if (list->data == first_column || list->data == last_column)
5104             tmp_width += grid_line_width / 2.0;
5105           else
5106             tmp_width += grid_line_width;
5107         }
5108
5109       if (tmp_width > column->requested_width)
5110         column->requested_width = tmp_width;
5111     }
5112
5113   if (draw_hgrid_lines)
5114     height += grid_line_width;
5115
5116   tree_view->priv->post_validation_flag = TRUE;
5117   return height;
5118 }
5119
5120
5121 static void
5122 validate_visible_area (PsppSheetView *tree_view)
5123 {
5124   GtkTreePath *path = NULL;
5125   GtkTreePath *above_path = NULL;
5126   GtkTreeIter iter;
5127   int node = -1;
5128   gint total_height;
5129   gint area_above = 0;
5130   gint area_below = 0;
5131   GtkAllocation allocation;
5132
5133   if (tree_view->priv->row_count == 0)
5134     return;
5135
5136   if (tree_view->priv->scroll_to_path == NULL)
5137     return;
5138
5139   gtk_widget_get_allocation (GTK_WIDGET (tree_view), &allocation);
5140
5141   total_height = allocation.height - TREE_VIEW_HEADER_HEIGHT (tree_view);
5142
5143   if (total_height == 0)
5144     return;
5145
5146   path = gtk_tree_row_reference_get_path (tree_view->priv->scroll_to_path);
5147   if (path)
5148     {
5149       /* we are going to scroll, and will update dy */
5150       _pspp_sheet_view_find_node (tree_view, path, &node);
5151       gtk_tree_model_get_iter (tree_view->priv->model, &iter, path);
5152
5153       if (tree_view->priv->scroll_to_use_align)
5154         {
5155           gint height = ROW_HEIGHT (tree_view);
5156           area_above = (total_height - height) *
5157             tree_view->priv->scroll_to_row_align;
5158           area_below = total_height - area_above - height;
5159           area_above = MAX (area_above, 0);
5160           area_below = MAX (area_below, 0);
5161         }
5162       else
5163         {
5164           /* two cases:
5165            * 1) row not visible
5166            * 2) row visible
5167            */
5168           gint dy;
5169           gint height = ROW_HEIGHT (tree_view);
5170
5171           dy = pspp_sheet_view_node_find_offset (tree_view, node);
5172
5173           if (dy >= gtk_adjustment_get_value (tree_view->priv->vadjustment) &&
5174               dy + height <= (gtk_adjustment_get_value (tree_view->priv->vadjustment)
5175                               + gtk_adjustment_get_page_size (tree_view->priv->vadjustment)))
5176             {
5177               /* row visible: keep the row at the same position */
5178               area_above = dy - gtk_adjustment_get_value (tree_view->priv->vadjustment);
5179               area_below = (gtk_adjustment_get_value (tree_view->priv->vadjustment) +
5180                             gtk_adjustment_get_page_size (tree_view->priv->vadjustment))
5181                 - dy - height;
5182             }
5183           else
5184             {
5185               /* row not visible */
5186               if (dy >= 0
5187                   && dy + height <= gtk_adjustment_get_page_size (tree_view->priv->vadjustment))
5188                 {
5189                   /* row at the beginning -- fixed */
5190                   area_above = dy;
5191                   area_below = gtk_adjustment_get_page_size (tree_view->priv->vadjustment)
5192                     - area_above - height;
5193                 }
5194               else if (dy >= (gtk_adjustment_get_upper (tree_view->priv->vadjustment) -
5195                               gtk_adjustment_get_page_size (tree_view->priv->vadjustment)))
5196                 {
5197                   /* row at the end -- fixed */
5198                   area_above = dy - (gtk_adjustment_get_upper (tree_view->priv->vadjustment) -
5199                                      gtk_adjustment_get_page_size (tree_view->priv->vadjustment));
5200                   area_below = gtk_adjustment_get_page_size (tree_view->priv->vadjustment) -
5201                     area_above - height;
5202
5203                   if (area_below < 0)
5204                     {
5205                       area_above = gtk_adjustment_get_page_size (tree_view->priv->vadjustment) - height;
5206                       area_below = 0;
5207                     }
5208                 }
5209               else
5210                 {
5211                   /* row somewhere in the middle, bring it to the top
5212                    * of the view
5213                    */
5214                   area_above = 0;
5215                   area_below = total_height - height;
5216                 }
5217             }
5218         }
5219     }
5220   else
5221     /* the scroll to isn't valid; ignore it.
5222      */
5223     {
5224       gtk_tree_row_reference_free (tree_view->priv->scroll_to_path);
5225       tree_view->priv->scroll_to_path = NULL;
5226       return;
5227     }
5228
5229   above_path = gtk_tree_path_copy (path);
5230
5231   /* Now, we walk forwards and backwards, measuring rows. Unfortunately,
5232    * backwards is much slower then forward, as there is no iter_prev function.
5233    * We go forwards first in case we run out of tree.  Then we go backwards to
5234    * fill out the top.
5235    */
5236   while (node >= 0 && area_below > 0)
5237     {
5238       gboolean done = FALSE;
5239       do
5240         {
5241           node = pspp_sheet_view_node_next (tree_view, node);
5242           if (node >= 0)
5243             {
5244               gboolean has_next = gtk_tree_model_iter_next (tree_view->priv->model, &iter);
5245               done = TRUE;
5246               gtk_tree_path_next (path);
5247
5248               /* Sanity Check! */
5249               TREE_VIEW_INTERNAL_ASSERT_VOID (has_next);
5250             }
5251           else
5252             break;
5253         }
5254       while (!done);
5255
5256       if (node < 0)
5257         break;
5258
5259       area_below -= ROW_HEIGHT (tree_view);
5260     }
5261   gtk_tree_path_free (path);
5262
5263   /* If we ran out of tree, and have extra area_below left, we need to add it
5264    * to area_above */
5265   if (area_below > 0)
5266     area_above += area_below;
5267
5268   _pspp_sheet_view_find_node (tree_view, above_path, &node);
5269
5270   /* We walk backwards */
5271   while (area_above > 0)
5272     {
5273       node = pspp_sheet_view_node_prev (tree_view, node);
5274
5275       /* Always find the new path in the tree.  We cannot just assume
5276        * a gtk_tree_path_prev() is enough here, as there might be children
5277        * in between this node and the previous sibling node.  If this
5278        * appears to be a performance hotspot in profiles, we can look into
5279        * intrigate logic for keeping path, node and iter in sync like
5280        * we do for forward walks.  (Which will be hard because of the lacking
5281        * iter_prev).
5282        */
5283
5284       if (node < 0)
5285         break;
5286
5287       gtk_tree_path_free (above_path);
5288       above_path = _pspp_sheet_view_find_path (tree_view, node);
5289
5290       gtk_tree_model_get_iter (tree_view->priv->model, &iter, above_path);
5291
5292       area_above -= ROW_HEIGHT (tree_view);
5293     }
5294
5295   /* set the dy here to scroll to the path,
5296    * and sync the top row accordingly
5297    */
5298   pspp_sheet_view_set_top_row (tree_view, above_path, -area_above);
5299   pspp_sheet_view_top_row_to_dy (tree_view);
5300
5301   gtk_tree_row_reference_free (tree_view->priv->scroll_to_path);
5302   tree_view->priv->scroll_to_path = NULL;
5303
5304   if (above_path)
5305     gtk_tree_path_free (above_path);
5306
5307   if (tree_view->priv->scroll_to_column)
5308     {
5309       tree_view->priv->scroll_to_column = NULL;
5310     }
5311   gtk_widget_queue_draw (GTK_WIDGET (tree_view));
5312 }
5313
5314 static void
5315 initialize_fixed_height_mode (PsppSheetView *tree_view)
5316 {
5317   if (!tree_view->priv->row_count)
5318     return;
5319
5320   if (tree_view->priv->fixed_height_set)
5321     return;
5322
5323   if (tree_view->priv->fixed_height < 0)
5324     {
5325       GtkTreeIter iter;
5326       GtkTreePath *path;
5327
5328       int node = 0;
5329
5330       path = _pspp_sheet_view_find_path (tree_view, node);
5331       gtk_tree_model_get_iter (tree_view->priv->model, &iter, path);
5332
5333       tree_view->priv->fixed_height = validate_row (tree_view, node, &iter, path);
5334
5335       gtk_tree_path_free (path);
5336
5337       g_object_notify (G_OBJECT (tree_view), "fixed-height");
5338     }
5339 }
5340
5341 /* Our strategy for finding nodes to validate is a little convoluted.  We find
5342  * the left-most uninvalidated node.  We then try walking right, validating
5343  * nodes.  Once we find a valid node, we repeat the previous process of finding
5344  * the first invalid node.
5345  */
5346
5347 static gboolean
5348 validate_rows_handler (PsppSheetView *tree_view)
5349 {
5350   initialize_fixed_height_mode (tree_view);
5351   if (tree_view->priv->validate_rows_timer)
5352     {
5353       g_source_remove (tree_view->priv->validate_rows_timer);
5354       tree_view->priv->validate_rows_timer = 0;
5355     }
5356
5357   return FALSE;
5358 }
5359
5360 static gboolean
5361 do_presize_handler (PsppSheetView *tree_view)
5362 {
5363   GtkRequisition requisition;
5364
5365   validate_visible_area (tree_view);
5366   tree_view->priv->presize_handler_timer = 0;
5367
5368   if (! gtk_widget_get_realized (GTK_WIDGET (tree_view)))
5369     return FALSE;
5370
5371   gtk_widget_get_preferred_size (GTK_WIDGET (tree_view), NULL, &requisition);
5372
5373   gtk_adjustment_set_upper (tree_view->priv->hadjustment, MAX (gtk_adjustment_get_upper (tree_view->priv->hadjustment), (gfloat)requisition.width));
5374   gtk_adjustment_set_upper (tree_view->priv->vadjustment, MAX (gtk_adjustment_get_upper (tree_view->priv->vadjustment), (gfloat)requisition.height));
5375   gtk_adjustment_changed (tree_view->priv->hadjustment);
5376   gtk_adjustment_changed (tree_view->priv->vadjustment);
5377   gtk_widget_queue_resize (GTK_WIDGET (tree_view));
5378                    
5379   return FALSE;
5380 }
5381
5382 static gboolean
5383 presize_handler_callback (gpointer data)
5384 {
5385   do_presize_handler (PSPP_SHEET_VIEW (data));
5386                    
5387   return FALSE;
5388 }
5389
5390 static void
5391 install_presize_handler (PsppSheetView *tree_view)
5392 {
5393   if (! gtk_widget_get_realized (GTK_WIDGET (tree_view)))
5394     return;
5395
5396   if (! tree_view->priv->presize_handler_timer)
5397     {
5398       tree_view->priv->presize_handler_timer =
5399         gdk_threads_add_idle_full (GTK_PRIORITY_RESIZE - 2, presize_handler_callback, tree_view, NULL);
5400     }
5401   if (! tree_view->priv->validate_rows_timer)
5402     {
5403       tree_view->priv->validate_rows_timer =
5404         gdk_threads_add_idle_full (PSPP_SHEET_VIEW_PRIORITY_VALIDATE, (GSourceFunc) validate_rows_handler, tree_view, NULL);
5405     }
5406 }
5407
5408 static gboolean
5409 scroll_sync_handler (PsppSheetView *tree_view)
5410 {
5411   if (tree_view->priv->height <= gtk_adjustment_get_page_size (tree_view->priv->vadjustment))
5412     gtk_adjustment_set_value (GTK_ADJUSTMENT (tree_view->priv->vadjustment), 0);
5413   else if (gtk_tree_row_reference_valid (tree_view->priv->top_row))
5414     pspp_sheet_view_top_row_to_dy (tree_view);
5415   else
5416     pspp_sheet_view_dy_to_top_row (tree_view);
5417
5418   tree_view->priv->scroll_sync_timer = 0;
5419
5420   return FALSE;
5421 }
5422
5423 static void
5424 install_scroll_sync_handler (PsppSheetView *tree_view)
5425 {
5426   if (!gtk_widget_get_realized (GTK_WIDGET (tree_view)))
5427     return;
5428
5429   if (!tree_view->priv->scroll_sync_timer)
5430     {
5431       tree_view->priv->scroll_sync_timer =
5432         gdk_threads_add_idle_full (PSPP_SHEET_VIEW_PRIORITY_SCROLL_SYNC, (GSourceFunc) scroll_sync_handler, tree_view, NULL);
5433     }
5434 }
5435
5436 static void
5437 pspp_sheet_view_set_top_row (PsppSheetView *tree_view,
5438                            GtkTreePath *path,
5439                            gint         offset)
5440 {
5441   gtk_tree_row_reference_free (tree_view->priv->top_row);
5442
5443   if (!path)
5444     {
5445       tree_view->priv->top_row = NULL;
5446       tree_view->priv->top_row_dy = 0;
5447     }
5448   else
5449     {
5450       tree_view->priv->top_row = gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view), tree_view->priv->model, path);
5451       tree_view->priv->top_row_dy = offset;
5452     }
5453 }
5454
5455 /* Always call this iff dy is in the visible range.  If the tree is empty, then
5456  * it's set to be NULL, and top_row_dy is 0;
5457  */
5458 static void
5459 pspp_sheet_view_dy_to_top_row (PsppSheetView *tree_view)
5460 {
5461   gint offset;
5462   GtkTreePath *path;
5463   int node;
5464
5465   if (tree_view->priv->row_count == 0)
5466     {
5467       pspp_sheet_view_set_top_row (tree_view, NULL, 0);
5468     }
5469   else
5470     {
5471       offset = pspp_sheet_view_find_offset (tree_view,
5472                                             tree_view->priv->dy,
5473                                             &node);
5474
5475       if (node < 0)
5476         {
5477           pspp_sheet_view_set_top_row (tree_view, NULL, 0);
5478         }
5479       else
5480         {
5481           path = _pspp_sheet_view_find_path (tree_view, node);
5482           pspp_sheet_view_set_top_row (tree_view, path, offset);
5483           gtk_tree_path_free (path);
5484         }
5485     }
5486 }
5487
5488 static void
5489 pspp_sheet_view_top_row_to_dy (PsppSheetView *tree_view)
5490 {
5491   GtkTreePath *path;
5492   int node;
5493   int new_dy;
5494
5495   /* Avoid recursive calls */
5496   if (tree_view->priv->in_top_row_to_dy)
5497     return;
5498
5499   if (tree_view->priv->top_row)
5500     path = gtk_tree_row_reference_get_path (tree_view->priv->top_row);
5501   else
5502     path = NULL;
5503
5504   if (!path)
5505     node = -1;
5506   else
5507     _pspp_sheet_view_find_node (tree_view, path, &node);
5508
5509   if (path)
5510     gtk_tree_path_free (path);
5511
5512   if (node < 0)
5513     {
5514       /* keep dy and set new toprow */
5515       gtk_tree_row_reference_free (tree_view->priv->top_row);
5516       tree_view->priv->top_row = NULL;
5517       tree_view->priv->top_row_dy = 0;
5518       /* DO NOT install the idle handler */
5519       pspp_sheet_view_dy_to_top_row (tree_view);
5520       return;
5521     }
5522
5523   if (ROW_HEIGHT (tree_view) < tree_view->priv->top_row_dy)
5524     {
5525       /* new top row -- do NOT install the idle handler */
5526       pspp_sheet_view_dy_to_top_row (tree_view);
5527       return;
5528     }
5529
5530   new_dy = pspp_sheet_view_node_find_offset (tree_view, node);
5531   new_dy += tree_view->priv->top_row_dy;
5532
5533   if (new_dy + gtk_adjustment_get_page_size (tree_view->priv->vadjustment) > tree_view->priv->height)
5534     new_dy = tree_view->priv->height - gtk_adjustment_get_page_size (tree_view->priv->vadjustment);
5535
5536   new_dy = MAX (0, new_dy);
5537
5538   tree_view->priv->in_top_row_to_dy = TRUE;
5539   gtk_adjustment_set_value (tree_view->priv->vadjustment, (gdouble)new_dy);
5540   tree_view->priv->in_top_row_to_dy = FALSE;
5541 }
5542
5543
5544 void
5545 _pspp_sheet_view_install_mark_rows_col_dirty (PsppSheetView *tree_view)
5546 {
5547   install_presize_handler (tree_view);
5548 }
5549
5550 /* Drag-and-drop */
5551
5552 static void
5553 set_source_row (GdkDragContext *context,
5554                 GtkTreeModel   *model,
5555                 GtkTreePath    *source_row)
5556 {
5557   g_object_set_data_full (G_OBJECT (context),
5558                           "gtk-tree-view-source-row",
5559                           source_row ? gtk_tree_row_reference_new (model, source_row) : NULL,
5560                           (GDestroyNotify) (source_row ? gtk_tree_row_reference_free : NULL));
5561 }
5562
5563 static GtkTreePath*
5564 get_source_row (GdkDragContext *context)
5565 {
5566   GtkTreeRowReference *ref =
5567     g_object_get_data (G_OBJECT (context), "gtk-tree-view-source-row");
5568
5569   if (ref)
5570     return gtk_tree_row_reference_get_path (ref);
5571   else
5572     return NULL;
5573 }
5574
5575 typedef struct
5576 {
5577   GtkTreeRowReference *dest_row;
5578   guint                path_down_mode   : 1;
5579   guint                empty_view_drop  : 1;
5580   guint                drop_append_mode : 1;
5581 }
5582 DestRow;
5583
5584 static void
5585 dest_row_free (gpointer data)
5586 {
5587   DestRow *dr = (DestRow *)data;
5588
5589   gtk_tree_row_reference_free (dr->dest_row);
5590   g_slice_free (DestRow, dr);
5591 }
5592
5593 static void
5594 set_dest_row (GdkDragContext *context,
5595               GtkTreeModel   *model,
5596               GtkTreePath    *dest_row,
5597               gboolean        path_down_mode,
5598               gboolean        empty_view_drop,
5599               gboolean        drop_append_mode)
5600 {
5601   DestRow *dr;
5602
5603   if (!dest_row)
5604     {
5605       g_object_set_data_full (G_OBJECT (context), "gtk-tree-view-dest-row",
5606                               NULL, NULL);
5607       return;
5608     }
5609
5610   dr = g_slice_new (DestRow);
5611
5612   dr->dest_row = gtk_tree_row_reference_new (model, dest_row);
5613   dr->path_down_mode = path_down_mode != FALSE;
5614   dr->empty_view_drop = empty_view_drop != FALSE;
5615   dr->drop_append_mode = drop_append_mode != FALSE;
5616
5617   g_object_set_data_full (G_OBJECT (context), "gtk-tree-view-dest-row",
5618                           dr, (GDestroyNotify) dest_row_free);
5619 }
5620
5621 static GtkTreePath*
5622 get_dest_row (GdkDragContext *context,
5623               gboolean       *path_down_mode)
5624 {
5625   DestRow *dr =
5626     g_object_get_data (G_OBJECT (context), "gtk-tree-view-dest-row");
5627
5628   if (dr)
5629     {
5630       GtkTreePath *path = NULL;
5631
5632       if (path_down_mode)
5633         *path_down_mode = dr->path_down_mode;
5634
5635       if (dr->dest_row)
5636         path = gtk_tree_row_reference_get_path (dr->dest_row);
5637       else if (dr->empty_view_drop)
5638         path = gtk_tree_path_new_from_indices (0, -1);
5639       else
5640         path = NULL;
5641
5642       if (path && dr->drop_append_mode)
5643         gtk_tree_path_next (path);
5644
5645       return path;
5646     }
5647   else
5648     return NULL;
5649 }
5650
5651 /* Get/set whether drag_motion requested the drag data and
5652  * drag_data_received should thus not actually insert the data,
5653  * since the data doesn't result from a drop.
5654  */
5655 static void
5656 set_status_pending (GdkDragContext *context,
5657                     GdkDragAction   suggested_action)
5658 {
5659   g_object_set_data (G_OBJECT (context),
5660                      "gtk-tree-view-status-pending",
5661                      GINT_TO_POINTER (suggested_action));
5662 }
5663
5664 static GdkDragAction
5665 get_status_pending (GdkDragContext *context)
5666 {
5667   return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (context),
5668                                              "gtk-tree-view-status-pending"));
5669 }
5670
5671 static TreeViewDragInfo*
5672 get_info (PsppSheetView *tree_view)
5673 {
5674   return g_object_get_data (G_OBJECT (tree_view), "gtk-tree-view-drag-info");
5675 }
5676
5677 static void
5678 destroy_info (TreeViewDragInfo *di)
5679 {
5680   g_slice_free (TreeViewDragInfo, di);
5681 }
5682
5683 static TreeViewDragInfo*
5684 ensure_info (PsppSheetView *tree_view)
5685 {
5686   TreeViewDragInfo *di;
5687
5688   di = get_info (tree_view);
5689
5690   if (di == NULL)
5691     {
5692       di = g_slice_new0 (TreeViewDragInfo);
5693
5694       g_object_set_data_full (G_OBJECT (tree_view),
5695                               "gtk-tree-view-drag-info",
5696                               di,
5697                               (GDestroyNotify) destroy_info);
5698     }
5699
5700   return di;
5701 }
5702
5703 static void
5704 remove_info (PsppSheetView *tree_view)
5705 {
5706   g_object_set_data (G_OBJECT (tree_view), "gtk-tree-view-drag-info", NULL);
5707 }
5708
5709 #if 0
5710 static gint
5711 drag_scan_timeout (gpointer data)
5712 {
5713   PsppSheetView *tree_view;
5714   gint x, y;
5715   GdkModifierType state;
5716   GtkTreePath *path = NULL;
5717   PsppSheetViewColumn *column = NULL;
5718   GdkRectangle visible_rect;
5719
5720   GDK_THREADS_ENTER ();
5721
5722   tree_view = PSPP_SHEET_VIEW (data);
5723
5724   gdk_window_get_pointer (tree_view->priv->bin_window,
5725                           &x, &y, &state);
5726
5727   pspp_sheet_view_get_visible_rect (tree_view, &visible_rect);
5728
5729   /* See if we are near the edge. */
5730   if ((x - visible_rect.x) < SCROLL_EDGE_SIZE ||
5731       (visible_rect.x + visible_rect.width - x) < SCROLL_EDGE_SIZE ||
5732       (y - visible_rect.y) < SCROLL_EDGE_SIZE ||
5733       (visible_rect.y + visible_rect.height - y) < SCROLL_EDGE_SIZE)
5734     {
5735       pspp_sheet_view_get_path_at_pos (tree_view,
5736                                      tree_view->priv->bin_window,
5737                                      x, y,
5738                                      &path,
5739                                      &column,
5740                                      NULL,
5741                                      NULL);
5742
5743       if (path != NULL)
5744         {
5745           pspp_sheet_view_scroll_to_cell (tree_view,
5746                                         path,
5747                                         column,
5748                                         TRUE,
5749                                         0.5, 0.5);
5750
5751           gtk_tree_path_free (path);
5752         }
5753     }
5754
5755   GDK_THREADS_LEAVE ();
5756
5757   return TRUE;
5758 }
5759 #endif /* 0 */
5760
5761 static void
5762 add_scroll_timeout (PsppSheetView *tree_view)
5763 {
5764   if (tree_view->priv->scroll_timeout == 0)
5765     {
5766       tree_view->priv->scroll_timeout =
5767         gdk_threads_add_timeout (150, scroll_row_timeout, tree_view);
5768     }
5769 }
5770
5771 static void
5772 remove_scroll_timeout (PsppSheetView *tree_view)
5773 {
5774   if (tree_view->priv->scroll_timeout != 0)
5775     {
5776       g_source_remove (tree_view->priv->scroll_timeout);
5777       tree_view->priv->scroll_timeout = 0;
5778     }
5779 }
5780
5781 static gboolean
5782 check_model_dnd (GtkTreeModel *model,
5783                  GType         required_iface,
5784                  const gchar  *signal)
5785 {
5786   if (model == NULL || !G_TYPE_CHECK_INSTANCE_TYPE ((model), required_iface))
5787     {
5788       g_warning ("You must override the default '%s' handler "
5789                  "on PsppSheetView when using models that don't support "
5790                  "the %s interface and enabling drag-and-drop. The simplest way to do this "
5791                  "is to connect to '%s' and call "
5792                  "g_signal_stop_emission_by_name() in your signal handler to prevent "
5793                  "the default handler from running. Look at the source code "
5794                  "for the default handler in gtktreeview.c to get an idea what "
5795                  "your handler should do. (gtktreeview.c is in the GTK source "
5796                  "code.) If you're using GTK from a language other than C, "
5797                  "there may be a more natural way to override default handlers, e.g. via derivation.",
5798                  signal, g_type_name (required_iface), signal);
5799       return FALSE;
5800     }
5801   else
5802     return TRUE;
5803 }
5804
5805 static gboolean
5806 scroll_row_timeout (gpointer data)
5807 {
5808   PsppSheetView *tree_view = data;
5809
5810   pspp_sheet_view_horizontal_autoscroll (tree_view);
5811   pspp_sheet_view_vertical_autoscroll (tree_view);
5812
5813   if (tree_view->priv->rubber_band_status == RUBBER_BAND_ACTIVE)
5814     pspp_sheet_view_update_rubber_band (tree_view);
5815
5816   return TRUE;
5817 }
5818
5819 /* Returns TRUE if event should not be propagated to parent widgets */
5820 static gboolean
5821 set_destination_row (PsppSheetView    *tree_view,
5822                      GdkDragContext *context,
5823                      /* coordinates relative to the widget */
5824                      gint            x,
5825                      gint            y,
5826                      GdkDragAction  *suggested_action,
5827                      GdkAtom        *target)
5828 {
5829   GtkTreePath *path = NULL;
5830   PsppSheetViewDropPosition pos;
5831   PsppSheetViewDropPosition old_pos;
5832   TreeViewDragInfo *di;
5833   GtkWidget *widget;
5834   GtkTreePath *old_dest_path = NULL;
5835   gboolean can_drop = FALSE;
5836
5837   *suggested_action = 0;
5838   *target = GDK_NONE;
5839
5840   widget = GTK_WIDGET (tree_view);
5841
5842   di = get_info (tree_view);
5843
5844   if (di == NULL || y - TREE_VIEW_HEADER_HEIGHT (tree_view) < 0)
5845     {
5846       /* someone unset us as a drag dest, note that if
5847        * we return FALSE drag_leave isn't called
5848        */
5849
5850       pspp_sheet_view_set_drag_dest_row (tree_view,
5851                                        NULL,
5852                                        PSPP_SHEET_VIEW_DROP_BEFORE);
5853
5854       remove_scroll_timeout (PSPP_SHEET_VIEW (widget));
5855
5856       return FALSE; /* no longer a drop site */
5857     }
5858
5859   *target = gtk_drag_dest_find_target (widget, context,
5860                                        gtk_drag_dest_get_target_list (widget));
5861   if (*target == GDK_NONE)
5862     {
5863       return FALSE;
5864     }
5865
5866   if (!pspp_sheet_view_get_dest_row_at_pos (tree_view,
5867                                           x, y,
5868                                           &path,
5869                                           &pos))
5870     {
5871       gint n_children;
5872       GtkTreeModel *model;
5873
5874       /* the row got dropped on empty space, let's setup a special case
5875        */
5876
5877       if (path)
5878         gtk_tree_path_free (path);
5879
5880       model = pspp_sheet_view_get_model (tree_view);
5881
5882       n_children = gtk_tree_model_iter_n_children (model, NULL);
5883       if (n_children)
5884         {
5885           pos = PSPP_SHEET_VIEW_DROP_AFTER;
5886           path = gtk_tree_path_new_from_indices (n_children - 1, -1);
5887         }
5888       else
5889         {
5890           pos = PSPP_SHEET_VIEW_DROP_BEFORE;
5891           path = gtk_tree_path_new_from_indices (0, -1);
5892         }
5893
5894       can_drop = TRUE;
5895
5896       goto out;
5897     }
5898
5899   g_assert (path);
5900
5901   /* If we left the current row's "open" zone, unset the timeout for
5902    * opening the row
5903    */
5904   pspp_sheet_view_get_drag_dest_row (tree_view,
5905                                    &old_dest_path,
5906                                    &old_pos);
5907
5908   if (old_dest_path)
5909     gtk_tree_path_free (old_dest_path);
5910
5911   if (TRUE /* FIXME if the location droppable predicate */)
5912     {
5913       can_drop = TRUE;
5914     }
5915
5916 out:
5917   if (can_drop)
5918     {
5919       GtkWidget *source_widget;
5920
5921       *suggested_action = gdk_drag_context_get_suggested_action (context);
5922       source_widget = gtk_drag_get_source_widget (context);
5923
5924       if (source_widget == widget)
5925         {
5926           /* Default to MOVE, unless the user has
5927            * pressed ctrl or shift to affect available actions
5928            */
5929           if ((gdk_drag_context_get_actions (context) & GDK_ACTION_MOVE) != 0)
5930             *suggested_action = GDK_ACTION_MOVE;
5931         }
5932
5933       pspp_sheet_view_set_drag_dest_row (PSPP_SHEET_VIEW (widget),
5934                                        path, pos);
5935     }
5936   else
5937     {
5938       /* can't drop here */
5939       pspp_sheet_view_set_drag_dest_row (PSPP_SHEET_VIEW (widget),
5940                                        NULL,
5941                                        PSPP_SHEET_VIEW_DROP_BEFORE);
5942     }
5943
5944   if (path)
5945     gtk_tree_path_free (path);
5946
5947   return TRUE;
5948 }
5949
5950 static GtkTreePath*
5951 get_logical_dest_row (PsppSheetView *tree_view,
5952                       gboolean    *path_down_mode,
5953                       gboolean    *drop_append_mode)
5954 {
5955   /* adjust path to point to the row the drop goes in front of */
5956   GtkTreePath *path = NULL;
5957   PsppSheetViewDropPosition pos;
5958
5959   g_return_val_if_fail (path_down_mode != NULL, NULL);
5960   g_return_val_if_fail (drop_append_mode != NULL, NULL);
5961
5962   *path_down_mode = FALSE;
5963   *drop_append_mode = 0;
5964
5965   pspp_sheet_view_get_drag_dest_row (tree_view, &path, &pos);
5966
5967   if (path == NULL)
5968     return NULL;
5969
5970   if (pos == PSPP_SHEET_VIEW_DROP_BEFORE)
5971     ; /* do nothing */
5972   else if (pos == PSPP_SHEET_VIEW_DROP_INTO_OR_BEFORE ||
5973            pos == PSPP_SHEET_VIEW_DROP_INTO_OR_AFTER)
5974     *path_down_mode = TRUE;
5975   else
5976     {
5977       GtkTreeIter iter;
5978       GtkTreeModel *model = pspp_sheet_view_get_model (tree_view);
5979
5980       g_assert (pos == PSPP_SHEET_VIEW_DROP_AFTER);
5981
5982       if (!gtk_tree_model_get_iter (model, &iter, path) ||
5983           !gtk_tree_model_iter_next (model, &iter))
5984         *drop_append_mode = 1;
5985       else
5986         {
5987           *drop_append_mode = 0;
5988           gtk_tree_path_next (path);
5989         }
5990     }
5991
5992   return path;
5993 }
5994
5995 static gboolean
5996 pspp_sheet_view_maybe_begin_dragging_row (PsppSheetView      *tree_view,
5997                                         GdkEventMotion   *event)
5998 {
5999   GtkWidget *widget = GTK_WIDGET (tree_view);
6000   GdkDragContext *context;
6001   TreeViewDragInfo *di;
6002   GtkTreePath *path = NULL;
6003   gint button;
6004   gint cell_x, cell_y;
6005   GtkTreeModel *model;
6006   gboolean retval = FALSE;
6007
6008   di = get_info (tree_view);
6009
6010   if (di == NULL || !di->source_set)
6011     goto out;
6012
6013   if (tree_view->priv->pressed_button < 0)
6014     goto out;
6015
6016   if (!gtk_drag_check_threshold (widget,
6017                                  tree_view->priv->press_start_x,
6018                                  tree_view->priv->press_start_y,
6019                                  event->x, event->y))
6020     goto out;
6021
6022   model = pspp_sheet_view_get_model (tree_view);
6023
6024   if (model == NULL)
6025     goto out;
6026
6027   button = tree_view->priv->pressed_button;
6028   tree_view->priv->pressed_button = -1;
6029
6030   pspp_sheet_view_get_path_at_pos (tree_view,
6031                                  tree_view->priv->press_start_x,
6032                                  tree_view->priv->press_start_y,
6033                                  &path,
6034                                  NULL,
6035                                  &cell_x,
6036                                  &cell_y);
6037
6038   if (path == NULL)
6039     goto out;
6040
6041   if (!GTK_IS_TREE_DRAG_SOURCE (model) ||
6042       !gtk_tree_drag_source_row_draggable (GTK_TREE_DRAG_SOURCE (model),
6043                                            path))
6044     goto out;
6045
6046   if (!(GDK_BUTTON1_MASK << (button - 1) & di->start_button_mask))
6047     goto out;
6048
6049   /* Now we can begin the drag */
6050
6051   retval = TRUE;
6052
6053   context = gtk_drag_begin (widget,
6054                             gtk_drag_source_get_target_list (widget),
6055                             di->source_actions,
6056                             button,
6057                             (GdkEvent*)event);
6058
6059   set_source_row (context, model, path);
6060
6061  out:
6062   if (path)
6063     gtk_tree_path_free (path);
6064
6065   return retval;
6066 }
6067
6068
6069
6070 static void
6071 pspp_sheet_view_drag_begin (GtkWidget      *widget,
6072                           GdkDragContext *context)
6073 {
6074 #if GTK3_TRANSITION
6075   PsppSheetView *tree_view;
6076   GtkTreePath *path = NULL;
6077   gint cell_x, cell_y;
6078   GdkPixmap *row_pix;
6079   TreeViewDragInfo *di;
6080
6081   tree_view = PSPP_SHEET_VIEW (widget);
6082
6083   /* if the user uses a custom DND source impl, we don't set the icon here */
6084   di = get_info (tree_view);
6085
6086   if (di == NULL || !di->source_set)
6087     return;
6088
6089   pspp_sheet_view_get_path_at_pos (tree_view,
6090                                  tree_view->priv->press_start_x,
6091                                  tree_view->priv->press_start_y,
6092                                  &path,
6093                                  NULL,
6094                                  &cell_x,
6095                                  &cell_y);
6096
6097   g_return_if_fail (path != NULL);
6098
6099   row_pix = pspp_sheet_view_create_row_drag_icon (tree_view,
6100                                                 path);
6101
6102   gtk_drag_set_icon_pixmap (context,
6103                             gdk_drawable_get_colormap (row_pix),
6104                             row_pix,
6105                             NULL,
6106                             /* the + 1 is for the black border in the icon */
6107                             tree_view->priv->press_start_x + 1,
6108                             cell_y + 1);
6109
6110   g_object_unref (row_pix);
6111   gtk_tree_path_free (path);
6112 #endif
6113 }
6114
6115
6116 static void
6117 pspp_sheet_view_drag_end (GtkWidget      *widget,
6118                         GdkDragContext *context)
6119 {
6120   /* do nothing */
6121 }
6122
6123 /* Default signal implementations for the drag signals */
6124 static void
6125 pspp_sheet_view_drag_data_get (GtkWidget        *widget,
6126                              GdkDragContext   *context,
6127                              GtkSelectionData *selection_data,
6128                              guint             info,
6129                              guint             time)
6130 {
6131   PsppSheetView *tree_view;
6132   GtkTreeModel *model;
6133   TreeViewDragInfo *di;
6134   GtkTreePath *source_row;
6135
6136   tree_view = PSPP_SHEET_VIEW (widget);
6137
6138   model = pspp_sheet_view_get_model (tree_view);
6139
6140   if (model == NULL)
6141     return;
6142
6143   di = get_info (PSPP_SHEET_VIEW (widget));
6144
6145   if (di == NULL)
6146     return;
6147
6148   source_row = get_source_row (context);
6149
6150   if (source_row == NULL)
6151     return;
6152
6153   /* We can implement the GTK_TREE_MODEL_ROW target generically for
6154    * any model; for DragSource models there are some other targets
6155    * we also support.
6156    */
6157
6158   if (GTK_IS_TREE_DRAG_SOURCE (model) &&
6159       gtk_tree_drag_source_drag_data_get (GTK_TREE_DRAG_SOURCE (model),
6160                                           source_row,
6161                                           selection_data))
6162     goto done;
6163
6164   /* If drag_data_get does nothing, try providing row data. */
6165   if (gtk_selection_data_get_target (selection_data) == gdk_atom_intern_static_string ("GTK_TREE_MODEL_ROW"))
6166     {
6167       gtk_tree_set_row_drag_data (selection_data,
6168                                   model,
6169                                   source_row);
6170     }
6171
6172  done:
6173   gtk_tree_path_free (source_row);
6174 }
6175
6176
6177 static void
6178 pspp_sheet_view_drag_data_delete (GtkWidget      *widget,
6179                                 GdkDragContext *context)
6180 {
6181   TreeViewDragInfo *di;
6182   GtkTreeModel *model;
6183   PsppSheetView *tree_view;
6184   GtkTreePath *source_row;
6185
6186   tree_view = PSPP_SHEET_VIEW (widget);
6187   model = pspp_sheet_view_get_model (tree_view);
6188
6189   if (!check_model_dnd (model, GTK_TYPE_TREE_DRAG_SOURCE, "drag_data_delete"))
6190     return;
6191
6192   di = get_info (tree_view);
6193
6194   if (di == NULL)
6195     return;
6196
6197   source_row = get_source_row (context);
6198
6199   if (source_row == NULL)
6200     return;
6201
6202   gtk_tree_drag_source_drag_data_delete (GTK_TREE_DRAG_SOURCE (model),
6203                                          source_row);
6204
6205   gtk_tree_path_free (source_row);
6206
6207   set_source_row (context, NULL, NULL);
6208 }
6209
6210 static void
6211 pspp_sheet_view_drag_leave (GtkWidget      *widget,
6212                           GdkDragContext *context,
6213                           guint             time)
6214 {
6215   /* unset any highlight row */
6216   pspp_sheet_view_set_drag_dest_row (PSPP_SHEET_VIEW (widget),
6217                                    NULL,
6218                                    PSPP_SHEET_VIEW_DROP_BEFORE);
6219
6220   remove_scroll_timeout (PSPP_SHEET_VIEW (widget));
6221 }
6222
6223
6224 static gboolean
6225 pspp_sheet_view_drag_motion (GtkWidget        *widget,
6226                            GdkDragContext   *context,
6227                            /* coordinates relative to the widget */
6228                            gint              x,
6229                            gint              y,
6230                            guint             time)
6231 {
6232   gboolean empty;
6233   GtkTreePath *path = NULL;
6234   PsppSheetViewDropPosition pos;
6235   PsppSheetView *tree_view;
6236   GdkDragAction suggested_action = 0;
6237   GdkAtom target;
6238
6239   tree_view = PSPP_SHEET_VIEW (widget);
6240
6241   if (!set_destination_row (tree_view, context, x, y, &suggested_action, &target))
6242     return FALSE;
6243
6244   pspp_sheet_view_get_drag_dest_row (tree_view, &path, &pos);
6245
6246   /* we only know this *after* set_desination_row */
6247   empty = tree_view->priv->empty_view_drop;
6248
6249   if (path == NULL && !empty)
6250     {
6251       /* Can't drop here. */
6252       gdk_drag_status (context, 0, time);
6253     }
6254   else
6255     {
6256       if (tree_view->priv->open_dest_timeout == 0 &&
6257           (pos == PSPP_SHEET_VIEW_DROP_INTO_OR_AFTER ||
6258            pos == PSPP_SHEET_VIEW_DROP_INTO_OR_BEFORE))
6259         {
6260           /* Nothing. */
6261         }
6262       else
6263         {
6264           add_scroll_timeout (tree_view);
6265         }
6266
6267       if (target == gdk_atom_intern_static_string ("GTK_TREE_MODEL_ROW"))
6268         {
6269           /* Request data so we can use the source row when
6270            * determining whether to accept the drop
6271            */
6272           set_status_pending (context, suggested_action);
6273           gtk_drag_get_data (widget, context, target, time);
6274         }
6275       else
6276         {
6277           set_status_pending (context, 0);
6278           gdk_drag_status (context, suggested_action, time);
6279         }
6280     }
6281
6282   if (path)
6283     gtk_tree_path_free (path);
6284
6285   return TRUE;
6286 }
6287
6288
6289 static gboolean
6290 pspp_sheet_view_drag_drop (GtkWidget        *widget,
6291                          GdkDragContext   *context,
6292                          /* coordinates relative to the widget */
6293                          gint              x,
6294                          gint              y,
6295                          guint             time)
6296 {
6297   PsppSheetView *tree_view;
6298   GtkTreePath *path;
6299   GdkDragAction suggested_action = 0;
6300   GdkAtom target = GDK_NONE;
6301   TreeViewDragInfo *di;
6302   GtkTreeModel *model;
6303   gboolean path_down_mode;
6304   gboolean drop_append_mode;
6305
6306   tree_view = PSPP_SHEET_VIEW (widget);
6307
6308   model = pspp_sheet_view_get_model (tree_view);
6309
6310   remove_scroll_timeout (PSPP_SHEET_VIEW (widget));
6311
6312   di = get_info (tree_view);
6313
6314   if (di == NULL)
6315     return FALSE;
6316
6317   if (!check_model_dnd (model, GTK_TYPE_TREE_DRAG_DEST, "drag_drop"))
6318     return FALSE;
6319
6320   if (!set_destination_row (tree_view, context, x, y, &suggested_action, &target))
6321     return FALSE;
6322
6323   path = get_logical_dest_row (tree_view, &path_down_mode, &drop_append_mode);
6324
6325   if (target != GDK_NONE && path != NULL)
6326     {
6327       /* in case a motion had requested drag data, change things so we
6328        * treat drag data receives as a drop.
6329        */
6330       set_status_pending (context, 0);
6331       set_dest_row (context, model, path,
6332                     path_down_mode, tree_view->priv->empty_view_drop,
6333                     drop_append_mode);
6334     }
6335
6336   if (path)
6337     gtk_tree_path_free (path);
6338
6339   /* Unset this thing */
6340   pspp_sheet_view_set_drag_dest_row (PSPP_SHEET_VIEW (widget),
6341                                    NULL,
6342                                    PSPP_SHEET_VIEW_DROP_BEFORE);
6343
6344   if (target != GDK_NONE)
6345     {
6346       gtk_drag_get_data (widget, context, target, time);
6347       return TRUE;
6348     }
6349   else
6350     return FALSE;
6351 }
6352
6353 static void
6354 pspp_sheet_view_drag_data_received (GtkWidget        *widget,
6355                                   GdkDragContext   *context,
6356                                   /* coordinates relative to the widget */
6357                                   gint              x,
6358                                   gint              y,
6359                                   GtkSelectionData *selection_data,
6360                                   guint             info,
6361                                   guint             time)
6362 {
6363   GtkTreePath *path;
6364   TreeViewDragInfo *di;
6365   gboolean accepted = FALSE;
6366   GtkTreeModel *model;
6367   PsppSheetView *tree_view;
6368   GtkTreePath *dest_row;
6369   GdkDragAction suggested_action;
6370   gboolean path_down_mode;
6371   gboolean drop_append_mode;
6372
6373   tree_view = PSPP_SHEET_VIEW (widget);
6374
6375   model = pspp_sheet_view_get_model (tree_view);
6376
6377   if (!check_model_dnd (model, GTK_TYPE_TREE_DRAG_DEST, "drag_data_received"))
6378     return;
6379
6380   di = get_info (tree_view);
6381
6382   if (di == NULL)
6383     return;
6384
6385   suggested_action = get_status_pending (context);
6386
6387   if (suggested_action)
6388     {
6389       /* We are getting this data due to a request in drag_motion,
6390        * rather than due to a request in drag_drop, so we are just
6391        * supposed to call drag_status, not actually paste in the
6392        * data.
6393        */
6394       path = get_logical_dest_row (tree_view, &path_down_mode,
6395                                    &drop_append_mode);
6396
6397       if (path == NULL)
6398         suggested_action = 0;
6399       else if (path_down_mode)
6400         gtk_tree_path_down (path);
6401
6402       if (suggested_action)
6403         {
6404           if (!gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (model),
6405                                                      path,
6406                                                      selection_data))
6407             {
6408               if (path_down_mode)
6409                 {
6410                   path_down_mode = FALSE;
6411                   gtk_tree_path_up (path);
6412
6413                   if (!gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (model),
6414                                                              path,
6415                                                              selection_data))
6416                     suggested_action = 0;
6417                 }
6418               else
6419                 suggested_action = 0;
6420             }
6421         }
6422
6423       gdk_drag_status (context, suggested_action, time);
6424
6425       if (path)
6426         gtk_tree_path_free (path);
6427
6428       /* If you can't drop, remove user drop indicator until the next motion */
6429       if (suggested_action == 0)
6430         pspp_sheet_view_set_drag_dest_row (PSPP_SHEET_VIEW (widget),
6431                                          NULL,
6432                                          PSPP_SHEET_VIEW_DROP_BEFORE);
6433
6434       return;
6435     }
6436
6437   dest_row = get_dest_row (context, &path_down_mode);
6438
6439   if (dest_row == NULL)
6440     return;
6441
6442   if (gtk_selection_data_get_length (selection_data) >= 0)
6443     {
6444       if (path_down_mode)
6445         {
6446           gtk_tree_path_down (dest_row);
6447           if (!gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (model),
6448                                                      dest_row, selection_data))
6449             gtk_tree_path_up (dest_row);
6450         }
6451     }
6452
6453   if (gtk_selection_data_get_length (selection_data) >= 0)
6454     {
6455       if (gtk_tree_drag_dest_drag_data_received (GTK_TREE_DRAG_DEST (model),
6456                                                  dest_row,
6457                                                  selection_data))
6458         accepted = TRUE;
6459     }
6460
6461   gtk_drag_finish (context,
6462                    accepted,
6463                    (gdk_drag_context_get_actions (context) == GDK_ACTION_MOVE),
6464                    time);
6465
6466   if (gtk_tree_path_get_depth (dest_row) == 1
6467       && gtk_tree_path_get_indices (dest_row)[0] == 0)
6468     {
6469       /* special special case drag to "0", scroll to first item */
6470       if (!tree_view->priv->scroll_to_path)
6471         pspp_sheet_view_scroll_to_cell (tree_view, dest_row, NULL, FALSE, 0.0, 0.0);
6472     }
6473
6474   gtk_tree_path_free (dest_row);
6475
6476   /* drop dest_row */
6477   set_dest_row (context, NULL, NULL, FALSE, FALSE, FALSE);
6478 }
6479
6480
6481
6482 /* GtkContainer Methods
6483  */
6484
6485
6486 static void
6487 pspp_sheet_view_remove (GtkContainer *container,
6488                       GtkWidget    *widget)
6489 {
6490   PsppSheetView *tree_view = PSPP_SHEET_VIEW (container);
6491   PsppSheetViewChild *child = NULL;
6492   GList *tmp_list;
6493
6494   tmp_list = tree_view->priv->children;
6495   while (tmp_list)
6496     {
6497       child = tmp_list->data;
6498       if (child->widget == widget)
6499         {
6500           gtk_widget_unparent (widget);
6501
6502           tree_view->priv->children = g_list_remove_link (tree_view->priv->children, tmp_list);
6503           g_list_free_1 (tmp_list);
6504           g_slice_free (PsppSheetViewChild, child);
6505           return;
6506         }
6507
6508       tmp_list = tmp_list->next;
6509     }
6510
6511   tmp_list = tree_view->priv->columns;
6512
6513   while (tmp_list)
6514     {
6515       PsppSheetViewColumn *column;
6516       column = tmp_list->data;
6517       if (column->button == widget)
6518         {
6519           gtk_widget_unparent (widget);
6520           return;
6521         }
6522       tmp_list = tmp_list->next;
6523     }
6524 }
6525
6526 static void
6527 pspp_sheet_view_forall (GtkContainer *container,
6528                       gboolean      include_internals,
6529                       GtkCallback   callback,
6530                       gpointer      callback_data)
6531 {
6532   PsppSheetView *tree_view = PSPP_SHEET_VIEW (container);
6533   PsppSheetViewChild *child = NULL;
6534   PsppSheetViewColumn *column;
6535   GList *tmp_list;
6536
6537   tmp_list = tree_view->priv->children;
6538   while (tmp_list)
6539     {
6540       child = tmp_list->data;
6541       tmp_list = tmp_list->next;
6542
6543       (* callback) (child->widget, callback_data);
6544     }
6545   if (include_internals == FALSE)
6546     return;
6547
6548   for (tmp_list = tree_view->priv->columns; tmp_list; tmp_list = tmp_list->next)
6549     {
6550       column = tmp_list->data;
6551
6552       if (column->button)
6553         (* callback) (column->button, callback_data);
6554     }
6555 }
6556
6557 /* Returns TRUE if the treeview contains no "special" (editable or activatable)
6558  * cells. If so we draw one big row-spanning focus rectangle.
6559  */
6560 static gboolean
6561 pspp_sheet_view_has_special_cell (PsppSheetView *tree_view)
6562 {
6563   GList *list;
6564
6565   if (tree_view->priv->special_cells != PSPP_SHEET_VIEW_SPECIAL_CELLS_DETECT)
6566     return tree_view->priv->special_cells = PSPP_SHEET_VIEW_SPECIAL_CELLS_YES;
6567
6568   for (list = tree_view->priv->columns; list; list = list->next)
6569     {
6570       if (!((PsppSheetViewColumn *)list->data)->visible)
6571         continue;
6572       if (_pspp_sheet_view_column_count_special_cells (list->data))
6573         return TRUE;
6574     }
6575
6576   return FALSE;
6577 }
6578
6579 static void
6580 pspp_sheet_view_focus_column (PsppSheetView *tree_view,
6581                               PsppSheetViewColumn *focus_column,
6582                               gboolean clamp_column_visible)
6583 {
6584   g_return_if_fail (focus_column != NULL);
6585
6586   tree_view->priv->focus_column = focus_column;
6587
6588   if (gtk_container_get_focus_child (GTK_CONTAINER (tree_view)) != focus_column->button)
6589     gtk_widget_grab_focus (focus_column->button);
6590
6591   if (clamp_column_visible)
6592     pspp_sheet_view_clamp_column_visible (tree_view, focus_column, FALSE);
6593 }
6594
6595 /* Returns TRUE if the focus is within the headers, after the focus operation is
6596  * done
6597  */
6598 static gboolean
6599 pspp_sheet_view_header_focus (PsppSheetView      *tree_view,
6600                             GtkDirectionType  dir,
6601                             gboolean          clamp_column_visible)
6602 {
6603   GtkWidget *focus_child;
6604   PsppSheetViewColumn *focus_column;
6605   GList *last_column, *first_column;
6606   GList *tmp_list;
6607   gboolean rtl;
6608
6609   if (! PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_HEADERS_VISIBLE))
6610     return FALSE;
6611
6612   focus_child = gtk_container_get_focus_child (GTK_CONTAINER (tree_view));
6613
6614   first_column = tree_view->priv->columns;
6615   while (first_column)
6616     {
6617       PsppSheetViewColumn *c = PSPP_SHEET_VIEW_COLUMN (first_column->data);
6618
6619       if (pspp_sheet_view_column_can_focus (c) && c->visible)
6620         break;
6621       first_column = first_column->next;
6622     }
6623
6624   /* No headers are visible, or are focusable.  We can't focus in or out.
6625    */
6626   if (first_column == NULL)
6627     return FALSE;
6628
6629   last_column = g_list_last (tree_view->priv->columns);
6630   while (last_column)
6631     {
6632       PsppSheetViewColumn *c = PSPP_SHEET_VIEW_COLUMN (last_column->data);
6633
6634       if (pspp_sheet_view_column_can_focus (c) && c->visible)
6635         break;
6636       last_column = last_column->prev;
6637     }
6638
6639
6640   rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
6641
6642   switch (dir)
6643     {
6644     case GTK_DIR_TAB_BACKWARD:
6645     case GTK_DIR_TAB_FORWARD:
6646     case GTK_DIR_UP:
6647     case GTK_DIR_DOWN:
6648       if (focus_child == NULL)
6649         {
6650           if (tree_view->priv->focus_column != NULL &&
6651               pspp_sheet_view_column_can_focus (tree_view->priv->focus_column))
6652             focus_column = tree_view->priv->focus_column;
6653           else
6654             focus_column = first_column->data;
6655           pspp_sheet_view_focus_column (tree_view, focus_column,
6656                                         clamp_column_visible);
6657           return TRUE;
6658         }
6659       return FALSE;
6660
6661     case GTK_DIR_LEFT:
6662     case GTK_DIR_RIGHT:
6663       if (focus_child == NULL)
6664         {
6665           if (tree_view->priv->focus_column != NULL)
6666             focus_column = tree_view->priv->focus_column;
6667           else if (dir == GTK_DIR_LEFT)
6668             focus_column = last_column->data;
6669           else
6670             focus_column = first_column->data;
6671           pspp_sheet_view_focus_column (tree_view, focus_column,
6672                                         clamp_column_visible);
6673           return TRUE;
6674         }
6675
6676       if (gtk_widget_child_focus (focus_child, dir))
6677         {
6678           /* The focus moves inside the button. */
6679           /* This is probably a great example of bad UI */
6680           if (clamp_column_visible)
6681             pspp_sheet_view_clamp_column_visible (tree_view,
6682                                                   tree_view->priv->focus_column,
6683                                                   FALSE);
6684           return TRUE;
6685         }
6686
6687       /* We need to move the focus among the row of buttons. */
6688       for (tmp_list = tree_view->priv->columns; tmp_list; tmp_list = tmp_list->next)
6689         if (PSPP_SHEET_VIEW_COLUMN (tmp_list->data)->button == focus_child)
6690           break;
6691
6692       if ((tmp_list == first_column && dir == (rtl ? GTK_DIR_RIGHT : GTK_DIR_LEFT))
6693           || (tmp_list == last_column && dir == (rtl ? GTK_DIR_LEFT : GTK_DIR_RIGHT)))
6694         {
6695           gtk_widget_error_bell (GTK_WIDGET (tree_view));
6696           return TRUE;
6697         }
6698
6699       while (tmp_list)
6700         {
6701           PsppSheetViewColumn *column;
6702
6703           if (dir == (rtl ? GTK_DIR_LEFT : GTK_DIR_RIGHT))
6704             tmp_list = tmp_list->next;
6705           else
6706             tmp_list = tmp_list->prev;
6707
6708           if (tmp_list == NULL)
6709             {
6710               g_warning ("Internal button not found");
6711               break;
6712             }
6713           column = tmp_list->data;
6714           if (column->visible &&
6715               pspp_sheet_view_column_can_focus (column))
6716             {
6717               if (column->button)
6718                 {
6719                   pspp_sheet_view_focus_column (tree_view, column,
6720                                                 clamp_column_visible);
6721                   return TRUE;
6722                 }
6723             }
6724         }
6725       return FALSE;
6726
6727     default:
6728       g_assert_not_reached ();
6729       break;
6730     }
6731
6732   return FALSE;
6733 }
6734
6735 /* This function returns in 'path' the first focusable path, if the given path
6736  * is already focusable, it's the returned one.
6737  *
6738  */
6739 static gboolean
6740 search_first_focusable_path (PsppSheetView  *tree_view,
6741                              GtkTreePath **path,
6742                              gboolean      search_forward,
6743                              int *new_node)
6744 {
6745   /* XXX this function is trivial given that the sheetview doesn't support
6746      separator rows */
6747   int node = -1;
6748
6749   if (!path || !*path)
6750     return FALSE;
6751
6752   _pspp_sheet_view_find_node (tree_view, *path, &node);
6753
6754   if (node < 0)
6755     return FALSE;
6756
6757   if (new_node)
6758     *new_node = node;
6759
6760   return (*path != NULL);
6761 }
6762
6763 static gint
6764 pspp_sheet_view_focus (GtkWidget        *widget,
6765                      GtkDirectionType  direction)
6766 {
6767   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
6768   GtkContainer *container = GTK_CONTAINER (widget);
6769   GtkWidget *focus_child;
6770
6771   if (!gtk_widget_is_sensitive (widget) || !gtk_widget_get_can_focus (widget))
6772     return FALSE;
6773
6774   focus_child = gtk_container_get_focus_child (container);
6775
6776   pspp_sheet_view_stop_editing (PSPP_SHEET_VIEW (widget), FALSE);
6777   /* Case 1.  Headers currently have focus. */
6778   if (focus_child)
6779     {
6780       switch (direction)
6781         {
6782         case GTK_DIR_LEFT:
6783         case GTK_DIR_RIGHT:
6784           pspp_sheet_view_header_focus (tree_view, direction, TRUE);
6785           return TRUE;
6786         case GTK_DIR_TAB_BACKWARD:
6787         case GTK_DIR_UP:
6788           return FALSE;
6789         case GTK_DIR_TAB_FORWARD:
6790         case GTK_DIR_DOWN:
6791           gtk_widget_grab_focus (widget);
6792           return TRUE;
6793         default:
6794           g_assert_not_reached ();
6795           return FALSE;
6796         }
6797     }
6798
6799   /* Case 2. We don't have focus at all. */
6800   if (!gtk_widget_has_focus (widget))
6801     {
6802       if (!pspp_sheet_view_header_focus (tree_view, direction, FALSE))
6803         gtk_widget_grab_focus (widget);
6804       return TRUE;
6805     }
6806
6807   /* Case 3. We have focus already. */
6808   if (direction == GTK_DIR_TAB_BACKWARD)
6809     return (pspp_sheet_view_header_focus (tree_view, direction, FALSE));
6810   else if (direction == GTK_DIR_TAB_FORWARD)
6811     return FALSE;
6812
6813   /* Other directions caught by the keybindings */
6814   gtk_widget_grab_focus (widget);
6815   return TRUE;
6816 }
6817
6818 static void
6819 pspp_sheet_view_grab_focus (GtkWidget *widget)
6820 {
6821   GTK_WIDGET_CLASS (pspp_sheet_view_parent_class)->grab_focus (widget);
6822
6823   pspp_sheet_view_focus_to_cursor (PSPP_SHEET_VIEW (widget));
6824 }
6825
6826 static void
6827 pspp_sheet_view_style_updated (GtkWidget *widget)
6828 {
6829   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
6830   GList *list;
6831   PsppSheetViewColumn *column;
6832   GtkStyleContext *context;
6833
6834   GTK_WIDGET_CLASS (pspp_sheet_view_parent_class)->style_updated (widget);
6835
6836   if (gtk_widget_get_realized (widget))
6837     {
6838       context = gtk_widget_get_style_context (GTK_WIDGET (tree_view));
6839       gtk_style_context_set_background (context, gtk_widget_get_window (GTK_WIDGET (tree_view)));
6840       gtk_style_context_set_background (context, tree_view->priv->header_window);
6841       pspp_sheet_view_set_grid_lines (tree_view, tree_view->priv->grid_lines);
6842     }
6843
6844   gtk_widget_style_get (widget,
6845                         "expander-size", &tree_view->priv->expander_size,
6846                         NULL);
6847   tree_view->priv->expander_size += EXPANDER_EXTRA_PADDING;
6848
6849   for (list = tree_view->priv->columns; list; list = list->next)
6850     {
6851       column = list->data;
6852       _pspp_sheet_view_column_cell_set_dirty (column);
6853     }
6854
6855   tree_view->priv->fixed_height = -1;
6856
6857   /* Invalidate cached button style. */
6858   if (tree_view->priv->button_style)
6859     {
6860       g_object_unref (tree_view->priv->button_style);
6861       tree_view->priv->button_style = NULL;
6862     }
6863
6864   gtk_widget_queue_resize (widget);
6865 }
6866
6867
6868 static void
6869 pspp_sheet_view_set_focus_child (GtkContainer *container,
6870                                GtkWidget    *child)
6871 {
6872   PsppSheetView *tree_view = PSPP_SHEET_VIEW (container);
6873   GList *list;
6874
6875   for (list = tree_view->priv->columns; list; list = list->next)
6876     {
6877       if (PSPP_SHEET_VIEW_COLUMN (list->data)->button == child)
6878         {
6879           tree_view->priv->focus_column = PSPP_SHEET_VIEW_COLUMN (list->data);
6880           break;
6881         }
6882     }
6883
6884   GTK_CONTAINER_CLASS (pspp_sheet_view_parent_class)->set_focus_child (container, child);
6885 }
6886
6887 static void
6888 pspp_sheet_view_set_adjustments (PsppSheetView   *tree_view,
6889                                GtkAdjustment *hadj,
6890                                GtkAdjustment *vadj)
6891 {
6892   gboolean need_adjust = FALSE;
6893
6894   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
6895
6896   if (hadj)
6897     g_return_if_fail (GTK_IS_ADJUSTMENT (hadj));
6898   else
6899     hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
6900   if (vadj)
6901     g_return_if_fail (GTK_IS_ADJUSTMENT (vadj));
6902   else
6903     vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
6904
6905   if (tree_view->priv->hadjustment && (tree_view->priv->hadjustment != hadj))
6906     {
6907       g_signal_handlers_disconnect_by_func (tree_view->priv->hadjustment,
6908                                             pspp_sheet_view_adjustment_changed,
6909                                             tree_view);
6910       g_object_unref (tree_view->priv->hadjustment);
6911     }
6912
6913   if (tree_view->priv->vadjustment && (tree_view->priv->vadjustment != vadj))
6914     {
6915       g_signal_handlers_disconnect_by_func (tree_view->priv->vadjustment,
6916                                             pspp_sheet_view_adjustment_changed,
6917                                             tree_view);
6918       g_object_unref (tree_view->priv->vadjustment);
6919     }
6920
6921   if (tree_view->priv->hadjustment != hadj)
6922     {
6923       tree_view->priv->hadjustment = hadj;
6924       g_object_ref_sink (tree_view->priv->hadjustment);
6925
6926       g_signal_connect (tree_view->priv->hadjustment, "value-changed",
6927                         G_CALLBACK (pspp_sheet_view_adjustment_changed),
6928                         tree_view);
6929       need_adjust = TRUE;
6930     }
6931
6932   if (tree_view->priv->vadjustment != vadj)
6933     {
6934       tree_view->priv->vadjustment = vadj;
6935       g_object_ref_sink (tree_view->priv->vadjustment);
6936
6937       g_signal_connect (tree_view->priv->vadjustment, "value-changed",
6938                         G_CALLBACK (pspp_sheet_view_adjustment_changed),
6939                         tree_view);
6940       need_adjust = TRUE;
6941     }
6942
6943   if (need_adjust)
6944     pspp_sheet_view_adjustment_changed (NULL, tree_view);
6945 }
6946
6947
6948 static gboolean
6949 pspp_sheet_view_real_move_cursor (PsppSheetView       *tree_view,
6950                                 GtkMovementStep    step,
6951                                 gint               count)
6952 {
6953   PsppSheetSelectMode mode;
6954   GdkModifierType state;
6955
6956   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
6957   g_return_val_if_fail (step == GTK_MOVEMENT_LOGICAL_POSITIONS ||
6958                         step == GTK_MOVEMENT_VISUAL_POSITIONS ||
6959                         step == GTK_MOVEMENT_DISPLAY_LINES ||
6960                         step == GTK_MOVEMENT_PAGES ||
6961                         step == GTK_MOVEMENT_BUFFER_ENDS ||
6962                         step == GTK_MOVEMENT_DISPLAY_LINE_ENDS, FALSE);
6963
6964   if (tree_view->priv->row_count == 0)
6965     return FALSE;
6966   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
6967     return FALSE;
6968
6969   pspp_sheet_view_stop_editing (tree_view, FALSE);
6970   PSPP_SHEET_VIEW_SET_FLAG (tree_view, PSPP_SHEET_VIEW_DRAW_KEYFOCUS);
6971   gtk_widget_grab_focus (GTK_WIDGET (tree_view));
6972
6973   mode = 0;
6974   if (gtk_get_current_event_state (&state))
6975     {
6976       if ((state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
6977         mode |= PSPP_SHEET_SELECT_MODE_TOGGLE;
6978       if ((state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK)
6979         mode |= PSPP_SHEET_SELECT_MODE_EXTEND;
6980     }
6981   /* else we assume not pressed */
6982
6983   switch (step)
6984     {
6985     case GTK_MOVEMENT_LOGICAL_POSITIONS:
6986       pspp_sheet_view_move_cursor_tab (tree_view, count);
6987       break;
6988     case GTK_MOVEMENT_VISUAL_POSITIONS:
6989       pspp_sheet_view_move_cursor_left_right (tree_view, count, mode);
6990       break;
6991     case GTK_MOVEMENT_DISPLAY_LINES:
6992       pspp_sheet_view_move_cursor_up_down (tree_view, count, mode);
6993       break;
6994     case GTK_MOVEMENT_PAGES:
6995       pspp_sheet_view_move_cursor_page_up_down (tree_view, count, mode);
6996       break;
6997     case GTK_MOVEMENT_BUFFER_ENDS:
6998       pspp_sheet_view_move_cursor_start_end (tree_view, count, mode);
6999       break;
7000     case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
7001       pspp_sheet_view_move_cursor_line_start_end (tree_view, count, mode);
7002       break;
7003     default:
7004       g_assert_not_reached ();
7005     }
7006
7007   return TRUE;
7008 }
7009
7010 static void
7011 pspp_sheet_view_put (PsppSheetView *tree_view,
7012                      GtkWidget   *child_widget,
7013                      GtkTreePath *path,
7014                      PsppSheetViewColumn *column)
7015 {
7016   PsppSheetViewChild *child;
7017   
7018   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
7019   g_return_if_fail (GTK_IS_WIDGET (child_widget));
7020
7021   child = g_slice_new (PsppSheetViewChild);
7022
7023   child->widget = child_widget;
7024   _pspp_sheet_view_find_node (tree_view, path, &child->node);
7025   if (child->node < 0)
7026     {
7027       g_assert_not_reached ();
7028     }
7029   child->column = column;
7030
7031   tree_view->priv->children = g_list_append (tree_view->priv->children, child);
7032
7033   if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
7034     gtk_widget_set_parent_window (child->widget, tree_view->priv->bin_window);
7035   
7036   gtk_widget_set_parent (child_widget, GTK_WIDGET (tree_view));
7037 }
7038
7039 /* TreeModel Callbacks
7040  */
7041
7042 static void
7043 pspp_sheet_view_row_changed (GtkTreeModel *model,
7044                            GtkTreePath  *path,
7045                            GtkTreeIter  *iter,
7046                            gpointer      data)
7047 {
7048   PsppSheetView *tree_view = (PsppSheetView *)data;
7049   int node;
7050   gboolean free_path = FALSE;
7051   GtkTreePath *cursor_path;
7052
7053   g_return_if_fail (path != NULL || iter != NULL);
7054
7055   if (tree_view->priv->cursor != NULL)
7056     cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
7057   else
7058     cursor_path = NULL;
7059
7060   if (tree_view->priv->edited_column &&
7061       (cursor_path == NULL || gtk_tree_path_compare (cursor_path, path) == 0))
7062     pspp_sheet_view_stop_editing (tree_view, TRUE);
7063
7064   if (cursor_path != NULL)
7065     gtk_tree_path_free (cursor_path);
7066
7067   if (path == NULL)
7068     {
7069       path = gtk_tree_model_get_path (model, iter);
7070       free_path = TRUE;
7071     }
7072   else if (iter == NULL)
7073     gtk_tree_model_get_iter (model, iter, path);
7074
7075   _pspp_sheet_view_find_node (tree_view,
7076                               path,
7077                               &node);
7078
7079   if (node >= 0)
7080     {
7081       if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
7082         pspp_sheet_view_node_queue_redraw (tree_view, node);
7083     }
7084   
7085   if (free_path)
7086     gtk_tree_path_free (path);
7087 }
7088
7089 static void
7090 pspp_sheet_view_row_inserted (GtkTreeModel *model,
7091                             GtkTreePath  *path,
7092                             GtkTreeIter  *iter,
7093                             gpointer      data)
7094 {
7095   PsppSheetView *tree_view = (PsppSheetView *) data;
7096   gint *indices;
7097   int tmpnode = -1;
7098   gint height = tree_view->priv->fixed_height;
7099   gboolean free_path = FALSE;
7100   gboolean node_visible = TRUE;
7101
7102   g_return_if_fail (path != NULL || iter != NULL);
7103
7104   if (path == NULL)
7105     {
7106       path = gtk_tree_model_get_path (model, iter);
7107       free_path = TRUE;
7108     }
7109   else if (iter == NULL)
7110     gtk_tree_model_get_iter (model, iter, path);
7111
7112   tree_view->priv->row_count = gtk_tree_model_iter_n_children (model, NULL);
7113
7114   /* Update all row-references */
7115   gtk_tree_row_reference_inserted (G_OBJECT (data), path);
7116   indices = gtk_tree_path_get_indices (path);
7117   tmpnode = indices[0];
7118
7119   range_tower_insert0 (tree_view->priv->selected, tmpnode, 1);
7120
7121   if (height > 0)
7122     {
7123       if (node_visible && node_is_visible (tree_view, tmpnode))
7124         gtk_widget_queue_resize (GTK_WIDGET (tree_view));
7125       else
7126         gtk_widget_queue_resize_no_redraw (GTK_WIDGET (tree_view));
7127     }
7128   else
7129     install_presize_handler (tree_view);
7130   if (free_path)
7131     gtk_tree_path_free (path);
7132 }
7133
7134 static void
7135 pspp_sheet_view_row_deleted (GtkTreeModel *model,
7136                            GtkTreePath  *path,
7137                            gpointer      data)
7138 {
7139   PsppSheetView *tree_view = (PsppSheetView *)data;
7140   int node;
7141
7142   g_return_if_fail (path != NULL);
7143
7144   gtk_tree_row_reference_deleted (G_OBJECT (data), path);
7145
7146   _pspp_sheet_view_find_node (tree_view, path, &node);
7147
7148   if (node < 0)
7149     return;
7150
7151   range_tower_delete (tree_view->priv->selected, node, 1);
7152
7153   /* Ensure we don't have a dangling pointer to a dead node */
7154   ensure_unprelighted (tree_view);
7155
7156   /* Cancel editting if we've started */
7157   pspp_sheet_view_stop_editing (tree_view, TRUE);
7158
7159   if (tree_view->priv->destroy_count_func)
7160     {
7161       gint child_count = 0;
7162       tree_view->priv->destroy_count_func (tree_view, path, child_count, tree_view->priv->destroy_count_data);
7163     }
7164
7165   tree_view->priv->row_count = gtk_tree_model_iter_n_children (model, NULL);
7166
7167   if (! gtk_tree_row_reference_valid (tree_view->priv->top_row))
7168     {
7169       gtk_tree_row_reference_free (tree_view->priv->top_row);
7170       tree_view->priv->top_row = NULL;
7171     }
7172
7173   install_scroll_sync_handler (tree_view);
7174
7175   gtk_widget_queue_resize (GTK_WIDGET (tree_view));
7176
7177 #if 0
7178   if (helper_data.changed)
7179     g_signal_emit_by_name (tree_view->priv->selection, "changed");
7180 #endif
7181 }
7182
7183 static void
7184 pspp_sheet_view_rows_reordered (GtkTreeModel *model,
7185                               GtkTreePath  *parent,
7186                               GtkTreeIter  *iter,
7187                               gint         *new_order,
7188                               gpointer      data)
7189 {
7190   PsppSheetView *tree_view = PSPP_SHEET_VIEW (data);
7191   gint len;
7192
7193   /* XXX need to adjust selection */
7194   len = gtk_tree_model_iter_n_children (model, iter);
7195
7196   if (len < 2)
7197     return;
7198
7199   gtk_tree_row_reference_reordered (G_OBJECT (data),
7200                                     parent,
7201                                     iter,
7202                                     new_order);
7203
7204   if (gtk_tree_path_get_depth (parent) != 0)
7205     return;
7206
7207   if (tree_view->priv->edited_column)
7208     pspp_sheet_view_stop_editing (tree_view, TRUE);
7209
7210   /* we need to be unprelighted */
7211   ensure_unprelighted (tree_view);
7212
7213   gtk_widget_queue_draw (GTK_WIDGET (tree_view));
7214
7215   pspp_sheet_view_dy_to_top_row (tree_view);
7216 }
7217
7218
7219 /* Internal tree functions
7220  */
7221
7222
7223 static void
7224 pspp_sheet_view_get_background_xrange (PsppSheetView       *tree_view,
7225                                      PsppSheetViewColumn *column,
7226                                      gint              *x1,
7227                                      gint              *x2)
7228 {
7229   PsppSheetViewColumn *tmp_column = NULL;
7230   gint total_width;
7231   GList *list;
7232   gboolean rtl;
7233
7234   if (x1)
7235     *x1 = 0;
7236
7237   if (x2)
7238     *x2 = 0;
7239
7240   rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
7241
7242   total_width = 0;
7243   for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns));
7244        list;
7245        list = (rtl ? list->prev : list->next))
7246     {
7247       tmp_column = list->data;
7248
7249       if (tmp_column == column)
7250         break;
7251
7252       if (tmp_column->visible)
7253         total_width += tmp_column->width;
7254     }
7255
7256   if (tmp_column != column)
7257     {
7258       g_warning (G_STRLOC": passed-in column isn't in the tree");
7259       return;
7260     }
7261
7262   if (x1)
7263     *x1 = total_width;
7264
7265   if (x2)
7266     {
7267       if (column->visible)
7268         *x2 = total_width + column->width;
7269       else
7270         *x2 = total_width; /* width of 0 */
7271     }
7272 }
7273
7274 /* Make sure the node is visible vertically */
7275 static void
7276 pspp_sheet_view_clamp_node_visible (PsppSheetView *tree_view,
7277                                     int node)
7278 {
7279   gint node_dy, height;
7280   GtkTreePath *path = NULL;
7281
7282   if (!gtk_widget_get_realized (GTK_WIDGET (tree_view)))
7283     return;
7284
7285   /* just return if the node is visible, avoiding a costly expose */
7286   node_dy = pspp_sheet_view_node_find_offset (tree_view, node);
7287   height = ROW_HEIGHT (tree_view);
7288   if (node_dy >= gtk_adjustment_get_value (tree_view->priv->vadjustment)
7289       && node_dy + height <= (gtk_adjustment_get_value (tree_view->priv->vadjustment)
7290                               + gtk_adjustment_get_page_size (tree_view->priv->vadjustment)))
7291     return;
7292
7293   path = _pspp_sheet_view_find_path (tree_view, node);
7294   if (path)
7295     {
7296       /* We process updates because we want to clear old selected items when we scroll.
7297        * if this is removed, we get a "selection streak" at the bottom. */
7298       gdk_window_process_updates (tree_view->priv->bin_window, TRUE);
7299       pspp_sheet_view_scroll_to_cell (tree_view, path, NULL, FALSE, 0.0, 0.0);
7300       gtk_tree_path_free (path);
7301     }
7302 }
7303
7304 static void
7305 pspp_sheet_view_clamp_column_visible (PsppSheetView       *tree_view,
7306                                     PsppSheetViewColumn *column,
7307                                     gboolean           focus_to_cell)
7308 {
7309   gint x, width;
7310
7311   if (column == NULL)
7312     return;
7313
7314   x = column->allocation.x;
7315   width = column->allocation.width;
7316
7317   if (width > gtk_adjustment_get_page_size (tree_view->priv->hadjustment))
7318     {
7319       /* The column is larger than the horizontal page size.  If the
7320        * column has cells which can be focussed individually, then we make
7321        * sure the cell which gets focus is fully visible (if even the
7322        * focus cell is bigger than the page size, we make sure the
7323        * left-hand side of the cell is visible).
7324        *
7325        * If the column does not have those so-called special cells, we
7326        * make sure the left-hand side of the column is visible.
7327        */
7328
7329       if (focus_to_cell && pspp_sheet_view_has_special_cell (tree_view))
7330         {
7331           GtkTreePath *cursor_path;
7332           GdkRectangle background_area, cell_area, focus_area;
7333
7334           cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
7335
7336           pspp_sheet_view_get_cell_area (tree_view,
7337                                        cursor_path, column, &cell_area);
7338           pspp_sheet_view_get_background_area (tree_view,
7339                                              cursor_path, column,
7340                                              &background_area);
7341
7342           gtk_tree_path_free (cursor_path);
7343
7344           _pspp_sheet_view_column_get_focus_area (column,
7345                                                 &background_area,
7346                                                 &cell_area,
7347                                                 &focus_area);
7348
7349           x = focus_area.x;
7350           width = focus_area.width;
7351
7352           if (width < gtk_adjustment_get_page_size (tree_view->priv->hadjustment))
7353             {
7354               if ((gtk_adjustment_get_value (tree_view->priv->hadjustment) + gtk_adjustment_get_page_size (tree_view->priv->hadjustment)) < (x + width))
7355                 gtk_adjustment_set_value (tree_view->priv->hadjustment,
7356                                           x + width - gtk_adjustment_get_page_size (tree_view->priv->hadjustment));
7357               else if (gtk_adjustment_get_value (tree_view->priv->hadjustment) > x)
7358                 gtk_adjustment_set_value (tree_view->priv->hadjustment, x);
7359             }
7360         }
7361
7362       gtk_adjustment_set_value (tree_view->priv->hadjustment,
7363                                 CLAMP (x,
7364                                        gtk_adjustment_get_lower (tree_view->priv->hadjustment),
7365                                        gtk_adjustment_get_upper (tree_view->priv->hadjustment)
7366                                        - gtk_adjustment_get_page_size (tree_view->priv->hadjustment)));
7367     }
7368   else
7369     {
7370       if ((gtk_adjustment_get_value (tree_view->priv->hadjustment) + gtk_adjustment_get_page_size (tree_view->priv->hadjustment)) < (x + width))
7371           gtk_adjustment_set_value (tree_view->priv->hadjustment,
7372                                     x + width - gtk_adjustment_get_page_size (tree_view->priv->hadjustment));
7373       else if (gtk_adjustment_get_value (tree_view->priv->hadjustment) > x)
7374         gtk_adjustment_set_value (tree_view->priv->hadjustment, x);
7375   }
7376 }
7377
7378 GtkTreePath *
7379 _pspp_sheet_view_find_path (PsppSheetView *tree_view,
7380                             int node)
7381 {
7382   GtkTreePath *path;
7383
7384   path = gtk_tree_path_new ();
7385   if (node >= 0)
7386     gtk_tree_path_append_index (path, node);
7387   return path;
7388 }
7389
7390 void
7391 _pspp_sheet_view_find_node (PsppSheetView  *tree_view,
7392                           GtkTreePath  *path,
7393                           int *node)
7394 {
7395   gint *indices = gtk_tree_path_get_indices (path);
7396   gint depth = gtk_tree_path_get_depth (path);
7397
7398   *node = -1;
7399   if (depth == 0 || indices[0] < 0 || indices[0] >= tree_view->priv->row_count)
7400     return;
7401   *node = indices[0];
7402 }
7403
7404 static void
7405 pspp_sheet_view_add_move_binding (GtkBindingSet  *binding_set,
7406                                 guint           keyval,
7407                                 guint           modmask,
7408                                 gboolean        add_shifted_binding,
7409                                 GtkMovementStep step,
7410                                 gint            count)
7411 {
7412   
7413   gtk_binding_entry_add_signal (binding_set, keyval, modmask,
7414                                 "move-cursor", 2,
7415                                 G_TYPE_ENUM, step,
7416                                 G_TYPE_INT, count);
7417
7418   if (add_shifted_binding)
7419     gtk_binding_entry_add_signal (binding_set, keyval, GDK_SHIFT_MASK,
7420                                   "move-cursor", 2,
7421                                   G_TYPE_ENUM, step,
7422                                   G_TYPE_INT, count);
7423
7424   if ((modmask & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
7425    return;
7426
7427   gtk_binding_entry_add_signal (binding_set, keyval, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
7428                                 "move-cursor", 2,
7429                                 G_TYPE_ENUM, step,
7430                                 G_TYPE_INT, count);
7431
7432   gtk_binding_entry_add_signal (binding_set, keyval, GDK_CONTROL_MASK,
7433                                 "move-cursor", 2,
7434                                 G_TYPE_ENUM, step,
7435                                 G_TYPE_INT, count);
7436 }
7437
7438 static void
7439 pspp_sheet_view_set_column_drag_info (PsppSheetView       *tree_view,
7440                                     PsppSheetViewColumn *column)
7441 {
7442   PsppSheetViewColumn *left_column;
7443   PsppSheetViewColumn *cur_column = NULL;
7444   PsppSheetViewColumnReorder *reorder;
7445   gboolean rtl;
7446   GList *tmp_list;
7447   gint left;
7448
7449   /* We want to precalculate the motion list such that we know what column slots
7450    * are available.
7451    */
7452   left_column = NULL;
7453   rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
7454
7455   /* First, identify all possible drop spots */
7456   if (rtl)
7457     tmp_list = g_list_last (tree_view->priv->columns);
7458   else
7459     tmp_list = g_list_first (tree_view->priv->columns);
7460
7461   while (tmp_list)
7462     {
7463       cur_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
7464       tmp_list = rtl?g_list_previous (tmp_list):g_list_next (tmp_list);
7465
7466       if (cur_column->visible == FALSE)
7467         continue;
7468
7469       /* If it's not the column moving and func tells us to skip over the column, we continue. */
7470       if (left_column != column && cur_column != column &&
7471           tree_view->priv->column_drop_func &&
7472           ! tree_view->priv->column_drop_func (tree_view, column, left_column, cur_column, tree_view->priv->column_drop_func_data))
7473         {
7474           left_column = cur_column;
7475           continue;
7476         }
7477       reorder = g_slice_new0 (PsppSheetViewColumnReorder);
7478       reorder->left_column = left_column;
7479       left_column = reorder->right_column = cur_column;
7480
7481       tree_view->priv->column_drag_info = g_list_append (tree_view->priv->column_drag_info, reorder);
7482     }
7483
7484   /* Add the last one */
7485   if (tree_view->priv->column_drop_func == NULL ||
7486       ((left_column != column) &&
7487        tree_view->priv->column_drop_func (tree_view, column, left_column, NULL, tree_view->priv->column_drop_func_data)))
7488     {
7489       reorder = g_slice_new0 (PsppSheetViewColumnReorder);
7490       reorder->left_column = left_column;
7491       reorder->right_column = NULL;
7492       tree_view->priv->column_drag_info = g_list_append (tree_view->priv->column_drag_info, reorder);
7493     }
7494
7495   /* We quickly check to see if it even makes sense to reorder columns. */
7496   /* If there is nothing that can be moved, then we return */
7497
7498   if (tree_view->priv->column_drag_info == NULL)
7499     return;
7500
7501   /* We know there are always 2 slots possbile, as you can always return column. */
7502   /* If that's all there is, return */
7503   if (tree_view->priv->column_drag_info->next == NULL || 
7504       (tree_view->priv->column_drag_info->next->next == NULL &&
7505        ((PsppSheetViewColumnReorder *)tree_view->priv->column_drag_info->data)->right_column == column &&
7506        ((PsppSheetViewColumnReorder *)tree_view->priv->column_drag_info->next->data)->left_column == column))
7507     {
7508       for (tmp_list = tree_view->priv->column_drag_info; tmp_list; tmp_list = tmp_list->next)
7509         g_slice_free (PsppSheetViewColumnReorder, tmp_list->data);
7510       g_list_free (tree_view->priv->column_drag_info);
7511       tree_view->priv->column_drag_info = NULL;
7512       return;
7513     }
7514   /* We fill in the ranges for the columns, now that we've isolated them */
7515   left = - TREE_VIEW_COLUMN_DRAG_DEAD_MULTIPLIER (tree_view);
7516
7517   for (tmp_list = tree_view->priv->column_drag_info; tmp_list; tmp_list = tmp_list->next)
7518     {
7519       reorder = (PsppSheetViewColumnReorder *) tmp_list->data;
7520
7521       reorder->left_align = left;
7522       if (tmp_list->next != NULL)
7523         {
7524           g_assert (tmp_list->next->data);
7525           left = reorder->right_align = (reorder->right_column->allocation.x +
7526                                          reorder->right_column->allocation.width +
7527                                          ((PsppSheetViewColumnReorder *)tmp_list->next->data)->left_column->allocation.x)/2;
7528         }
7529       else
7530         {
7531           gint width = gdk_window_get_width (tree_view->priv->header_window);
7532           reorder->right_align = width + TREE_VIEW_COLUMN_DRAG_DEAD_MULTIPLIER (tree_view);
7533         }
7534     }
7535 }
7536
7537 void
7538 _pspp_sheet_view_column_start_drag (PsppSheetView       *tree_view,
7539                                   PsppSheetViewColumn *column)
7540 {
7541   GdkEvent *send_event;
7542   GtkAllocation allocation;
7543   gint x, y;
7544   GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (tree_view));
7545   GdkDisplay *display = gdk_screen_get_display (screen);
7546
7547   g_return_if_fail (tree_view->priv->column_drag_info == NULL);
7548   g_return_if_fail (tree_view->priv->cur_reorder == NULL);
7549   g_return_if_fail (column->button);
7550
7551   pspp_sheet_view_set_column_drag_info (tree_view, column);
7552
7553   if (tree_view->priv->column_drag_info == NULL)
7554     return;
7555
7556   if (tree_view->priv->drag_window == NULL)
7557     {
7558       GdkWindowAttr attributes;
7559       guint attributes_mask;
7560
7561       attributes.window_type = GDK_WINDOW_CHILD;
7562       attributes.wclass = GDK_INPUT_OUTPUT;
7563       attributes.x = column->allocation.x;
7564       attributes.y = 0;
7565       attributes.width = column->allocation.width;
7566       attributes.height = column->allocation.height;
7567       attributes.visual = gtk_widget_get_visual (GTK_WIDGET (tree_view));
7568       attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK;
7569       attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL ;
7570
7571       tree_view->priv->drag_window = gdk_window_new (tree_view->priv->bin_window,
7572                                                      &attributes,
7573                                                      attributes_mask);
7574       gdk_window_set_user_data (tree_view->priv->drag_window, GTK_WIDGET (tree_view));
7575     }
7576
7577   gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME);
7578   gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME);
7579
7580   gtk_grab_remove (column->button);
7581
7582   send_event = gdk_event_new (GDK_LEAVE_NOTIFY);
7583   send_event->crossing.send_event = TRUE;
7584   send_event->crossing.window = g_object_ref (gtk_button_get_event_window (GTK_BUTTON (column->button)));
7585   send_event->crossing.subwindow = NULL;
7586   send_event->crossing.detail = GDK_NOTIFY_ANCESTOR;
7587   send_event->crossing.time = GDK_CURRENT_TIME;
7588
7589   gtk_propagate_event (column->button, send_event);
7590   gdk_event_free (send_event);
7591
7592   send_event = gdk_event_new (GDK_BUTTON_RELEASE);
7593   send_event->button.window = g_object_ref (gdk_screen_get_root_window (screen));
7594   send_event->button.send_event = TRUE;
7595   send_event->button.time = GDK_CURRENT_TIME;
7596   send_event->button.x = -1;
7597   send_event->button.y = -1;
7598   send_event->button.axes = NULL;
7599   send_event->button.state = 0;
7600   send_event->button.button = 1;
7601   send_event->button.device = 
7602     gdk_device_manager_get_client_pointer (gdk_display_get_device_manager (display));
7603
7604   send_event->button.x_root = 0;
7605   send_event->button.y_root = 0;
7606
7607   gtk_propagate_event (column->button, send_event);
7608   gdk_event_free (send_event);
7609
7610   /* Kids, don't try this at home */
7611   g_object_ref (column->button);
7612   gtk_container_remove (GTK_CONTAINER (tree_view), column->button);
7613   gtk_widget_set_parent_window (column->button, tree_view->priv->drag_window);
7614   gtk_widget_set_parent (column->button, GTK_WIDGET (tree_view));
7615   g_object_unref (column->button);
7616
7617   tree_view->priv->drag_column_x = column->allocation.x;
7618   allocation = column->allocation;
7619   allocation.x = 0;
7620   gtk_widget_size_allocate (column->button, &allocation);
7621   gtk_widget_set_parent_window (column->button, tree_view->priv->drag_window);
7622
7623   tree_view->priv->drag_column = column;
7624   gdk_window_show (tree_view->priv->drag_window);
7625
7626   gdk_window_get_origin (tree_view->priv->header_window, &x, &y);
7627
7628   gtk_widget_grab_focus (GTK_WIDGET (tree_view));
7629   while (gtk_events_pending ())
7630     gtk_main_iteration ();
7631
7632   PSPP_SHEET_VIEW_SET_FLAG (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_DRAG);
7633   gdk_pointer_grab (tree_view->priv->drag_window,
7634                     FALSE,
7635                     GDK_POINTER_MOTION_MASK|GDK_BUTTON_RELEASE_MASK,
7636                     NULL, NULL, GDK_CURRENT_TIME);
7637   gdk_keyboard_grab (tree_view->priv->drag_window,
7638                      FALSE,
7639                      GDK_CURRENT_TIME);
7640 }
7641
7642 void
7643 _pspp_sheet_view_queue_draw_node (PsppSheetView        *tree_view,
7644                                 int node,
7645                                 const GdkRectangle *clip_rect)
7646 {
7647   GdkRectangle rect;
7648   GtkAllocation allocation;
7649
7650   if (!gtk_widget_get_realized (GTK_WIDGET (tree_view)))
7651     return;
7652
7653   gtk_widget_get_allocation (GTK_WIDGET (tree_view), &allocation);
7654   rect.x = 0;
7655   rect.width = MAX (tree_view->priv->width, allocation.width);
7656
7657   rect.y = BACKGROUND_FIRST_PIXEL (tree_view, node);
7658   rect.height = ROW_HEIGHT (tree_view);
7659
7660   if (clip_rect)
7661     {
7662       GdkRectangle new_rect;
7663
7664       gdk_rectangle_intersect (clip_rect, &rect, &new_rect);
7665
7666       gdk_window_invalidate_rect (tree_view->priv->bin_window, &new_rect, TRUE);
7667     }
7668   else
7669     {
7670       gdk_window_invalidate_rect (tree_view->priv->bin_window, &rect, TRUE);
7671     }
7672 }
7673
7674 static void
7675 pspp_sheet_view_queue_draw_path (PsppSheetView        *tree_view,
7676                                GtkTreePath        *path,
7677                                const GdkRectangle *clip_rect)
7678 {
7679   int node = -1;
7680
7681   _pspp_sheet_view_find_node (tree_view, path, &node);
7682
7683   if (node)
7684     _pspp_sheet_view_queue_draw_node (tree_view, node, clip_rect);
7685 }
7686
7687 static void
7688 pspp_sheet_view_focus_to_cursor (PsppSheetView *tree_view)
7689
7690 {
7691   GtkTreePath *cursor_path;
7692
7693   if ((tree_view->priv->row_count == 0) ||
7694       (! gtk_widget_get_realized (GTK_WIDGET (tree_view))))
7695     return;
7696
7697   cursor_path = NULL;
7698   if (tree_view->priv->cursor)
7699     cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
7700
7701   if (cursor_path == NULL)
7702     {
7703       /* There's no cursor.  Move the cursor to the first selected row, if any
7704        * are selected, otherwise to the first row in the sheetview.
7705        */
7706       GList *selected_rows;
7707       GtkTreeModel *model;
7708       PsppSheetSelection *selection;
7709
7710       selection = pspp_sheet_view_get_selection (tree_view);
7711       selected_rows = pspp_sheet_selection_get_selected_rows (selection, &model);
7712
7713       if (selected_rows)
7714         {
7715           /* XXX we could avoid doing O(n) work to get this result */
7716           cursor_path = gtk_tree_path_copy((const GtkTreePath *)(selected_rows->data));
7717           g_list_foreach (selected_rows, (GFunc)gtk_tree_path_free, NULL);
7718           g_list_free (selected_rows);
7719         }
7720       else
7721         {
7722           cursor_path = gtk_tree_path_new_first ();
7723           search_first_focusable_path (tree_view, &cursor_path,
7724                                        TRUE, NULL);
7725         }
7726
7727       gtk_tree_row_reference_free (tree_view->priv->cursor);
7728       tree_view->priv->cursor = NULL;
7729
7730       if (cursor_path)
7731         {
7732           if (tree_view->priv->selection->type == PSPP_SHEET_SELECTION_MULTIPLE ||
7733               tree_view->priv->selection->type == PSPP_SHEET_SELECTION_RECTANGLE)
7734             pspp_sheet_view_real_set_cursor (tree_view, cursor_path, FALSE, FALSE, 0);
7735           else
7736             pspp_sheet_view_real_set_cursor (tree_view, cursor_path, TRUE, FALSE, 0);
7737         }
7738     }
7739
7740   if (cursor_path)
7741     {
7742       /* Now find a column for the cursor. */
7743       PSPP_SHEET_VIEW_SET_FLAG (tree_view, PSPP_SHEET_VIEW_DRAW_KEYFOCUS);
7744
7745       pspp_sheet_view_queue_draw_path (tree_view, cursor_path, NULL);
7746       gtk_tree_path_free (cursor_path);
7747
7748       if (tree_view->priv->focus_column == NULL)
7749         {
7750           GList *list;
7751           for (list = tree_view->priv->columns; list; list = list->next)
7752             {
7753               if (PSPP_SHEET_VIEW_COLUMN (list->data)->visible)
7754                 {
7755                   tree_view->priv->focus_column = PSPP_SHEET_VIEW_COLUMN (list->data);
7756                   pspp_sheet_selection_unselect_all_columns (tree_view->priv->selection);
7757                   pspp_sheet_selection_select_column (tree_view->priv->selection, tree_view->priv->focus_column);
7758                   break;
7759                 }
7760             }
7761
7762         }
7763     }
7764 }
7765
7766 static gboolean
7767 pspp_sheet_view_move_cursor_up_down (PsppSheetView *tree_view,
7768                                    gint         count,
7769                                    PsppSheetSelectMode mode)
7770 {
7771   gint selection_count;
7772   int cursor_node = -1;
7773   int new_cursor_node = -1;
7774   GtkTreePath *cursor_path = NULL;
7775   gboolean grab_focus = TRUE;
7776
7777   if (! gtk_widget_has_focus (GTK_WIDGET (tree_view)))
7778     return FALSE;
7779
7780   cursor_path = NULL;
7781   if (!gtk_tree_row_reference_valid (tree_view->priv->cursor))
7782     /* FIXME: we lost the cursor; should we get the first? */
7783     return FALSE;
7784
7785   cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
7786   _pspp_sheet_view_find_node (tree_view, cursor_path, &cursor_node);
7787
7788   if (cursor_node < 0)
7789     /* FIXME: we lost the cursor; should we get the first? */
7790     return FALSE;
7791
7792   selection_count = pspp_sheet_selection_count_selected_rows (tree_view->priv->selection);
7793
7794   if (selection_count == 0
7795       && tree_view->priv->selection->type != PSPP_SHEET_SELECTION_NONE
7796       && !(mode & PSPP_SHEET_SELECT_MODE_TOGGLE))
7797     {
7798       /* Don't move the cursor, but just select the current node */
7799       new_cursor_node = cursor_node;
7800     }
7801   else
7802     {
7803       if (count == -1)
7804         new_cursor_node = pspp_sheet_view_node_prev (tree_view, cursor_node);
7805       else
7806         new_cursor_node = pspp_sheet_view_node_next (tree_view, cursor_node);
7807     }
7808
7809   gtk_tree_path_free (cursor_path);
7810
7811   if (new_cursor_node)
7812     {
7813       cursor_path = _pspp_sheet_view_find_path (tree_view, new_cursor_node);
7814
7815       search_first_focusable_path (tree_view, &cursor_path,
7816                                    (count != -1),
7817                                    &new_cursor_node);
7818
7819       if (cursor_path)
7820         gtk_tree_path_free (cursor_path);
7821     }
7822
7823   /*
7824    * If the list has only one item and multi-selection is set then select
7825    * the row (if not yet selected).
7826    */
7827   if ((tree_view->priv->selection->type == PSPP_SHEET_SELECTION_MULTIPLE ||
7828        tree_view->priv->selection->type == PSPP_SHEET_SELECTION_RECTANGLE) &&
7829       new_cursor_node < 0)
7830     {
7831       if (count == -1)
7832         new_cursor_node = pspp_sheet_view_node_next (tree_view, cursor_node);
7833       else
7834         new_cursor_node = pspp_sheet_view_node_prev (tree_view, cursor_node);
7835
7836       if (new_cursor_node < 0
7837           && !pspp_sheet_view_node_is_selected (tree_view, cursor_node))
7838         {
7839           new_cursor_node = cursor_node;
7840         }
7841       else
7842         {
7843           new_cursor_node = -1;
7844         }
7845     }
7846
7847   if (new_cursor_node >= 0)
7848     {
7849       cursor_path = _pspp_sheet_view_find_path (tree_view, new_cursor_node);
7850       pspp_sheet_view_real_set_cursor (tree_view, cursor_path, TRUE, TRUE, mode);
7851       gtk_tree_path_free (cursor_path);
7852     }
7853   else
7854     {
7855       pspp_sheet_view_clamp_node_visible (tree_view, cursor_node);
7856
7857       if (!(mode & PSPP_SHEET_SELECT_MODE_EXTEND))
7858         {
7859           if (! gtk_widget_keynav_failed (GTK_WIDGET (tree_view),
7860                                           count < 0 ?
7861                                           GTK_DIR_UP : GTK_DIR_DOWN))
7862             {
7863               GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (tree_view));
7864
7865               if (toplevel)
7866                 gtk_widget_child_focus (toplevel,
7867                                         count < 0 ?
7868                                         GTK_DIR_TAB_BACKWARD :
7869                                         GTK_DIR_TAB_FORWARD);
7870
7871               grab_focus = FALSE;
7872             }
7873         }
7874       else
7875         {
7876           gtk_widget_error_bell (GTK_WIDGET (tree_view));
7877         }
7878     }
7879
7880   if (grab_focus)
7881     gtk_widget_grab_focus (GTK_WIDGET (tree_view));
7882
7883   return new_cursor_node >= 0;
7884 }
7885
7886 static void
7887 pspp_sheet_view_move_cursor_page_up_down (PsppSheetView *tree_view,
7888                                           gint         count,
7889                                           PsppSheetSelectMode mode)
7890 {
7891   int cursor_node = -1;
7892   GtkTreePath *old_cursor_path = NULL;
7893   GtkTreePath *cursor_path = NULL;
7894   int start_cursor_node = -1;
7895   gint y;
7896   gint window_y;
7897   gint vertical_separator;
7898
7899   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
7900     return;
7901
7902   if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
7903     old_cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
7904   else
7905     /* This is sorta weird.  Focus in should give us a cursor */
7906     return;
7907
7908   gtk_widget_style_get (GTK_WIDGET (tree_view), "vertical-separator", &vertical_separator, NULL);
7909   _pspp_sheet_view_find_node (tree_view, old_cursor_path, &cursor_node);
7910
7911   if (cursor_node < 0)
7912     {
7913       /* FIXME: we lost the cursor.  Should we try to get one? */
7914       gtk_tree_path_free (old_cursor_path);
7915       return;
7916     }
7917
7918   y = pspp_sheet_view_node_find_offset (tree_view, cursor_node);
7919   window_y = RBTREE_Y_TO_TREE_WINDOW_Y (tree_view, y);
7920   y += tree_view->priv->cursor_offset;
7921   y += count * (int)gtk_adjustment_get_page_increment (tree_view->priv->vadjustment);
7922   y = CLAMP (y, (gint)gtk_adjustment_get_lower (tree_view->priv->vadjustment),  (gint)gtk_adjustment_get_upper (tree_view->priv->vadjustment) - vertical_separator);
7923
7924   if (y >= tree_view->priv->height)
7925     y = tree_view->priv->height - 1;
7926
7927   tree_view->priv->cursor_offset =
7928     pspp_sheet_view_find_offset (tree_view, y, &cursor_node);
7929
7930   if (tree_view->priv->cursor_offset > BACKGROUND_HEIGHT (tree_view))
7931     {
7932       cursor_node = pspp_sheet_view_node_next (tree_view, cursor_node);
7933       tree_view->priv->cursor_offset -= BACKGROUND_HEIGHT (tree_view);
7934     }
7935
7936   y -= tree_view->priv->cursor_offset;
7937   cursor_path = _pspp_sheet_view_find_path (tree_view, cursor_node);
7938
7939   start_cursor_node = cursor_node;
7940
7941   if (! search_first_focusable_path (tree_view, &cursor_path,
7942                                      (count != -1),
7943                                      &cursor_node))
7944     {
7945       /* It looks like we reached the end of the view without finding
7946        * a focusable row.  We will step backwards to find the last
7947        * focusable row.
7948        */
7949       cursor_node = start_cursor_node;
7950       cursor_path = _pspp_sheet_view_find_path (tree_view, cursor_node);
7951
7952       search_first_focusable_path (tree_view, &cursor_path,
7953                                    (count == -1),
7954                                    &cursor_node);
7955     }
7956
7957   if (!cursor_path)
7958     goto cleanup;
7959
7960   /* update y */
7961   y = pspp_sheet_view_node_find_offset (tree_view, cursor_node);
7962
7963   pspp_sheet_view_real_set_cursor (tree_view, cursor_path, TRUE, FALSE, mode);
7964
7965   y -= window_y;
7966   pspp_sheet_view_scroll_to_point (tree_view, -1, y);
7967   pspp_sheet_view_clamp_node_visible (tree_view, cursor_node);
7968   _pspp_sheet_view_queue_draw_node (tree_view, cursor_node, NULL);
7969
7970   if (!gtk_tree_path_compare (old_cursor_path, cursor_path))
7971     gtk_widget_error_bell (GTK_WIDGET (tree_view));
7972
7973   gtk_widget_grab_focus (GTK_WIDGET (tree_view));
7974
7975 cleanup:
7976   gtk_tree_path_free (old_cursor_path);
7977   gtk_tree_path_free (cursor_path);
7978 }
7979
7980 static void
7981 pspp_sheet_view_move_cursor_left_right (PsppSheetView *tree_view,
7982                                         gint         count,
7983                                         PsppSheetSelectMode mode)
7984 {
7985   int cursor_node = -1;
7986   GtkTreePath *cursor_path = NULL;
7987   PsppSheetViewColumn *column;
7988   GtkTreeIter iter;
7989   GList *list;
7990   gboolean found_column = FALSE;
7991   gboolean rtl;
7992
7993   rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
7994
7995   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
7996     return;
7997
7998   if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
7999     cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
8000   else
8001     return;
8002
8003   _pspp_sheet_view_find_node (tree_view, cursor_path, &cursor_node);
8004   if (cursor_node < 0)
8005     return;
8006   if (gtk_tree_model_get_iter (tree_view->priv->model, &iter, cursor_path) == FALSE)
8007     {
8008       gtk_tree_path_free (cursor_path);
8009       return;
8010     }
8011   gtk_tree_path_free (cursor_path);
8012
8013   list = rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns);
8014   if (tree_view->priv->focus_column)
8015     {
8016       for (; list; list = (rtl ? list->prev : list->next))
8017         {
8018           if (list->data == tree_view->priv->focus_column)
8019             break;
8020         }
8021     }
8022
8023   while (list)
8024     {
8025       gboolean left, right;
8026
8027       column = list->data;
8028       if (column->visible == FALSE || column->row_head)
8029         goto loop_end;
8030
8031       pspp_sheet_view_column_cell_set_cell_data (column,
8032                                                tree_view->priv->model,
8033                                                &iter);
8034
8035       if (rtl)
8036         {
8037           right = list->prev ? TRUE : FALSE;
8038           left = list->next ? TRUE : FALSE;
8039         }
8040       else
8041         {
8042           left = list->prev ? TRUE : FALSE;
8043           right = list->next ? TRUE : FALSE;
8044         }
8045
8046       if (_pspp_sheet_view_column_cell_focus (column, count, left, right))
8047         {
8048           tree_view->priv->focus_column = column;
8049           found_column = TRUE;
8050           break;
8051         }
8052     loop_end:
8053       if (count == 1)
8054         list = rtl ? list->prev : list->next;
8055       else
8056         list = rtl ? list->next : list->prev;
8057     }
8058
8059   if (found_column)
8060     {
8061       _pspp_sheet_view_queue_draw_node (tree_view, cursor_node, NULL);
8062       g_signal_emit (tree_view, tree_view_signals[CURSOR_CHANGED], 0);
8063       gtk_widget_grab_focus (GTK_WIDGET (tree_view));
8064     }
8065   else
8066     {
8067       gtk_widget_error_bell (GTK_WIDGET (tree_view));
8068     }
8069
8070   pspp_sheet_view_clamp_column_visible (tree_view,
8071                                       tree_view->priv->focus_column, TRUE);
8072 }
8073
8074 static void
8075 pspp_sheet_view_move_cursor_line_start_end (PsppSheetView *tree_view,
8076                                             gint         count,
8077                                             PsppSheetSelectMode mode)
8078 {
8079   int cursor_node = -1;
8080   GtkTreePath *cursor_path = NULL;
8081   PsppSheetViewColumn *column;
8082   PsppSheetViewColumn *found_column;
8083   GtkTreeIter iter;
8084   GList *list;
8085   gboolean rtl;
8086
8087   rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
8088
8089   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
8090     return;
8091
8092   if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
8093     cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
8094   else
8095     return;
8096
8097   _pspp_sheet_view_find_node (tree_view, cursor_path, &cursor_node);
8098   if (cursor_node < 0)
8099     return;
8100   if (gtk_tree_model_get_iter (tree_view->priv->model, &iter, cursor_path) == FALSE)
8101     {
8102       gtk_tree_path_free (cursor_path);
8103       return;
8104     }
8105   gtk_tree_path_free (cursor_path);
8106
8107   list = rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns);
8108   if (tree_view->priv->focus_column)
8109     {
8110       for (; list; list = (rtl ? list->prev : list->next))
8111         {
8112           if (list->data == tree_view->priv->focus_column)
8113             break;
8114         }
8115     }
8116
8117   found_column = NULL;
8118   while (list)
8119     {
8120       gboolean left, right;
8121
8122       column = list->data;
8123       if (column->visible == FALSE || column->row_head)
8124         goto loop_end;
8125
8126       pspp_sheet_view_column_cell_set_cell_data (column,
8127                                                tree_view->priv->model,
8128                                                &iter);
8129
8130       if (rtl)
8131         {
8132           right = list->prev ? TRUE : FALSE;
8133           left = list->next ? TRUE : FALSE;
8134         }
8135       else
8136         {
8137           left = list->prev ? TRUE : FALSE;
8138           right = list->next ? TRUE : FALSE;
8139         }
8140
8141       if (column->tabbable
8142           && _pspp_sheet_view_column_cell_focus (column, count, left, right))
8143         found_column = column;
8144
8145     loop_end:
8146       if (count == 1)
8147         list = rtl ? list->prev : list->next;
8148       else
8149         list = rtl ? list->next : list->prev;
8150     }
8151
8152   if (found_column)
8153     {
8154       tree_view->priv->focus_column = found_column;
8155       _pspp_sheet_view_queue_draw_node (tree_view, cursor_node, NULL);
8156       g_signal_emit (tree_view, tree_view_signals[CURSOR_CHANGED], 0);
8157       gtk_widget_grab_focus (GTK_WIDGET (tree_view));
8158     }
8159
8160   pspp_sheet_view_clamp_column_visible (tree_view,
8161                                       tree_view->priv->focus_column, TRUE);
8162 }
8163
8164 static gboolean
8165 try_move_cursor_tab (PsppSheetView *tree_view,
8166                      gboolean start_at_focus_column,
8167                      gint count)
8168 {
8169   PsppSheetViewColumn *column;
8170   GtkTreeIter iter;
8171   int cursor_node = -1;
8172   GtkTreePath *cursor_path = NULL;
8173   gboolean rtl;
8174   GList *list;
8175
8176   if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
8177     cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
8178   else
8179     return TRUE;
8180
8181   _pspp_sheet_view_find_node (tree_view, cursor_path, &cursor_node);
8182   if (cursor_node < 0)
8183     return TRUE;
8184   if (gtk_tree_model_get_iter (tree_view->priv->model, &iter, cursor_path) == FALSE)
8185     {
8186       gtk_tree_path_free (cursor_path);
8187       return TRUE;
8188     }
8189   gtk_tree_path_free (cursor_path);
8190
8191   rtl = gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL;
8192   if (start_at_focus_column)
8193     {
8194       list = (rtl
8195               ? g_list_last (tree_view->priv->columns)
8196               : g_list_first (tree_view->priv->columns));
8197       if (tree_view->priv->focus_column)
8198         {
8199           for (; list; list = (rtl ? list->prev : list->next))
8200             {
8201               if (list->data == tree_view->priv->focus_column)
8202                 break;
8203             }
8204         }
8205     }
8206   else
8207     {
8208       list = (rtl ^ (count == 1)
8209               ? g_list_first (tree_view->priv->columns)
8210               : g_list_last (tree_view->priv->columns));
8211     }
8212
8213   while (list)
8214     {
8215       gboolean left, right;
8216
8217       column = list->data;
8218       if (column->visible == FALSE || !column->tabbable)
8219         goto loop_end;
8220
8221       pspp_sheet_view_column_cell_set_cell_data (column,
8222                                                  tree_view->priv->model,
8223                                                  &iter);
8224
8225       if (rtl)
8226         {
8227           right = list->prev ? TRUE : FALSE;
8228           left = list->next ? TRUE : FALSE;
8229         }
8230       else
8231         {
8232           left = list->prev ? TRUE : FALSE;
8233           right = list->next ? TRUE : FALSE;
8234         }
8235
8236       if (column->tabbable
8237           && _pspp_sheet_view_column_cell_focus (column, count, left, right))
8238         {
8239           tree_view->priv->focus_column = column;
8240           _pspp_sheet_view_queue_draw_node (tree_view, cursor_node, NULL);
8241           g_signal_emit (tree_view, tree_view_signals[CURSOR_CHANGED], 0);
8242           gtk_widget_grab_focus (GTK_WIDGET (tree_view));
8243           return TRUE;
8244         }
8245     loop_end:
8246       if (count == 1)
8247         list = rtl ? list->prev : list->next;
8248       else
8249         list = rtl ? list->next : list->prev;
8250     }
8251
8252   return FALSE;
8253 }
8254
8255 static void
8256 pspp_sheet_view_move_cursor_tab (PsppSheetView *tree_view,
8257                                  gint         count)
8258 {
8259   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
8260     return;
8261
8262   if (!try_move_cursor_tab (tree_view, TRUE, count))
8263     {
8264       if (pspp_sheet_view_move_cursor_up_down (tree_view, count, 0)
8265           && !try_move_cursor_tab (tree_view, FALSE, count))
8266         gtk_widget_error_bell (GTK_WIDGET (tree_view));
8267     }
8268
8269   pspp_sheet_view_clamp_column_visible (tree_view,
8270                                         tree_view->priv->focus_column, TRUE);
8271 }
8272
8273 static void
8274 pspp_sheet_view_move_cursor_start_end (PsppSheetView *tree_view,
8275                                        gint         count,
8276                                        PsppSheetSelectMode mode)
8277 {
8278   int cursor_node;
8279   GtkTreePath *path;
8280   GtkTreePath *old_path;
8281
8282   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
8283     return;
8284
8285   g_return_if_fail (tree_view->priv->row_count > 0);
8286
8287   pspp_sheet_view_get_cursor (tree_view, &old_path, NULL);
8288
8289   if (count == -1)
8290     {
8291       /* Now go forward to find the first focusable row. */
8292       path = _pspp_sheet_view_find_path (tree_view, 0);
8293       search_first_focusable_path (tree_view, &path,
8294                                    TRUE, &cursor_node);
8295     }
8296   else
8297     {
8298       /* Now go backwards to find last focusable row. */
8299       path = _pspp_sheet_view_find_path (tree_view, tree_view->priv->row_count - 1);
8300       search_first_focusable_path (tree_view, &path,
8301                                    FALSE, &cursor_node);
8302     }
8303
8304   if (!path)
8305     goto cleanup;
8306
8307   if (gtk_tree_path_compare (old_path, path))
8308     {
8309       pspp_sheet_view_real_set_cursor (tree_view, path, TRUE, TRUE, mode);
8310       gtk_widget_grab_focus (GTK_WIDGET (tree_view));
8311     }
8312   else
8313     {
8314       gtk_widget_error_bell (GTK_WIDGET (tree_view));
8315     }
8316
8317 cleanup:
8318   gtk_tree_path_free (old_path);
8319   gtk_tree_path_free (path);
8320 }
8321
8322 static gboolean
8323 pspp_sheet_view_real_select_all (PsppSheetView *tree_view)
8324 {
8325   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
8326     return FALSE;
8327
8328   if (tree_view->priv->selection->type != PSPP_SHEET_SELECTION_MULTIPLE &&
8329       tree_view->priv->selection->type != PSPP_SHEET_SELECTION_RECTANGLE)
8330     return FALSE;
8331
8332   pspp_sheet_selection_select_all (tree_view->priv->selection);
8333
8334   return TRUE;
8335 }
8336
8337 static gboolean
8338 pspp_sheet_view_real_unselect_all (PsppSheetView *tree_view)
8339 {
8340   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
8341     return FALSE;
8342
8343   if (tree_view->priv->selection->type != PSPP_SHEET_SELECTION_MULTIPLE &&
8344       tree_view->priv->selection->type != PSPP_SHEET_SELECTION_RECTANGLE)
8345     return FALSE;
8346
8347   pspp_sheet_selection_unselect_all (tree_view->priv->selection);
8348
8349   return TRUE;
8350 }
8351
8352 static gboolean
8353 pspp_sheet_view_real_select_cursor_row (PsppSheetView *tree_view,
8354                                         gboolean     start_editing,
8355                                         PsppSheetSelectMode mode)
8356 {
8357   int new_node = -1;
8358   int cursor_node = -1;
8359   GtkTreePath *cursor_path = NULL;
8360
8361   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
8362     return FALSE;
8363
8364   if (tree_view->priv->cursor)
8365     cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
8366
8367   if (cursor_path == NULL)
8368     return FALSE;
8369
8370   _pspp_sheet_view_find_node (tree_view, cursor_path,
8371                               &cursor_node);
8372
8373   if (cursor_node < 0)
8374     {
8375       gtk_tree_path_free (cursor_path);
8376       return FALSE;
8377     }
8378
8379   if (!(mode & PSPP_SHEET_SELECT_MODE_EXTEND) && start_editing &&
8380       tree_view->priv->focus_column)
8381     {
8382       if (pspp_sheet_view_start_editing (tree_view, cursor_path))
8383         {
8384           gtk_tree_path_free (cursor_path);
8385           return TRUE;
8386         }
8387     }
8388
8389   _pspp_sheet_selection_internal_select_node (tree_view->priv->selection,
8390                                             cursor_node,
8391                                             cursor_path,
8392                                             mode,
8393                                             FALSE);
8394
8395   /* We bail out if the original (tree, node) don't exist anymore after
8396    * handling the selection-changed callback.  We do return TRUE because
8397    * the key press has been handled at this point.
8398    */
8399   _pspp_sheet_view_find_node (tree_view, cursor_path, &new_node);
8400
8401   if (cursor_node != new_node)
8402     return FALSE;
8403
8404   pspp_sheet_view_clamp_node_visible (tree_view, cursor_node);
8405
8406   gtk_widget_grab_focus (GTK_WIDGET (tree_view));
8407   _pspp_sheet_view_queue_draw_node (tree_view, cursor_node, NULL);
8408
8409   if (!(mode & PSPP_SHEET_SELECT_MODE_EXTEND))
8410     pspp_sheet_view_row_activated (tree_view, cursor_path,
8411                                  tree_view->priv->focus_column);
8412     
8413   gtk_tree_path_free (cursor_path);
8414
8415   return TRUE;
8416 }
8417
8418 static gboolean
8419 pspp_sheet_view_real_toggle_cursor_row (PsppSheetView *tree_view)
8420 {
8421   int new_node = -1;
8422   int cursor_node = -1;
8423   GtkTreePath *cursor_path = NULL;
8424
8425   if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
8426     return FALSE;
8427
8428   cursor_path = NULL;
8429   if (tree_view->priv->cursor)
8430     cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
8431
8432   if (cursor_path == NULL)
8433     return FALSE;
8434
8435   _pspp_sheet_view_find_node (tree_view, cursor_path, &cursor_node);
8436   if (cursor_node < 0)
8437     {
8438       gtk_tree_path_free (cursor_path);
8439       return FALSE;
8440     }
8441
8442   _pspp_sheet_selection_internal_select_node (tree_view->priv->selection,
8443                                             cursor_node,
8444                                             cursor_path,
8445                                             PSPP_SHEET_SELECT_MODE_TOGGLE,
8446                                             FALSE);
8447
8448   /* We bail out if the original (tree, node) don't exist anymore after
8449    * handling the selection-changed callback.  We do return TRUE because
8450    * the key press has been handled at this point.
8451    */
8452   _pspp_sheet_view_find_node (tree_view, cursor_path, &new_node);
8453
8454   if (cursor_node != new_node)
8455     return FALSE;
8456
8457   pspp_sheet_view_clamp_node_visible (tree_view, cursor_node);
8458
8459   gtk_widget_grab_focus (GTK_WIDGET (tree_view));
8460   pspp_sheet_view_queue_draw_path (tree_view, cursor_path, NULL);
8461   gtk_tree_path_free (cursor_path);
8462
8463   return TRUE;
8464 }
8465
8466 static gboolean
8467 pspp_sheet_view_search_entry_flush_timeout (PsppSheetView *tree_view)
8468 {
8469   pspp_sheet_view_search_dialog_hide (tree_view->priv->search_window, tree_view);
8470   tree_view->priv->typeselect_flush_timeout = 0;
8471
8472   return FALSE;
8473 }
8474
8475 /* Cut and paste from gtkwindow.c */
8476 static void
8477 send_focus_change (GtkWidget *widget,
8478                    gboolean   in)
8479 {
8480   GdkEvent *fevent = gdk_event_new (GDK_FOCUS_CHANGE);
8481
8482   fevent->focus_change.type = GDK_FOCUS_CHANGE;
8483   fevent->focus_change.window = g_object_ref (gtk_widget_get_window (widget));
8484   fevent->focus_change.in = in;
8485   
8486   gtk_widget_send_focus_change (widget, fevent);
8487   gdk_event_free (fevent);
8488 }
8489
8490 static void
8491 pspp_sheet_view_ensure_interactive_directory (PsppSheetView *tree_view)
8492 {
8493   GtkWidget *frame, *vbox, *toplevel;
8494   GdkScreen *screen;
8495
8496   if (tree_view->priv->search_custom_entry_set)
8497     return;
8498
8499   toplevel = gtk_widget_get_toplevel (GTK_WIDGET (tree_view));
8500   screen = gtk_widget_get_screen (GTK_WIDGET (tree_view));
8501
8502    if (tree_view->priv->search_window != NULL)
8503      {
8504        if (gtk_window_get_group (GTK_WINDOW (toplevel)))
8505          gtk_window_group_add_window (gtk_window_get_group (GTK_WINDOW (toplevel)),
8506                                       GTK_WINDOW (tree_view->priv->search_window));
8507        else if (gtk_window_get_group (GTK_WINDOW (tree_view->priv->search_window)))
8508          gtk_window_group_remove_window (gtk_window_get_group (GTK_WINDOW (tree_view->priv->search_window)),
8509                                          GTK_WINDOW (tree_view->priv->search_window));
8510        gtk_window_set_screen (GTK_WINDOW (tree_view->priv->search_window), screen);
8511        return;
8512      }
8513    
8514   tree_view->priv->search_window = gtk_window_new (GTK_WINDOW_POPUP);
8515   gtk_window_set_screen (GTK_WINDOW (tree_view->priv->search_window), screen);
8516
8517   if (gtk_window_get_group (GTK_WINDOW (toplevel)))
8518     gtk_window_group_add_window (gtk_window_get_group (GTK_WINDOW (toplevel)),
8519                                  GTK_WINDOW (tree_view->priv->search_window));
8520
8521   gtk_window_set_type_hint (GTK_WINDOW (tree_view->priv->search_window),
8522                             GDK_WINDOW_TYPE_HINT_UTILITY);
8523   gtk_window_set_modal (GTK_WINDOW (tree_view->priv->search_window), TRUE);
8524   g_signal_connect (tree_view->priv->search_window, "delete-event",
8525                     G_CALLBACK (pspp_sheet_view_search_delete_event),
8526                     tree_view);
8527   g_signal_connect (tree_view->priv->search_window, "key-press-event",
8528                     G_CALLBACK (pspp_sheet_view_search_key_press_event),
8529                     tree_view);
8530   g_signal_connect (tree_view->priv->search_window, "button-press-event",
8531                     G_CALLBACK (pspp_sheet_view_search_button_press_event),
8532                     tree_view);
8533   g_signal_connect (tree_view->priv->search_window, "scroll-event",
8534                     G_CALLBACK (pspp_sheet_view_search_scroll_event),
8535                     tree_view);
8536
8537   frame = gtk_frame_new (NULL);
8538   gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
8539   gtk_widget_show (frame);
8540   gtk_container_add (GTK_CONTAINER (tree_view->priv->search_window), frame);
8541
8542   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
8543   gtk_widget_show (vbox);
8544   gtk_container_add (GTK_CONTAINER (frame), vbox);
8545   gtk_container_set_border_width (GTK_CONTAINER (vbox), 3);
8546
8547   /* add entry */
8548   tree_view->priv->search_entry = gtk_entry_new ();
8549   gtk_widget_show (tree_view->priv->search_entry);
8550   g_signal_connect (tree_view->priv->search_entry, "populate-popup",
8551                     G_CALLBACK (pspp_sheet_view_search_disable_popdown),
8552                     tree_view);
8553   g_signal_connect (tree_view->priv->search_entry,
8554                     "activate", G_CALLBACK (pspp_sheet_view_search_activate),
8555                     tree_view);
8556
8557 #if GTK3_TRANSITION
8558   g_signal_connect (GTK_ENTRY (tree_view->priv->search_entry)->im_context,
8559                     "preedit-changed",
8560                     G_CALLBACK (pspp_sheet_view_search_preedit_changed),
8561                     tree_view);
8562 #endif
8563
8564   gtk_container_add (GTK_CONTAINER (vbox),
8565                      tree_view->priv->search_entry);
8566
8567   gtk_widget_realize (tree_view->priv->search_entry);
8568 }
8569
8570 /* Pops up the interactive search entry.  If keybinding is TRUE then the user
8571  * started this by typing the start_interactive_search keybinding.  Otherwise, it came from 
8572  */
8573 static gboolean
8574 pspp_sheet_view_real_start_interactive_search (PsppSheetView *tree_view,
8575                                              gboolean     keybinding)
8576 {
8577   /* We only start interactive search if we have focus or the columns
8578    * have focus.  If one of our children have focus, we don't want to
8579    * start the search.
8580    */
8581   GList *list;
8582   gboolean found_focus = FALSE;
8583   GtkWidgetClass *entry_parent_class;
8584   
8585   if (!tree_view->priv->enable_search && !keybinding)
8586     return FALSE;
8587
8588   if (tree_view->priv->search_custom_entry_set)
8589     return FALSE;
8590
8591   if (tree_view->priv->search_window != NULL &&
8592       gtk_widget_get_visible (tree_view->priv->search_window))
8593     return TRUE;
8594
8595   for (list = tree_view->priv->columns; list; list = list->next)
8596     {
8597       PsppSheetViewColumn *column;
8598
8599       column = list->data;
8600       if (! column->visible)
8601         continue;
8602
8603       if (column->button && gtk_widget_has_focus (column->button))
8604         {
8605           found_focus = TRUE;
8606           break;
8607         }
8608     }
8609   
8610   if (gtk_widget_has_focus (GTK_WIDGET (tree_view)))
8611     found_focus = TRUE;
8612
8613   if (!found_focus)
8614     return FALSE;
8615
8616   if (tree_view->priv->search_column < 0)
8617     return FALSE;
8618
8619   pspp_sheet_view_ensure_interactive_directory (tree_view);
8620
8621   if (keybinding)
8622     gtk_entry_set_text (GTK_ENTRY (tree_view->priv->search_entry), "");
8623
8624   /* done, show it */
8625   tree_view->priv->search_position_func (tree_view, tree_view->priv->search_window, tree_view->priv->search_position_user_data);
8626   gtk_widget_show (tree_view->priv->search_window);
8627   if (tree_view->priv->search_entry_changed_id == 0)
8628     {
8629       tree_view->priv->search_entry_changed_id =
8630         g_signal_connect (tree_view->priv->search_entry, "changed",
8631                           G_CALLBACK (pspp_sheet_view_search_init),
8632                           tree_view);
8633     }
8634
8635   tree_view->priv->typeselect_flush_timeout =
8636     gdk_threads_add_timeout (PSPP_SHEET_VIEW_SEARCH_DIALOG_TIMEOUT,
8637                    (GSourceFunc) pspp_sheet_view_search_entry_flush_timeout,
8638                    tree_view);
8639
8640   /* Grab focus will select all the text.  We don't want that to happen, so we
8641    * call the parent instance and bypass the selection change.  This is probably
8642    * really non-kosher. */
8643   entry_parent_class = g_type_class_peek_parent (GTK_ENTRY_GET_CLASS (tree_view->priv->search_entry));
8644   (entry_parent_class->grab_focus) (tree_view->priv->search_entry);
8645
8646   /* send focus-in event */
8647   send_focus_change (tree_view->priv->search_entry, TRUE);
8648
8649   /* search first matching iter */
8650   pspp_sheet_view_search_init (tree_view->priv->search_entry, tree_view);
8651
8652   return TRUE;
8653 }
8654
8655 static gboolean
8656 pspp_sheet_view_start_interactive_search (PsppSheetView *tree_view)
8657 {
8658   return pspp_sheet_view_real_start_interactive_search (tree_view, TRUE);
8659 }
8660
8661 /* this function returns the new width of the column being resized given
8662  * the column and x position of the cursor; the x cursor position is passed
8663  * in as a pointer and automagicly corrected if it's beyond min/max limits
8664  */
8665 static gint
8666 pspp_sheet_view_new_column_width (PsppSheetView *tree_view,
8667                                 gint       i,
8668                                 gint      *x)
8669 {
8670   PsppSheetViewColumn *column;
8671   gint width;
8672   gboolean rtl;
8673
8674   /* first translate the x position from gtk_widget_get_window (widget)
8675    * to clist->clist_window
8676    */
8677   rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
8678   column = g_list_nth (tree_view->priv->columns, i)->data;
8679   width = rtl ? (column->allocation.x + column->allocation.width - *x) : (*x - column->allocation.x);
8680  
8681   /* Clamp down the value */
8682   if (column->min_width == -1)
8683     width = MAX (column->button_request, width);
8684   else
8685     width = MAX (column->min_width, width);
8686   if (column->max_width != -1)
8687     width = MIN (width, column->max_width);
8688
8689   *x = rtl ? (column->allocation.x + column->allocation.width - width) : (column->allocation.x + width);
8690  
8691   return width;
8692 }
8693
8694 void 
8695 pspp_sheet_view_column_update_button (PsppSheetViewColumn *tree_column);
8696
8697 /* Callbacks */
8698 static void
8699 pspp_sheet_view_adjustment_changed (GtkAdjustment *adjustment,
8700                                   PsppSheetView   *tree_view)
8701 {
8702   if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
8703     {
8704       gint dy;
8705
8706       gdk_window_move (tree_view->priv->bin_window,
8707                        - gtk_adjustment_get_value (tree_view->priv->hadjustment),
8708                        TREE_VIEW_HEADER_HEIGHT (tree_view));
8709       gdk_window_move (tree_view->priv->header_window,
8710                        - gtk_adjustment_get_value (tree_view->priv->hadjustment),
8711                        0);
8712       dy = tree_view->priv->dy - (int) gtk_adjustment_get_value (tree_view->priv->vadjustment);
8713
8714       gdk_window_scroll (tree_view->priv->bin_window, 0, dy);
8715
8716       if (dy != 0)
8717         {
8718           /* update our dy and top_row */
8719           tree_view->priv->dy = (int) gtk_adjustment_get_value (tree_view->priv->vadjustment);
8720
8721           update_prelight (tree_view,
8722                            tree_view->priv->event_last_x,
8723                            tree_view->priv->event_last_y);
8724
8725           if (!tree_view->priv->in_top_row_to_dy)
8726             pspp_sheet_view_dy_to_top_row (tree_view);
8727         }
8728
8729       update_childrens_allocation(tree_view);
8730     }
8731 }
8732
8733 /* Public methods
8734  */
8735
8736 /**
8737  * pspp_sheet_view_new:
8738  *
8739  * Creates a new #PsppSheetView widget.
8740  *
8741  * Return value: A newly created #PsppSheetView widget.
8742  **/
8743 GtkWidget *
8744 pspp_sheet_view_new (void)
8745 {
8746   return g_object_new (PSPP_TYPE_SHEET_VIEW, NULL);
8747 }
8748
8749 /**
8750  * pspp_sheet_view_new_with_model:
8751  * @model: the model.
8752  *
8753  * Creates a new #PsppSheetView widget with the model initialized to @model.
8754  *
8755  * Return value: A newly created #PsppSheetView widget.
8756  **/
8757 GtkWidget *
8758 pspp_sheet_view_new_with_model (GtkTreeModel *model)
8759 {
8760   return g_object_new (PSPP_TYPE_SHEET_VIEW, "model", model, NULL);
8761 }
8762
8763 /* Public Accessors
8764  */
8765
8766 /**
8767  * pspp_sheet_view_get_model:
8768  * @tree_view: a #PsppSheetView
8769  *
8770  * Returns the model the #PsppSheetView is based on.  Returns %NULL if the
8771  * model is unset.
8772  *
8773  * Return value: A #GtkTreeModel, or %NULL if none is currently being used.
8774  **/
8775 GtkTreeModel *
8776 pspp_sheet_view_get_model (PsppSheetView *tree_view)
8777 {
8778   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
8779
8780   return tree_view->priv->model;
8781 }
8782
8783 /**
8784  * pspp_sheet_view_set_model:
8785  * @tree_view: A #GtkTreeNode.
8786  * @model: (allow-none): The model.
8787  *
8788  * Sets the model for a #PsppSheetView.  If the @tree_view already has a model
8789  * set, it will remove it before setting the new model.  If @model is %NULL,
8790  * then it will unset the old model.
8791  **/
8792 void
8793 pspp_sheet_view_set_model (PsppSheetView  *tree_view,
8794                          GtkTreeModel *model)
8795 {
8796   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
8797   g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model));
8798
8799   if (model == tree_view->priv->model)
8800     return;
8801
8802   if (tree_view->priv->scroll_to_path)
8803     {
8804       gtk_tree_row_reference_free (tree_view->priv->scroll_to_path);
8805       tree_view->priv->scroll_to_path = NULL;
8806     }
8807
8808   if (tree_view->priv->model)
8809     {
8810       GList *tmplist = tree_view->priv->columns;
8811
8812       if (tree_view->priv->selected)
8813         range_tower_set0 (tree_view->priv->selected, 0, ULONG_MAX);
8814       pspp_sheet_view_stop_editing (tree_view, TRUE);
8815
8816       g_signal_handlers_disconnect_by_func (tree_view->priv->model,
8817                                             pspp_sheet_view_row_changed,
8818                                             tree_view);
8819       g_signal_handlers_disconnect_by_func (tree_view->priv->model,
8820                                             pspp_sheet_view_row_inserted,
8821                                             tree_view);
8822       g_signal_handlers_disconnect_by_func (tree_view->priv->model,
8823                                             pspp_sheet_view_row_deleted,
8824                                             tree_view);
8825       g_signal_handlers_disconnect_by_func (tree_view->priv->model,
8826                                             pspp_sheet_view_rows_reordered,
8827                                             tree_view);
8828
8829       for (; tmplist; tmplist = tmplist->next)
8830         _pspp_sheet_view_column_unset_model (tmplist->data,
8831                                            tree_view->priv->model);
8832
8833       tree_view->priv->prelight_node = -1;
8834
8835       gtk_tree_row_reference_free (tree_view->priv->drag_dest_row);
8836       tree_view->priv->drag_dest_row = NULL;
8837       gtk_tree_row_reference_free (tree_view->priv->cursor);
8838       tree_view->priv->cursor = NULL;
8839       gtk_tree_row_reference_free (tree_view->priv->anchor);
8840       tree_view->priv->anchor = NULL;
8841       gtk_tree_row_reference_free (tree_view->priv->top_row);
8842       tree_view->priv->top_row = NULL;
8843       gtk_tree_row_reference_free (tree_view->priv->scroll_to_path);
8844       tree_view->priv->scroll_to_path = NULL;
8845
8846       tree_view->priv->scroll_to_column = NULL;
8847
8848       g_object_unref (tree_view->priv->model);
8849
8850       tree_view->priv->search_column = -1;
8851       tree_view->priv->fixed_height = -1;
8852       tree_view->priv->dy = tree_view->priv->top_row_dy = 0;
8853       tree_view->priv->last_button_x = -1;
8854       tree_view->priv->last_button_y = -1;
8855     }
8856
8857   tree_view->priv->model = model;
8858
8859   if (tree_view->priv->model)
8860     {
8861       gint i;
8862
8863       if (tree_view->priv->search_column == -1)
8864         {
8865           for (i = 0; i < gtk_tree_model_get_n_columns (model); i++)
8866             {
8867               GType type = gtk_tree_model_get_column_type (model, i);
8868
8869               if (g_value_type_transformable (type, G_TYPE_STRING))
8870                 {
8871                   tree_view->priv->search_column = i;
8872                   break;
8873                 }
8874             }
8875         }
8876
8877       g_object_ref (tree_view->priv->model);
8878       g_signal_connect (tree_view->priv->model,
8879                         "row-changed",
8880                         G_CALLBACK (pspp_sheet_view_row_changed),
8881                         tree_view);
8882       g_signal_connect (tree_view->priv->model,
8883                         "row-inserted",
8884                         G_CALLBACK (pspp_sheet_view_row_inserted),
8885                         tree_view);
8886       g_signal_connect (tree_view->priv->model,
8887                         "row-deleted",
8888                         G_CALLBACK (pspp_sheet_view_row_deleted),
8889                         tree_view);
8890       g_signal_connect (tree_view->priv->model,
8891                         "rows-reordered",
8892                         G_CALLBACK (pspp_sheet_view_rows_reordered),
8893                         tree_view);
8894
8895       tree_view->priv->row_count = gtk_tree_model_iter_n_children (tree_view->priv->model, NULL);
8896
8897       /*  FIXME: do I need to do this? pspp_sheet_view_create_buttons (tree_view); */
8898       install_presize_handler (tree_view);
8899     }
8900
8901   g_object_notify (G_OBJECT (tree_view), "model");
8902
8903   if (tree_view->priv->selection)
8904     _pspp_sheet_selection_emit_changed (tree_view->priv->selection);
8905
8906   if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
8907     gtk_widget_queue_resize (GTK_WIDGET (tree_view));
8908 }
8909
8910 /**
8911  * pspp_sheet_view_get_selection:
8912  * @tree_view: A #PsppSheetView.
8913  *
8914  * Gets the #PsppSheetSelection associated with @tree_view.
8915  *
8916  * Return value: A #PsppSheetSelection object.
8917  **/
8918 PsppSheetSelection *
8919 pspp_sheet_view_get_selection (PsppSheetView *tree_view)
8920 {
8921   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
8922
8923   return tree_view->priv->selection;
8924 }
8925
8926 /**
8927  * pspp_sheet_view_get_hadjustment:
8928  * @tree_view: A #PsppSheetView
8929  *
8930  * Gets the #GtkAdjustment currently being used for the horizontal aspect.
8931  *
8932  * Return value: A #GtkAdjustment object, or %NULL if none is currently being
8933  * used.
8934  **/
8935 GtkAdjustment *
8936 pspp_sheet_view_get_hadjustment (PsppSheetView *tree_view)
8937 {
8938   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
8939
8940   return pspp_sheet_view_do_get_hadjustment (tree_view);
8941 }
8942
8943 static GtkAdjustment *
8944 pspp_sheet_view_do_get_hadjustment (PsppSheetView *tree_view)
8945 {
8946   return tree_view->priv->hadjustment;
8947 }
8948
8949 /**
8950  * pspp_sheet_view_set_hadjustment:
8951  * @tree_view: A #PsppSheetView
8952  * @adjustment: (allow-none): The #GtkAdjustment to set, or %NULL
8953  *
8954  * Sets the #GtkAdjustment for the current horizontal aspect.
8955  **/
8956 void
8957 pspp_sheet_view_set_hadjustment (PsppSheetView   *tree_view,
8958                                GtkAdjustment *adjustment)
8959 {
8960   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
8961
8962   pspp_sheet_view_set_adjustments (tree_view,
8963                                  adjustment,
8964                                  tree_view->priv->vadjustment);
8965
8966   g_object_notify (G_OBJECT (tree_view), "hadjustment");
8967 }
8968
8969 static void
8970 pspp_sheet_view_do_set_hadjustment (PsppSheetView   *tree_view,
8971                                   GtkAdjustment *adjustment)
8972 {
8973   PsppSheetViewPrivate *priv = tree_view->priv;
8974
8975   if (adjustment && priv->hadjustment == adjustment)
8976     return;
8977
8978   if (priv->hadjustment != NULL)
8979     {
8980       g_signal_handlers_disconnect_by_func (priv->hadjustment,
8981                                             pspp_sheet_view_adjustment_changed,
8982                                             tree_view);
8983       g_object_unref (priv->hadjustment);
8984     }
8985
8986   if (adjustment == NULL)
8987     adjustment = gtk_adjustment_new (0.0, 0.0, 0.0,
8988                                      0.0, 0.0, 0.0);
8989
8990   g_signal_connect (adjustment, "value-changed",
8991                     G_CALLBACK (pspp_sheet_view_adjustment_changed), tree_view);
8992   priv->hadjustment = g_object_ref_sink (adjustment);
8993   /* FIXME: Adjustment should probably be populated here with fresh values, but
8994    * internal details are too complicated for me to decipher right now.
8995    */
8996   pspp_sheet_view_adjustment_changed (NULL, tree_view);
8997
8998   g_object_notify (G_OBJECT (tree_view), "hadjustment");
8999 }
9000
9001 /**
9002  * pspp_sheet_view_get_vadjustment:
9003  * @tree_view: A #PsppSheetView
9004  *
9005  * Gets the #GtkAdjustment currently being used for the vertical aspect.
9006  *
9007  * Return value: (transfer none): A #GtkAdjustment object, or %NULL
9008  *     if none is currently being used.
9009  *
9010  * Deprecated: 3.0: Use gtk_scrollable_get_vadjustment()
9011  **/
9012 GtkAdjustment *
9013 pspp_sheet_view_get_vadjustment (PsppSheetView *tree_view)
9014 {
9015   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
9016
9017   return pspp_sheet_view_do_get_vadjustment (tree_view);
9018 }
9019
9020 static GtkAdjustment *
9021 pspp_sheet_view_do_get_vadjustment (PsppSheetView *tree_view)
9022 {
9023   return tree_view->priv->vadjustment;
9024 }
9025
9026 /**
9027  * pspp_sheet_view_set_vadjustment:
9028  * @tree_view: A #PsppSheetView
9029  * @adjustment: (allow-none): The #GtkAdjustment to set, or %NULL
9030  *
9031  * Sets the #GtkAdjustment for the current vertical aspect.
9032  *
9033  * Deprecated: 3.0: Use gtk_scrollable_set_vadjustment()
9034  **/
9035 void
9036 pspp_sheet_view_set_vadjustment (PsppSheetView   *tree_view,
9037                                GtkAdjustment *adjustment)
9038 {
9039   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9040   g_return_if_fail (adjustment == NULL || GTK_IS_ADJUSTMENT (adjustment));
9041
9042   pspp_sheet_view_do_set_vadjustment (tree_view, adjustment);
9043 }
9044
9045 static void
9046 pspp_sheet_view_do_set_vadjustment (PsppSheetView   *tree_view,
9047                                   GtkAdjustment *adjustment)
9048 {
9049   PsppSheetViewPrivate *priv = tree_view->priv;
9050
9051   if (adjustment && priv->vadjustment == adjustment)
9052     return;
9053
9054   if (priv->vadjustment != NULL)
9055     {
9056       g_signal_handlers_disconnect_by_func (priv->vadjustment,
9057                                             pspp_sheet_view_adjustment_changed,
9058                                             tree_view);
9059       g_object_unref (priv->vadjustment);
9060     }
9061
9062   if (adjustment == NULL)
9063     adjustment = gtk_adjustment_new (0.0, 0.0, 0.0,
9064                                      0.0, 0.0, 0.0);
9065
9066   g_signal_connect (adjustment, "value-changed",
9067                     G_CALLBACK (pspp_sheet_view_adjustment_changed), tree_view);
9068   priv->vadjustment = g_object_ref_sink (adjustment);
9069   /* FIXME: Adjustment should probably be populated here with fresh values, but
9070    * internal details are too complicated for me to decipher right now.
9071    */
9072   pspp_sheet_view_adjustment_changed (NULL, tree_view);
9073   g_object_notify (G_OBJECT (tree_view), "vadjustment");
9074 }
9075
9076 /* Column and header operations */
9077
9078 /**
9079  * pspp_sheet_view_get_headers_visible:
9080  * @tree_view: A #PsppSheetView.
9081  *
9082  * Returns %TRUE if the headers on the @tree_view are visible.
9083  *
9084  * Return value: Whether the headers are visible or not.
9085  **/
9086 gboolean
9087 pspp_sheet_view_get_headers_visible (PsppSheetView *tree_view)
9088 {
9089   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
9090
9091   return PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_HEADERS_VISIBLE);
9092 }
9093
9094 /**
9095  * pspp_sheet_view_set_headers_visible:
9096  * @tree_view: A #PsppSheetView.
9097  * @headers_visible: %TRUE if the headers are visible
9098  *
9099  * Sets the visibility state of the headers.
9100  **/
9101 void
9102 pspp_sheet_view_set_headers_visible (PsppSheetView *tree_view,
9103                                    gboolean     headers_visible)
9104 {
9105   gint x, y;
9106   GList *list;
9107   PsppSheetViewColumn *column;
9108   GtkAllocation allocation;
9109
9110   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9111
9112   gtk_widget_get_allocation (GTK_WIDGET (tree_view), &allocation);
9113
9114   headers_visible = !! headers_visible;
9115
9116   if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_HEADERS_VISIBLE) == headers_visible)
9117     return;
9118
9119   if (headers_visible)
9120     PSPP_SHEET_VIEW_SET_FLAG (tree_view, PSPP_SHEET_VIEW_HEADERS_VISIBLE);
9121   else
9122     PSPP_SHEET_VIEW_UNSET_FLAG (tree_view, PSPP_SHEET_VIEW_HEADERS_VISIBLE);
9123
9124   if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
9125     {
9126       gdk_window_get_position (tree_view->priv->bin_window, &x, &y);
9127       if (headers_visible)
9128         {
9129           gdk_window_move_resize (tree_view->priv->bin_window, x, y  + TREE_VIEW_HEADER_HEIGHT (tree_view), 
9130                                   tree_view->priv->width, allocation.height -  + TREE_VIEW_HEADER_HEIGHT (tree_view));
9131
9132           if (gtk_widget_get_mapped (GTK_WIDGET (tree_view)))
9133             pspp_sheet_view_map_buttons (tree_view);
9134         }
9135       else
9136         {
9137           gdk_window_move_resize (tree_view->priv->bin_window, x, y, tree_view->priv->width, tree_view->priv->height);
9138
9139           for (list = tree_view->priv->columns; list; list = list->next)
9140             {
9141               column = list->data;
9142               if (column->button)
9143                 gtk_widget_unmap (column->button);
9144             }
9145           gdk_window_hide (tree_view->priv->header_window);
9146         }
9147     }
9148
9149   gtk_adjustment_set_page_size (tree_view->priv->vadjustment, allocation.height - TREE_VIEW_HEADER_HEIGHT (tree_view));
9150   gtk_adjustment_set_page_increment (tree_view->priv->vadjustment, (allocation.height - TREE_VIEW_HEADER_HEIGHT (tree_view)) / 2);
9151   gtk_adjustment_set_lower (tree_view->priv->vadjustment, 0);
9152   gtk_adjustment_set_upper (tree_view->priv->vadjustment, tree_view->priv->height);
9153   gtk_adjustment_changed (tree_view->priv->vadjustment);
9154
9155   gtk_widget_queue_resize (GTK_WIDGET (tree_view));
9156
9157   g_object_notify (G_OBJECT (tree_view), "headers-visible");
9158 }
9159
9160 /**
9161  * pspp_sheet_view_columns_autosize:
9162  * @tree_view: A #PsppSheetView.
9163  *
9164  * Resizes all columns to their optimal width. Only works after the
9165  * treeview has been realized.
9166  **/
9167 void
9168 pspp_sheet_view_columns_autosize (PsppSheetView *tree_view)
9169 {
9170   gboolean dirty = FALSE;
9171   GList *list;
9172   PsppSheetViewColumn *column;
9173
9174   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9175
9176   for (list = tree_view->priv->columns; list; list = list->next)
9177     {
9178       column = list->data;
9179       _pspp_sheet_view_column_cell_set_dirty (column);
9180       dirty = TRUE;
9181     }
9182
9183   if (dirty)
9184     gtk_widget_queue_resize (GTK_WIDGET (tree_view));
9185 }
9186
9187 /**
9188  * pspp_sheet_view_set_headers_clickable:
9189  * @tree_view: A #PsppSheetView.
9190  * @setting: %TRUE if the columns are clickable.
9191  *
9192  * Allow the column title buttons to be clicked.
9193  **/
9194 void
9195 pspp_sheet_view_set_headers_clickable (PsppSheetView *tree_view,
9196                                      gboolean   setting)
9197 {
9198   GList *list;
9199
9200   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9201
9202   for (list = tree_view->priv->columns; list; list = list->next)
9203     pspp_sheet_view_column_set_clickable (PSPP_SHEET_VIEW_COLUMN (list->data), setting);
9204
9205   g_object_notify (G_OBJECT (tree_view), "headers-clickable");
9206 }
9207
9208
9209 /**
9210  * pspp_sheet_view_get_headers_clickable:
9211  * @tree_view: A #PsppSheetView.
9212  *
9213  * Returns whether all header columns are clickable.
9214  *
9215  * Return value: %TRUE if all header columns are clickable, otherwise %FALSE
9216  *
9217  * Since: 2.10
9218  **/
9219 gboolean 
9220 pspp_sheet_view_get_headers_clickable (PsppSheetView *tree_view)
9221 {
9222   GList *list;
9223   
9224   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
9225
9226   for (list = tree_view->priv->columns; list; list = list->next)
9227     if (!PSPP_SHEET_VIEW_COLUMN (list->data)->clickable)
9228       return FALSE;
9229
9230   return TRUE;
9231 }
9232
9233 /**
9234  * pspp_sheet_view_set_rules_hint
9235  * @tree_view: a #PsppSheetView
9236  * @setting: %TRUE if the tree requires reading across rows
9237  *
9238  * This function tells GTK+ that the user interface for your
9239  * application requires users to read across tree rows and associate
9240  * cells with one another. By default, GTK+ will then render the tree
9241  * with alternating row colors. Do <emphasis>not</emphasis> use it
9242  * just because you prefer the appearance of the ruled tree; that's a
9243  * question for the theme. Some themes will draw tree rows in
9244  * alternating colors even when rules are turned off, and users who
9245  * prefer that appearance all the time can choose those themes. You
9246  * should call this function only as a <emphasis>semantic</emphasis>
9247  * hint to the theme engine that your tree makes alternating colors
9248  * useful from a functional standpoint (since it has lots of columns,
9249  * generally).
9250  *
9251  **/
9252 void
9253 pspp_sheet_view_set_rules_hint (PsppSheetView  *tree_view,
9254                               gboolean      setting)
9255 {
9256   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9257
9258   setting = setting != FALSE;
9259
9260   if (tree_view->priv->has_rules != setting)
9261     {
9262       tree_view->priv->has_rules = setting;
9263       gtk_widget_queue_draw (GTK_WIDGET (tree_view));
9264     }
9265
9266   g_object_notify (G_OBJECT (tree_view), "rules-hint");
9267 }
9268
9269 /**
9270  * pspp_sheet_view_get_rules_hint
9271  * @tree_view: a #PsppSheetView
9272  *
9273  * Gets the setting set by pspp_sheet_view_set_rules_hint().
9274  *
9275  * Return value: %TRUE if rules are useful for the user of this tree
9276  **/
9277 gboolean
9278 pspp_sheet_view_get_rules_hint (PsppSheetView  *tree_view)
9279 {
9280   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
9281
9282   return tree_view->priv->has_rules;
9283 }
9284
9285 /* Public Column functions
9286  */
9287
9288 /**
9289  * pspp_sheet_view_append_column:
9290  * @tree_view: A #PsppSheetView.
9291  * @column: The #PsppSheetViewColumn to add.
9292  *
9293  * Appends @column to the list of columns.
9294  *
9295  * Return value: The number of columns in @tree_view after appending.
9296  **/
9297 gint
9298 pspp_sheet_view_append_column (PsppSheetView       *tree_view,
9299                              PsppSheetViewColumn *column)
9300 {
9301   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), -1);
9302   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (column), -1);
9303   g_return_val_if_fail (column->tree_view == NULL, -1);
9304
9305   return pspp_sheet_view_insert_column (tree_view, column, -1);
9306 }
9307
9308
9309 /**
9310  * pspp_sheet_view_remove_column:
9311  * @tree_view: A #PsppSheetView.
9312  * @column: The #PsppSheetViewColumn to remove.
9313  *
9314  * Removes @column from @tree_view.
9315  *
9316  * Return value: The number of columns in @tree_view after removing.
9317  **/
9318 gint
9319 pspp_sheet_view_remove_column (PsppSheetView       *tree_view,
9320                              PsppSheetViewColumn *column)
9321 {
9322   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), -1);
9323   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (column), -1);
9324   g_return_val_if_fail (column->tree_view == GTK_WIDGET (tree_view), -1);
9325
9326   if (tree_view->priv->focus_column == column)
9327     tree_view->priv->focus_column = NULL;
9328
9329   if (tree_view->priv->edited_column == column)
9330     {
9331       pspp_sheet_view_stop_editing (tree_view, TRUE);
9332
9333       /* no need to, but just to be sure ... */
9334       tree_view->priv->edited_column = NULL;
9335     }
9336
9337   _pspp_sheet_view_column_unset_tree_view (column);
9338
9339   tree_view->priv->columns = g_list_remove (tree_view->priv->columns, column);
9340   tree_view->priv->n_columns--;
9341
9342   if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
9343     {
9344       GList *list;
9345
9346       _pspp_sheet_view_column_unrealize_button (column);
9347       for (list = tree_view->priv->columns; list; list = list->next)
9348         {
9349           PsppSheetViewColumn *tmp_column;
9350
9351           tmp_column = PSPP_SHEET_VIEW_COLUMN (list->data);
9352           if (tmp_column->visible)
9353             _pspp_sheet_view_column_cell_set_dirty (tmp_column);
9354         }
9355
9356       if (tree_view->priv->n_columns == 0 &&
9357           pspp_sheet_view_get_headers_visible (tree_view) && 
9358           tree_view->priv->header_window)
9359         gdk_window_hide (tree_view->priv->header_window);
9360
9361       gtk_widget_queue_resize (GTK_WIDGET (tree_view));
9362     }
9363
9364   g_object_unref (column);
9365   g_signal_emit (tree_view, tree_view_signals[COLUMNS_CHANGED], 0);
9366
9367   return tree_view->priv->n_columns;
9368 }
9369
9370 /**
9371  * pspp_sheet_view_insert_column:
9372  * @tree_view: A #PsppSheetView.
9373  * @column: The #PsppSheetViewColumn to be inserted.
9374  * @position: The position to insert @column in.
9375  *
9376  * This inserts the @column into the @tree_view at @position.  If @position is
9377  * -1, then the column is inserted at the end.
9378  *
9379  * Return value: The number of columns in @tree_view after insertion.
9380  **/
9381 gint
9382 pspp_sheet_view_insert_column (PsppSheetView       *tree_view,
9383                              PsppSheetViewColumn *column,
9384                              gint               position)
9385 {
9386   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), -1);
9387   g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (column), -1);
9388   g_return_val_if_fail (column->tree_view == NULL, -1);
9389
9390   g_object_ref_sink (column);
9391
9392   if (tree_view->priv->n_columns == 0 &&
9393       gtk_widget_get_realized (GTK_WIDGET (tree_view)) &&
9394       pspp_sheet_view_get_headers_visible (tree_view))
9395     {
9396       gdk_window_show (tree_view->priv->header_window);
9397     }
9398
9399   tree_view->priv->columns = g_list_insert (tree_view->priv->columns,
9400                                             column, position);
9401   tree_view->priv->n_columns++;
9402
9403   _pspp_sheet_view_column_set_tree_view (column, tree_view);
9404
9405   if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
9406     {
9407       GList *list;
9408
9409       _pspp_sheet_view_column_realize_button (column);
9410
9411       for (list = tree_view->priv->columns; list; list = list->next)
9412         {
9413           column = PSPP_SHEET_VIEW_COLUMN (list->data);
9414           if (column->visible)
9415             _pspp_sheet_view_column_cell_set_dirty (column);
9416         }
9417       gtk_widget_queue_resize (GTK_WIDGET (tree_view));
9418     }
9419
9420   g_signal_emit (tree_view, tree_view_signals[COLUMNS_CHANGED], 0);
9421
9422   return tree_view->priv->n_columns;
9423 }
9424
9425 /**
9426  * pspp_sheet_view_insert_column_with_attributes:
9427  * @tree_view: A #PsppSheetView
9428  * @position: The position to insert the new column in.
9429  * @title: The title to set the header to.
9430  * @cell: The #GtkCellRenderer.
9431  * @Varargs: A %NULL-terminated list of attributes.
9432  *
9433  * Creates a new #PsppSheetViewColumn and inserts it into the @tree_view at
9434  * @position.  If @position is -1, then the newly created column is inserted at
9435  * the end.  The column is initialized with the attributes given.
9436  *
9437  * Return value: The number of columns in @tree_view after insertion.
9438  **/
9439 gint
9440 pspp_sheet_view_insert_column_with_attributes (PsppSheetView     *tree_view,
9441                                              gint             position,
9442                                              const gchar     *title,
9443                                              GtkCellRenderer *cell,
9444                                              ...)
9445 {
9446   PsppSheetViewColumn *column;
9447   gchar *attribute;
9448   va_list args;
9449   gint column_id;
9450
9451   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), -1);
9452
9453   column = pspp_sheet_view_column_new ();
9454   pspp_sheet_view_column_set_title (column, title);
9455   pspp_sheet_view_column_pack_start (column, cell, TRUE);
9456
9457   va_start (args, cell);
9458
9459   attribute = va_arg (args, gchar *);
9460
9461   while (attribute != NULL)
9462     {
9463       column_id = va_arg (args, gint);
9464       pspp_sheet_view_column_add_attribute (column, cell, attribute, column_id);
9465       attribute = va_arg (args, gchar *);
9466     }
9467
9468   va_end (args);
9469
9470   pspp_sheet_view_insert_column (tree_view, column, position);
9471
9472   return tree_view->priv->n_columns;
9473 }
9474
9475 /**
9476  * pspp_sheet_view_insert_column_with_data_func:
9477  * @tree_view: a #PsppSheetView
9478  * @position: Position to insert, -1 for append
9479  * @title: column title
9480  * @cell: cell renderer for column
9481  * @func: function to set attributes of cell renderer
9482  * @data: data for @func
9483  * @dnotify: destroy notifier for @data
9484  *
9485  * Convenience function that inserts a new column into the #PsppSheetView
9486  * with the given cell renderer and a #GtkCellDataFunc to set cell renderer
9487  * attributes (normally using data from the model). See also
9488  * pspp_sheet_view_column_set_cell_data_func(), pspp_sheet_view_column_pack_start().
9489  *
9490  * Return value: number of columns in the tree view post-insert
9491  **/
9492 gint
9493 pspp_sheet_view_insert_column_with_data_func  (PsppSheetView               *tree_view,
9494                                              gint                       position,
9495                                              const gchar               *title,
9496                                              GtkCellRenderer           *cell,
9497                                              PsppSheetCellDataFunc        func,
9498                                              gpointer                   data,
9499                                              GDestroyNotify             dnotify)
9500 {
9501   PsppSheetViewColumn *column;
9502
9503   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), -1);
9504
9505   column = pspp_sheet_view_column_new ();
9506   pspp_sheet_view_column_set_title (column, title);
9507   pspp_sheet_view_column_pack_start (column, cell, TRUE);
9508   pspp_sheet_view_column_set_cell_data_func (column, cell, func, data, dnotify);
9509
9510   pspp_sheet_view_insert_column (tree_view, column, position);
9511
9512   return tree_view->priv->n_columns;
9513 }
9514
9515 /**
9516  * pspp_sheet_view_get_column:
9517  * @tree_view: A #PsppSheetView.
9518  * @n: The position of the column, counting from 0.
9519  *
9520  * Gets the #PsppSheetViewColumn at the given position in the #tree_view.
9521  *
9522  * Return value: The #PsppSheetViewColumn, or %NULL if the position is outside the
9523  * range of columns.
9524  **/
9525 PsppSheetViewColumn *
9526 pspp_sheet_view_get_column (PsppSheetView *tree_view,
9527                           gint         n)
9528 {
9529   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
9530
9531   if (n < 0 || n >= tree_view->priv->n_columns)
9532     return NULL;
9533
9534   if (tree_view->priv->columns == NULL)
9535     return NULL;
9536
9537   return PSPP_SHEET_VIEW_COLUMN (g_list_nth (tree_view->priv->columns, n)->data);
9538 }
9539
9540 /**
9541  * pspp_sheet_view_get_columns:
9542  * @tree_view: A #PsppSheetView
9543  *
9544  * Returns a #GList of all the #PsppSheetViewColumn s currently in @tree_view.
9545  * The returned list must be freed with g_list_free ().
9546  *
9547  * Return value: (element-type PsppSheetViewColumn) (transfer container): A list of #PsppSheetViewColumn s
9548  **/
9549 GList *
9550 pspp_sheet_view_get_columns (PsppSheetView *tree_view)
9551 {
9552   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
9553
9554   return g_list_copy (tree_view->priv->columns);
9555 }
9556
9557 /**
9558  * pspp_sheet_view_move_column_after:
9559  * @tree_view: A #PsppSheetView
9560  * @column: The #PsppSheetViewColumn to be moved.
9561  * @base_column: (allow-none): The #PsppSheetViewColumn to be moved relative to, or %NULL.
9562  *
9563  * Moves @column to be after to @base_column.  If @base_column is %NULL, then
9564  * @column is placed in the first position.
9565  **/
9566 void
9567 pspp_sheet_view_move_column_after (PsppSheetView       *tree_view,
9568                                  PsppSheetViewColumn *column,
9569                                  PsppSheetViewColumn *base_column)
9570 {
9571   GList *column_list_el, *base_el = NULL;
9572
9573   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9574
9575   column_list_el = g_list_find (tree_view->priv->columns, column);
9576   g_return_if_fail (column_list_el != NULL);
9577
9578   if (base_column)
9579     {
9580       base_el = g_list_find (tree_view->priv->columns, base_column);
9581       g_return_if_fail (base_el != NULL);
9582     }
9583
9584   if (column_list_el->prev == base_el)
9585     return;
9586
9587   tree_view->priv->columns = g_list_remove_link (tree_view->priv->columns, column_list_el);
9588   if (base_el == NULL)
9589     {
9590       column_list_el->prev = NULL;
9591       column_list_el->next = tree_view->priv->columns;
9592       if (column_list_el->next)
9593         column_list_el->next->prev = column_list_el;
9594       tree_view->priv->columns = column_list_el;
9595     }
9596   else
9597     {
9598       column_list_el->prev = base_el;
9599       column_list_el->next = base_el->next;
9600       if (column_list_el->next)
9601         column_list_el->next->prev = column_list_el;
9602       base_el->next = column_list_el;
9603     }
9604
9605   if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
9606     {
9607       gtk_widget_queue_resize (GTK_WIDGET (tree_view));
9608       pspp_sheet_view_size_allocate_columns (GTK_WIDGET (tree_view), NULL);
9609     }
9610
9611   g_signal_emit (tree_view, tree_view_signals[COLUMNS_CHANGED], 0);
9612 }
9613
9614 /**
9615  * pspp_sheet_view_set_column_drag_function:
9616  * @tree_view: A #PsppSheetView.
9617  * @func: (allow-none): A function to determine which columns are reorderable, or %NULL.
9618  * @user_data: (allow-none): User data to be passed to @func, or %NULL
9619  * @destroy: (allow-none): Destroy notifier for @user_data, or %NULL
9620  *
9621  * Sets a user function for determining where a column may be dropped when
9622  * dragged.  This function is called on every column pair in turn at the
9623  * beginning of a column drag to determine where a drop can take place.  The
9624  * arguments passed to @func are: the @tree_view, the #PsppSheetViewColumn being
9625  * dragged, the two #PsppSheetViewColumn s determining the drop spot, and
9626  * @user_data.  If either of the #PsppSheetViewColumn arguments for the drop spot
9627  * are %NULL, then they indicate an edge.  If @func is set to be %NULL, then
9628  * @tree_view reverts to the default behavior of allowing all columns to be
9629  * dropped everywhere.
9630  **/
9631 void
9632 pspp_sheet_view_set_column_drag_function (PsppSheetView               *tree_view,
9633                                         PsppSheetViewColumnDropFunc  func,
9634                                         gpointer                   user_data,
9635                                         GDestroyNotify             destroy)
9636 {
9637   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9638
9639   if (tree_view->priv->column_drop_func_data_destroy)
9640     tree_view->priv->column_drop_func_data_destroy (tree_view->priv->column_drop_func_data);
9641
9642   tree_view->priv->column_drop_func = func;
9643   tree_view->priv->column_drop_func_data = user_data;
9644   tree_view->priv->column_drop_func_data_destroy = destroy;
9645 }
9646
9647 /**
9648  * pspp_sheet_view_scroll_to_point:
9649  * @tree_view: a #PsppSheetView
9650  * @tree_x: X coordinate of new top-left pixel of visible area, or -1
9651  * @tree_y: Y coordinate of new top-left pixel of visible area, or -1
9652  *
9653  * Scrolls the tree view such that the top-left corner of the visible
9654  * area is @tree_x, @tree_y, where @tree_x and @tree_y are specified
9655  * in tree coordinates.  The @tree_view must be realized before
9656  * this function is called.  If it isn't, you probably want to be
9657  * using pspp_sheet_view_scroll_to_cell().
9658  *
9659  * If either @tree_x or @tree_y are -1, then that direction isn't scrolled.
9660  **/
9661 void
9662 pspp_sheet_view_scroll_to_point (PsppSheetView *tree_view,
9663                                gint         tree_x,
9664                                gint         tree_y)
9665 {
9666   GtkAdjustment *hadj;
9667   GtkAdjustment *vadj;
9668
9669   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9670   g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (tree_view)));
9671
9672   hadj = tree_view->priv->hadjustment;
9673   vadj = tree_view->priv->vadjustment;
9674
9675   if (tree_x != -1)
9676     gtk_adjustment_set_value (hadj, CLAMP (tree_x, gtk_adjustment_get_lower (hadj), gtk_adjustment_get_upper (hadj) - gtk_adjustment_get_page_size (hadj)));
9677   if (tree_y != -1)
9678     gtk_adjustment_set_value (vadj, CLAMP (tree_y, gtk_adjustment_get_lower (vadj), gtk_adjustment_get_upper (vadj) - gtk_adjustment_get_page_size (vadj)));
9679 }
9680
9681 /**
9682  * pspp_sheet_view_scroll_to_cell:
9683  * @tree_view: A #PsppSheetView.
9684  * @path: (allow-none): The path of the row to move to, or %NULL.
9685  * @column: (allow-none): The #PsppSheetViewColumn to move horizontally to, or %NULL.
9686  * @use_align: whether to use alignment arguments, or %FALSE.
9687  * @row_align: The vertical alignment of the row specified by @path.
9688  * @col_align: The horizontal alignment of the column specified by @column.
9689  *
9690  * Moves the alignments of @tree_view to the position specified by @column and
9691  * @path.  If @column is %NULL, then no horizontal scrolling occurs.  Likewise,
9692  * if @path is %NULL no vertical scrolling occurs.  At a minimum, one of @column
9693  * or @path need to be non-%NULL.  @row_align determines where the row is
9694  * placed, and @col_align determines where @column is placed.  Both are expected
9695  * to be between 0.0 and 1.0. 0.0 means left/top alignment, 1.0 means
9696  * right/bottom alignment, 0.5 means center.
9697  *
9698  * If @use_align is %FALSE, then the alignment arguments are ignored, and the
9699  * tree does the minimum amount of work to scroll the cell onto the screen.
9700  * This means that the cell will be scrolled to the edge closest to its current
9701  * position.  If the cell is currently visible on the screen, nothing is done.
9702  *
9703  * This function only works if the model is set, and @path is a valid row on the
9704  * model.  If the model changes before the @tree_view is realized, the centered
9705  * path will be modified to reflect this change.
9706  **/
9707 void
9708 pspp_sheet_view_scroll_to_cell (PsppSheetView       *tree_view,
9709                               GtkTreePath       *path,
9710                               PsppSheetViewColumn *column,
9711                               gboolean           use_align,
9712                               gfloat             row_align,
9713                               gfloat             col_align)
9714 {
9715   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9716   g_return_if_fail (tree_view->priv->model != NULL);
9717   g_return_if_fail (row_align >= 0.0 && row_align <= 1.0);
9718   g_return_if_fail (col_align >= 0.0 && col_align <= 1.0);
9719   g_return_if_fail (path != NULL || column != NULL);
9720
9721 #if 0
9722   g_print ("pspp_sheet_view_scroll_to_cell:\npath: %s\ncolumn: %s\nuse_align: %d\nrow_align: %f\ncol_align: %f\n",
9723            gtk_tree_path_to_string (path), column?"non-null":"null", use_align, row_align, col_align);
9724 #endif
9725   row_align = CLAMP (row_align, 0.0, 1.0);
9726   col_align = CLAMP (col_align, 0.0, 1.0);
9727
9728
9729   /* Note: Despite the benefits that come from having one code path for the
9730    * scrolling code, we short-circuit validate_visible_area's immplementation as
9731    * it is much slower than just going to the point.
9732    */
9733   if (!gtk_widget_get_visible (GTK_WIDGET (tree_view)) ||
9734       !gtk_widget_get_realized (GTK_WIDGET (tree_view))
9735       /* XXX || GTK_WIDGET_ALLOC_NEEDED (tree_view) */)
9736     {
9737       if (tree_view->priv->scroll_to_path)
9738         gtk_tree_row_reference_free (tree_view->priv->scroll_to_path);
9739
9740       tree_view->priv->scroll_to_path = NULL;
9741       tree_view->priv->scroll_to_column = NULL;
9742
9743       if (path)
9744         tree_view->priv->scroll_to_path = gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view), tree_view->priv->model, path);
9745       if (column)
9746         tree_view->priv->scroll_to_column = column;
9747       tree_view->priv->scroll_to_use_align = use_align;
9748       tree_view->priv->scroll_to_row_align = row_align;
9749       tree_view->priv->scroll_to_col_align = col_align;
9750
9751       install_presize_handler (tree_view);
9752     }
9753   else
9754     {
9755       GdkRectangle cell_rect;
9756       GdkRectangle vis_rect;
9757       gint dest_x, dest_y;
9758
9759       pspp_sheet_view_get_background_area (tree_view, path, column, &cell_rect);
9760       pspp_sheet_view_get_visible_rect (tree_view, &vis_rect);
9761
9762       cell_rect.y = TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, cell_rect.y);
9763
9764       dest_x = vis_rect.x;
9765       dest_y = vis_rect.y;
9766
9767       if (column)
9768         {
9769           if (use_align)
9770             {
9771               dest_x = cell_rect.x - ((vis_rect.width - cell_rect.width) * col_align);
9772             }
9773           else
9774             {
9775               if (cell_rect.x < vis_rect.x)
9776                 dest_x = cell_rect.x;
9777               if (cell_rect.x + cell_rect.width > vis_rect.x + vis_rect.width)
9778                 dest_x = cell_rect.x + cell_rect.width - vis_rect.width;
9779             }
9780         }
9781
9782       if (path)
9783         {
9784           if (use_align)
9785             {
9786               dest_y = cell_rect.y - ((vis_rect.height - cell_rect.height) * row_align);
9787               dest_y = MAX (dest_y, 0);
9788             }
9789           else
9790             {
9791               if (cell_rect.y < vis_rect.y)
9792                 dest_y = cell_rect.y;
9793               if (cell_rect.y + cell_rect.height > vis_rect.y + vis_rect.height)
9794                 dest_y = cell_rect.y + cell_rect.height - vis_rect.height;
9795             }
9796         }
9797
9798       pspp_sheet_view_scroll_to_point (tree_view, dest_x, dest_y);
9799     }
9800 }
9801
9802 /**
9803  * pspp_sheet_view_row_activated:
9804  * @tree_view: A #PsppSheetView
9805  * @path: The #GtkTreePath to be activated.
9806  * @column: The #PsppSheetViewColumn to be activated.
9807  *
9808  * Activates the cell determined by @path and @column.
9809  **/
9810 void
9811 pspp_sheet_view_row_activated (PsppSheetView       *tree_view,
9812                              GtkTreePath       *path,
9813                              PsppSheetViewColumn *column)
9814 {
9815   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9816
9817   g_signal_emit (tree_view, tree_view_signals[ROW_ACTIVATED], 0, path, column);
9818 }
9819
9820
9821 /**
9822  * pspp_sheet_view_get_reorderable:
9823  * @tree_view: a #PsppSheetView
9824  *
9825  * Retrieves whether the user can reorder the tree via drag-and-drop. See
9826  * pspp_sheet_view_set_reorderable().
9827  *
9828  * Return value: %TRUE if the tree can be reordered.
9829  **/
9830 gboolean
9831 pspp_sheet_view_get_reorderable (PsppSheetView *tree_view)
9832 {
9833   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
9834
9835   return tree_view->priv->reorderable;
9836 }
9837
9838 /**
9839  * pspp_sheet_view_set_reorderable:
9840  * @tree_view: A #PsppSheetView.
9841  * @reorderable: %TRUE, if the tree can be reordered.
9842  *
9843  * This function is a convenience function to allow you to reorder
9844  * models that support the #GtkDragSourceIface and the
9845  * #GtkDragDestIface.  Both #GtkTreeStore and #GtkListStore support
9846  * these.  If @reorderable is %TRUE, then the user can reorder the
9847  * model by dragging and dropping rows. The developer can listen to
9848  * these changes by connecting to the model's row_inserted and
9849  * row_deleted signals. The reordering is implemented by setting up
9850  * the tree view as a drag source and destination. Therefore, drag and
9851  * drop can not be used in a reorderable view for any other purpose.
9852  *
9853  * This function does not give you any degree of control over the order -- any
9854  * reordering is allowed.  If more control is needed, you should probably
9855  * handle drag and drop manually.
9856  **/
9857 void
9858 pspp_sheet_view_set_reorderable (PsppSheetView *tree_view,
9859                                gboolean     reorderable)
9860 {
9861   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9862
9863   reorderable = reorderable != FALSE;
9864
9865   if (tree_view->priv->reorderable == reorderable)
9866     return;
9867
9868   if (reorderable)
9869     {
9870       const GtkTargetEntry row_targets[] = {
9871         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, 0 }
9872       };
9873
9874       pspp_sheet_view_enable_model_drag_source (tree_view,
9875                                               GDK_BUTTON1_MASK,
9876                                               row_targets,
9877                                               G_N_ELEMENTS (row_targets),
9878                                               GDK_ACTION_MOVE);
9879       pspp_sheet_view_enable_model_drag_dest (tree_view,
9880                                             row_targets,
9881                                             G_N_ELEMENTS (row_targets),
9882                                             GDK_ACTION_MOVE);
9883     }
9884   else
9885     {
9886       pspp_sheet_view_unset_rows_drag_source (tree_view);
9887       pspp_sheet_view_unset_rows_drag_dest (tree_view);
9888     }
9889
9890   tree_view->priv->reorderable = reorderable;
9891
9892   g_object_notify (G_OBJECT (tree_view), "reorderable");
9893 }
9894
9895 /* If CLEAR_AND_SELECT is true, then the row will be selected and, unless Shift
9896    is pressed, other rows will be unselected.
9897
9898    If CLAMP_NODE is true, then the sheetview will scroll to make the row
9899    visible. */
9900 static void
9901 pspp_sheet_view_real_set_cursor (PsppSheetView     *tree_view,
9902                                GtkTreePath     *path,
9903                                gboolean         clear_and_select,
9904                                gboolean         clamp_node,
9905                                PsppSheetSelectMode mode)
9906 {
9907   int node = -1;
9908
9909   if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
9910     {
9911       GtkTreePath *cursor_path;
9912       cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
9913       pspp_sheet_view_queue_draw_path (tree_view, cursor_path, NULL);
9914       gtk_tree_path_free (cursor_path);
9915     }
9916
9917   gtk_tree_row_reference_free (tree_view->priv->cursor);
9918   tree_view->priv->cursor = NULL;
9919
9920   _pspp_sheet_view_find_node (tree_view, path, &node);
9921   tree_view->priv->cursor =
9922     gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view),
9923                                       tree_view->priv->model,
9924                                       path);
9925
9926   if (tree_view->priv->row_count > 0)
9927     {
9928       int new_node = -1;
9929
9930       if (clear_and_select && !(mode & PSPP_SHEET_SELECT_MODE_TOGGLE))
9931         _pspp_sheet_selection_internal_select_node (tree_view->priv->selection,
9932                                                     node, path, mode,
9933                                                     FALSE);
9934
9935       /* We have to re-find tree and node here again, somebody might have
9936        * cleared the node or the whole tree in the PsppSheetSelection::changed
9937        * callback. If the nodes differ we bail out here.
9938        */
9939       _pspp_sheet_view_find_node (tree_view, path, &new_node);
9940
9941       if (node != new_node)
9942         return;
9943
9944       if (clamp_node)
9945         {
9946           pspp_sheet_view_clamp_node_visible (tree_view, node);
9947           _pspp_sheet_view_queue_draw_node (tree_view, node, NULL);
9948         }
9949     }
9950
9951   g_signal_emit (tree_view, tree_view_signals[CURSOR_CHANGED], 0);
9952 }
9953
9954 /**
9955  * pspp_sheet_view_get_cursor:
9956  * @tree_view: A #PsppSheetView
9957  * @path: (allow-none): A pointer to be filled with the current cursor path, or %NULL
9958  * @focus_column: (allow-none): A pointer to be filled with the current focus column, or %NULL
9959  *
9960  * Fills in @path and @focus_column with the current path and focus column.  If
9961  * the cursor isn't currently set, then *@path will be %NULL.  If no column
9962  * currently has focus, then *@focus_column will be %NULL.
9963  *
9964  * The returned #GtkTreePath must be freed with gtk_tree_path_free() when
9965  * you are done with it.
9966  **/
9967 void
9968 pspp_sheet_view_get_cursor (PsppSheetView        *tree_view,
9969                           GtkTreePath       **path,
9970                           PsppSheetViewColumn **focus_column)
9971 {
9972   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9973
9974   if (path)
9975     {
9976       if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
9977         *path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
9978       else
9979         *path = NULL;
9980     }
9981
9982   if (focus_column)
9983     {
9984       *focus_column = tree_view->priv->focus_column;
9985     }
9986 }
9987
9988 /**
9989  * pspp_sheet_view_set_cursor:
9990  * @tree_view: A #PsppSheetView
9991  * @path: A #GtkTreePath
9992  * @focus_column: (allow-none): A #PsppSheetViewColumn, or %NULL
9993  * @start_editing: %TRUE if the specified cell should start being edited.
9994  *
9995  * Sets the current keyboard focus to be at @path, and selects it.  This is
9996  * useful when you want to focus the user's attention on a particular row.  If
9997  * @focus_column is not %NULL, then focus is given to the column specified by 
9998  * it. Additionally, if @focus_column is specified, and @start_editing is 
9999  * %TRUE, then editing should be started in the specified cell.  
10000  * This function is often followed by @gtk_widget_grab_focus (@tree_view) 
10001  * in order to give keyboard focus to the widget.  Please note that editing 
10002  * can only happen when the widget is realized.
10003  *
10004  * If @path is invalid for @model, the current cursor (if any) will be unset
10005  * and the function will return without failing.
10006  **/
10007 void
10008 pspp_sheet_view_set_cursor (PsppSheetView       *tree_view,
10009                           GtkTreePath       *path,
10010                           PsppSheetViewColumn *focus_column,
10011                           gboolean           start_editing)
10012 {
10013   pspp_sheet_view_set_cursor_on_cell (tree_view, path, focus_column,
10014                                     NULL, start_editing);
10015 }
10016
10017 /**
10018  * pspp_sheet_view_set_cursor_on_cell:
10019  * @tree_view: A #PsppSheetView
10020  * @path: A #GtkTreePath
10021  * @focus_column: (allow-none): A #PsppSheetViewColumn, or %NULL
10022  * @focus_cell: (allow-none): A #GtkCellRenderer, or %NULL
10023  * @start_editing: %TRUE if the specified cell should start being edited.
10024  *
10025  * Sets the current keyboard focus to be at @path, and selects it.  This is
10026  * useful when you want to focus the user's attention on a particular row.  If
10027  * @focus_column is not %NULL, then focus is given to the column specified by
10028  * it. If @focus_column and @focus_cell are not %NULL, and @focus_column
10029  * contains 2 or more editable or activatable cells, then focus is given to
10030  * the cell specified by @focus_cell. Additionally, if @focus_column is
10031  * specified, and @start_editing is %TRUE, then editing should be started in
10032  * the specified cell.  This function is often followed by
10033  * @gtk_widget_grab_focus (@tree_view) in order to give keyboard focus to the
10034  * widget.  Please note that editing can only happen when the widget is
10035  * realized.
10036  *
10037  * If @path is invalid for @model, the current cursor (if any) will be unset
10038  * and the function will return without failing.
10039  *
10040  * Since: 2.2
10041  **/
10042 void
10043 pspp_sheet_view_set_cursor_on_cell (PsppSheetView       *tree_view,
10044                                   GtkTreePath       *path,
10045                                   PsppSheetViewColumn *focus_column,
10046                                   GtkCellRenderer   *focus_cell,
10047                                   gboolean           start_editing)
10048 {
10049   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10050   g_return_if_fail (path != NULL);
10051   g_return_if_fail (focus_column == NULL || PSPP_IS_SHEET_VIEW_COLUMN (focus_column));
10052
10053   if (!tree_view->priv->model)
10054     return;
10055
10056   if (focus_cell)
10057     {
10058       g_return_if_fail (focus_column);
10059       g_return_if_fail (GTK_IS_CELL_RENDERER (focus_cell));
10060     }
10061
10062   /* cancel the current editing, if it exists */
10063   if (tree_view->priv->edited_column &&
10064       tree_view->priv->edited_column->editable_widget)
10065     pspp_sheet_view_stop_editing (tree_view, TRUE);
10066
10067   pspp_sheet_view_real_set_cursor (tree_view, path, TRUE, TRUE, 0);
10068
10069   if (focus_column && focus_column->visible)
10070     {
10071       GList *list;
10072       gboolean column_in_tree = FALSE;
10073
10074       for (list = tree_view->priv->columns; list; list = list->next)
10075         if (list->data == focus_column)
10076           {
10077             column_in_tree = TRUE;
10078             break;
10079           }
10080       g_return_if_fail (column_in_tree);
10081       tree_view->priv->focus_column = focus_column;
10082       if (focus_cell)
10083         pspp_sheet_view_column_focus_cell (focus_column, focus_cell);
10084       if (start_editing)
10085         pspp_sheet_view_start_editing (tree_view, path);
10086
10087       pspp_sheet_selection_unselect_all_columns (tree_view->priv->selection);
10088       pspp_sheet_selection_select_column (tree_view->priv->selection, focus_column);
10089
10090     }
10091 }
10092
10093 /**
10094  * pspp_sheet_view_get_bin_window:
10095  * @tree_view: A #PsppSheetView
10096  * 
10097  * Returns the window that @tree_view renders to.  This is used primarily to
10098  * compare to <literal>event->window</literal> to confirm that the event on
10099  * @tree_view is on the right window.
10100  * 
10101  * Return value: A #GdkWindow, or %NULL when @tree_view hasn't been realized yet
10102  **/
10103 GdkWindow *
10104 pspp_sheet_view_get_bin_window (PsppSheetView *tree_view)
10105 {
10106   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
10107
10108   return tree_view->priv->bin_window;
10109 }
10110
10111 /**
10112  * pspp_sheet_view_get_path_at_pos:
10113  * @tree_view: A #PsppSheetView.
10114  * @x: The x position to be identified (relative to bin_window).
10115  * @y: The y position to be identified (relative to bin_window).
10116  * @path: (out) (allow-none): A pointer to a #GtkTreePath pointer to be filled in, or %NULL
10117  * @column: (out) (allow-none): A pointer to a #PsppSheetViewColumn pointer to be filled in, or %NULL
10118  * @cell_x: (out) (allow-none): A pointer where the X coordinate relative to the cell can be placed, or %NULL
10119  * @cell_y: (out) (allow-none): A pointer where the Y coordinate relative to the cell can be placed, or %NULL
10120  *
10121  * Finds the path at the point (@x, @y), relative to bin_window coordinates
10122  * (please see pspp_sheet_view_get_bin_window()).
10123  * That is, @x and @y are relative to an events coordinates. @x and @y must
10124  * come from an event on the @tree_view only where <literal>event->window ==
10125  * pspp_sheet_view_get_bin_window (<!-- -->)</literal>. It is primarily for
10126  * things like popup menus. If @path is non-%NULL, then it will be filled
10127  * with the #GtkTreePath at that point.  This path should be freed with
10128  * gtk_tree_path_free().  If @column is non-%NULL, then it will be filled
10129  * with the column at that point.  @cell_x and @cell_y return the coordinates
10130  * relative to the cell background (i.e. the @background_area passed to
10131  * gtk_cell_renderer_render()).  This function is only meaningful if
10132  * @tree_view is realized.  Therefore this function will always return %FALSE
10133  * if @tree_view is not realized or does not have a model.
10134  *
10135  * For converting widget coordinates (eg. the ones you get from
10136  * GtkWidget::query-tooltip), please see
10137  * pspp_sheet_view_convert_widget_to_bin_window_coords().
10138  *
10139  * Return value: %TRUE if a row exists at that coordinate.
10140  **/
10141 gboolean
10142 pspp_sheet_view_get_path_at_pos (PsppSheetView        *tree_view,
10143                                gint                x,
10144                                gint                y,
10145                                GtkTreePath       **path,
10146                                PsppSheetViewColumn **column,
10147                                gint               *cell_x,
10148                                gint               *cell_y)
10149 {
10150   int node;
10151   gint y_offset;
10152
10153   g_return_val_if_fail (tree_view != NULL, FALSE);
10154
10155   if (path)
10156     *path = NULL;
10157   if (column)
10158     *column = NULL;
10159
10160   if (tree_view->priv->bin_window == NULL)
10161     return FALSE;
10162
10163   if (tree_view->priv->row_count == 0)
10164     return FALSE;
10165
10166   if (x > gtk_adjustment_get_upper (tree_view->priv->hadjustment))
10167     return FALSE;
10168
10169   if (x < 0 || y < 0)
10170     return FALSE;
10171
10172   if (column || cell_x)
10173     {
10174       PsppSheetViewColumn *tmp_column;
10175       PsppSheetViewColumn *last_column = NULL;
10176       GList *list;
10177       gint remaining_x = x;
10178       gboolean found = FALSE;
10179       gboolean rtl;
10180
10181       rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
10182       for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns));
10183            list;
10184            list = (rtl ? list->prev : list->next))
10185         {
10186           tmp_column = list->data;
10187
10188           if (tmp_column->visible == FALSE)
10189             continue;
10190
10191           last_column = tmp_column;
10192           if (remaining_x <= tmp_column->width)
10193             {
10194               found = TRUE;
10195
10196               if (column)
10197                 *column = tmp_column;
10198
10199               if (cell_x)
10200                 *cell_x = remaining_x;
10201
10202               break;
10203             }
10204           remaining_x -= tmp_column->width;
10205         }
10206
10207       /* If found is FALSE and there is a last_column, then it the remainder
10208        * space is in that area
10209        */
10210       if (!found)
10211         {
10212           if (last_column)
10213             {
10214               if (column)
10215                 *column = last_column;
10216               
10217               if (cell_x)
10218                 *cell_x = last_column->width + remaining_x;
10219             }
10220           else
10221             {
10222               return FALSE;
10223             }
10224         }
10225     }
10226
10227   y_offset = pspp_sheet_view_find_offset (tree_view,
10228                                           TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, y),
10229                                           &node);
10230
10231   if (node < 0)
10232     return FALSE;
10233
10234   if (cell_y)
10235     *cell_y = y_offset;
10236
10237   if (path)
10238     *path = _pspp_sheet_view_find_path (tree_view, node);
10239
10240   return TRUE;
10241 }
10242
10243 /* Computes 'cell_area' from 'background_area', which must be the background
10244    area for a cell.  Set 'subtract_focus_rect' to TRUE to compute the cell area
10245    as passed to a GtkCellRenderer's "render" function, or to FALSE to compute
10246    the cell area as passed to _pspp_sheet_view_column_cell_render().
10247
10248    'column' is required to properly adjust 'cell_area->x' and
10249    'cell_area->width'.  It may be set to NULL if these values are not of
10250    interest.  In this case 'cell_area->x' and 'cell_area->width' will be
10251    returned as 0. */
10252 static void
10253 pspp_sheet_view_adjust_cell_area (PsppSheetView        *tree_view,
10254                                   PsppSheetViewColumn  *column,
10255                                   const GdkRectangle   *background_area,
10256                                   gboolean              subtract_focus_rect,
10257                                   GdkRectangle         *cell_area)
10258 {
10259   gint vertical_separator;
10260   gint horizontal_separator;
10261
10262   *cell_area = *background_area;
10263
10264   gtk_widget_style_get (GTK_WIDGET (tree_view),
10265                         "vertical-separator", &vertical_separator,
10266                         "horizontal-separator", &horizontal_separator,
10267                         NULL);
10268   cell_area->x += horizontal_separator / 2;
10269   cell_area->y += vertical_separator / 2;
10270   cell_area->width -= horizontal_separator;
10271   cell_area->height -= vertical_separator;
10272
10273   if (subtract_focus_rect)
10274     {
10275       int focus_line_width;
10276
10277       gtk_widget_style_get (GTK_WIDGET (tree_view),
10278                             "focus-line-width", &focus_line_width,
10279                             NULL);
10280       cell_area->x += focus_line_width;
10281       cell_area->y += focus_line_width;
10282       cell_area->width -= 2 * focus_line_width;
10283       cell_area->height -= 2 * focus_line_width;
10284     }
10285
10286   if (tree_view->priv->grid_lines != PSPP_SHEET_VIEW_GRID_LINES_NONE)
10287     {
10288       gint grid_line_width;
10289       gtk_widget_style_get (GTK_WIDGET (tree_view),
10290                             "grid-line-width", &grid_line_width,
10291                             NULL);
10292
10293       if ((tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_VERTICAL
10294            || tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_BOTH)
10295           && column != NULL)
10296         {
10297           PsppSheetViewColumn *first_column, *last_column;
10298           GList *list;
10299
10300           /* Find the last visible column. */
10301           last_column = NULL;
10302           for (list = g_list_last (tree_view->priv->columns);
10303                list;
10304                list = list->prev)
10305             {
10306               PsppSheetViewColumn *c = list->data;
10307               if (c->visible)
10308                 {
10309                   last_column = c;
10310                   break;
10311                 }
10312             }
10313
10314           /* Find the first visible column. */
10315           first_column = NULL;
10316           for (list = g_list_first (tree_view->priv->columns);
10317                list;
10318                list = list->next)
10319             {
10320               PsppSheetViewColumn *c = list->data;
10321               if (c->visible)
10322                 {
10323                   first_column = c;
10324                   break;
10325                 }
10326             }
10327
10328           if (column == first_column)
10329             {
10330               cell_area->width -= grid_line_width / 2;
10331             }
10332           else if (column == last_column)
10333             {
10334               cell_area->x += grid_line_width / 2;
10335               cell_area->width -= grid_line_width / 2;
10336             }
10337           else
10338             {
10339               cell_area->x += grid_line_width / 2;
10340               cell_area->width -= grid_line_width;
10341             }
10342         }
10343
10344       if (tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_HORIZONTAL
10345           || tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_BOTH)
10346         {
10347           cell_area->y += grid_line_width / 2;
10348           cell_area->height -= grid_line_width;
10349         }
10350     }
10351
10352   if (column == NULL)
10353     {
10354       cell_area->x = 0;
10355       cell_area->width = 0;
10356     }
10357 }
10358
10359 /**
10360  * pspp_sheet_view_get_cell_area:
10361  * @tree_view: a #PsppSheetView
10362  * @path: (allow-none): a #GtkTreePath for the row, or %NULL to get only horizontal coordinates
10363  * @column: (allow-none): a #PsppSheetViewColumn for the column, or %NULL to get only vertical coordinates
10364  * @rect: rectangle to fill with cell rect
10365  *
10366  * Fills the bounding rectangle in bin_window coordinates for the cell at the
10367  * row specified by @path and the column specified by @column.  If @path is
10368  * %NULL, or points to a path not currently displayed, the @y and @height fields
10369  * of the rectangle will be filled with 0. If @column is %NULL, the @x and @width
10370  * fields will be filled with 0.  The sum of all cell rects does not cover the
10371  * entire tree; there are extra pixels in between rows, for example. The
10372  * returned rectangle is equivalent to the @cell_area passed to
10373  * gtk_cell_renderer_render().  This function is only valid if @tree_view is
10374  * realized.
10375  **/
10376 void
10377 pspp_sheet_view_get_cell_area (PsppSheetView        *tree_view,
10378                              GtkTreePath        *path,
10379                              PsppSheetViewColumn  *column,
10380                              GdkRectangle       *rect)
10381 {
10382   GdkRectangle background_area;
10383
10384   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10385   g_return_if_fail (column == NULL || PSPP_IS_SHEET_VIEW_COLUMN (column));
10386   g_return_if_fail (rect != NULL);
10387   g_return_if_fail (!column || column->tree_view == (GtkWidget *) tree_view);
10388   g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (tree_view)));
10389
10390   pspp_sheet_view_get_background_area (tree_view, path, column,
10391                                        &background_area);
10392   pspp_sheet_view_adjust_cell_area (tree_view, column, &background_area,
10393                                     FALSE, rect);
10394 }
10395
10396 /**
10397  * pspp_sheet_view_get_background_area:
10398  * @tree_view: a #PsppSheetView
10399  * @path: (allow-none): a #GtkTreePath for the row, or %NULL to get only horizontal coordinates
10400  * @column: (allow-none): a #PsppSheetViewColumn for the column, or %NULL to get only vertical coordiantes
10401  * @rect: rectangle to fill with cell background rect
10402  *
10403  * Fills the bounding rectangle in bin_window coordinates for the cell at the
10404  * row specified by @path and the column specified by @column.  If @path is
10405  * %NULL, or points to a node not found in the tree, the @y and @height fields of
10406  * the rectangle will be filled with 0. If @column is %NULL, the @x and @width
10407  * fields will be filled with 0.  The returned rectangle is equivalent to the
10408  * @background_area passed to gtk_cell_renderer_render().  These background
10409  * areas tile to cover the entire bin window.  Contrast with the @cell_area,
10410  * returned by pspp_sheet_view_get_cell_area(), which returns only the cell
10411  * itself, excluding surrounding borders.
10412  *
10413  **/
10414 void
10415 pspp_sheet_view_get_background_area (PsppSheetView        *tree_view,
10416                                    GtkTreePath        *path,
10417                                    PsppSheetViewColumn  *column,
10418                                    GdkRectangle       *rect)
10419 {
10420   int node = -1;
10421
10422   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10423   g_return_if_fail (column == NULL || PSPP_IS_SHEET_VIEW_COLUMN (column));
10424   g_return_if_fail (rect != NULL);
10425
10426   rect->x = 0;
10427   rect->y = 0;
10428   rect->width = 0;
10429   rect->height = 0;
10430
10431   if (path)
10432     {
10433       /* Get vertical coords */
10434
10435       _pspp_sheet_view_find_node (tree_view, path, &node);
10436       if (node < 0)
10437         return;
10438
10439       rect->y = BACKGROUND_FIRST_PIXEL (tree_view, node);
10440
10441       rect->height = ROW_HEIGHT (tree_view);
10442     }
10443
10444   if (column)
10445     {
10446       gint x2 = 0;
10447
10448       pspp_sheet_view_get_background_xrange (tree_view, column, &rect->x, &x2);
10449       rect->width = x2 - rect->x;
10450     }
10451 }
10452
10453 /**
10454  * pspp_sheet_view_get_visible_rect:
10455  * @tree_view: a #PsppSheetView
10456  * @visible_rect: rectangle to fill
10457  *
10458  * Fills @visible_rect with the currently-visible region of the
10459  * buffer, in tree coordinates. Convert to bin_window coordinates with
10460  * pspp_sheet_view_convert_tree_to_bin_window_coords().
10461  * Tree coordinates start at 0,0 for row 0 of the tree, and cover the entire
10462  * scrollable area of the tree.
10463  **/
10464 void
10465 pspp_sheet_view_get_visible_rect (PsppSheetView  *tree_view,
10466                                 GdkRectangle *visible_rect)
10467 {
10468   GtkWidget *widget;
10469
10470   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10471
10472   widget = GTK_WIDGET (tree_view);
10473
10474   if (visible_rect)
10475     {
10476       GtkAllocation allocation;
10477       gtk_widget_get_allocation (widget, &allocation);
10478       visible_rect->x = gtk_adjustment_get_value (tree_view->priv->hadjustment);
10479       visible_rect->y = gtk_adjustment_get_value (tree_view->priv->vadjustment);
10480       visible_rect->width  = allocation.width;
10481       visible_rect->height = allocation.height - TREE_VIEW_HEADER_HEIGHT (tree_view);
10482     }
10483 }
10484
10485 /**
10486  * pspp_sheet_view_widget_to_tree_coords:
10487  * @tree_view: a #PsppSheetView
10488  * @wx: X coordinate relative to bin_window
10489  * @wy: Y coordinate relative to bin_window
10490  * @tx: return location for tree X coordinate
10491  * @ty: return location for tree Y coordinate
10492  *
10493  * Converts bin_window coordinates to coordinates for the
10494  * tree (the full scrollable area of the tree).
10495  *
10496  * Deprecated: 2.12: Due to historial reasons the name of this function is
10497  * incorrect.  For converting coordinates relative to the widget to
10498  * bin_window coordinates, please see
10499  * pspp_sheet_view_convert_widget_to_bin_window_coords().
10500  *
10501  **/
10502 void
10503 pspp_sheet_view_widget_to_tree_coords (PsppSheetView *tree_view,
10504                                       gint         wx,
10505                                       gint         wy,
10506                                       gint        *tx,
10507                                       gint        *ty)
10508 {
10509   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10510
10511   if (tx)
10512     *tx = wx + gtk_adjustment_get_value (tree_view->priv->hadjustment);
10513   if (ty)
10514     *ty = wy + tree_view->priv->dy;
10515 }
10516
10517 /**
10518  * pspp_sheet_view_tree_to_widget_coords:
10519  * @tree_view: a #PsppSheetView
10520  * @tx: tree X coordinate
10521  * @ty: tree Y coordinate
10522  * @wx: return location for X coordinate relative to bin_window
10523  * @wy: return location for Y coordinate relative to bin_window
10524  *
10525  * Converts tree coordinates (coordinates in full scrollable area of the tree)
10526  * to bin_window coordinates.
10527  *
10528  * Deprecated: 2.12: Due to historial reasons the name of this function is
10529  * incorrect.  For converting bin_window coordinates to coordinates relative
10530  * to bin_window, please see
10531  * pspp_sheet_view_convert_bin_window_to_widget_coords().
10532  *
10533  **/
10534 void
10535 pspp_sheet_view_tree_to_widget_coords (PsppSheetView *tree_view,
10536                                      gint         tx,
10537                                      gint         ty,
10538                                      gint        *wx,
10539                                      gint        *wy)
10540 {
10541   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10542
10543   if (wx)
10544     *wx = tx - gtk_adjustment_get_value (tree_view->priv->hadjustment);
10545   if (wy)
10546     *wy = ty - tree_view->priv->dy;
10547 }
10548
10549
10550 /**
10551  * pspp_sheet_view_convert_widget_to_tree_coords:
10552  * @tree_view: a #PsppSheetView
10553  * @wx: X coordinate relative to the widget
10554  * @wy: Y coordinate relative to the widget
10555  * @tx: return location for tree X coordinate
10556  * @ty: return location for tree Y coordinate
10557  *
10558  * Converts widget coordinates to coordinates for the
10559  * tree (the full scrollable area of the tree).
10560  *
10561  * Since: 2.12
10562  **/
10563 void
10564 pspp_sheet_view_convert_widget_to_tree_coords (PsppSheetView *tree_view,
10565                                              gint         wx,
10566                                              gint         wy,
10567                                              gint        *tx,
10568                                              gint        *ty)
10569 {
10570   gint x, y;
10571
10572   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10573
10574   pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view,
10575                                                      wx, wy,
10576                                                      &x, &y);
10577   pspp_sheet_view_convert_bin_window_to_tree_coords (tree_view,
10578                                                    x, y,
10579                                                    tx, ty);
10580 }
10581
10582 /**
10583  * pspp_sheet_view_convert_tree_to_widget_coords:
10584  * @tree_view: a #PsppSheetView
10585  * @tx: X coordinate relative to the tree
10586  * @ty: Y coordinate relative to the tree
10587  * @wx: return location for widget X coordinate
10588  * @wy: return location for widget Y coordinate
10589  *
10590  * Converts tree coordinates (coordinates in full scrollable area of the tree)
10591  * to widget coordinates.
10592  *
10593  * Since: 2.12
10594  **/
10595 void
10596 pspp_sheet_view_convert_tree_to_widget_coords (PsppSheetView *tree_view,
10597                                              gint         tx,
10598                                              gint         ty,
10599                                              gint        *wx,
10600                                              gint        *wy)
10601 {
10602   gint x, y;
10603
10604   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10605
10606   pspp_sheet_view_convert_tree_to_bin_window_coords (tree_view,
10607                                                    tx, ty,
10608                                                    &x, &y);
10609   pspp_sheet_view_convert_bin_window_to_widget_coords (tree_view,
10610                                                      x, y,
10611                                                      wx, wy);
10612 }
10613
10614 /**
10615  * pspp_sheet_view_convert_widget_to_bin_window_coords:
10616  * @tree_view: a #PsppSheetView
10617  * @wx: X coordinate relative to the widget
10618  * @wy: Y coordinate relative to the widget
10619  * @bx: return location for bin_window X coordinate
10620  * @by: return location for bin_window Y coordinate
10621  *
10622  * Converts widget coordinates to coordinates for the bin_window
10623  * (see pspp_sheet_view_get_bin_window()).
10624  *
10625  * Since: 2.12
10626  **/
10627 void
10628 pspp_sheet_view_convert_widget_to_bin_window_coords (PsppSheetView *tree_view,
10629                                                    gint         wx,
10630                                                    gint         wy,
10631                                                    gint        *bx,
10632                                                    gint        *by)
10633 {
10634   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10635
10636   if (bx)
10637     *bx = wx + gtk_adjustment_get_value (tree_view->priv->hadjustment);
10638   if (by)
10639     *by = wy - TREE_VIEW_HEADER_HEIGHT (tree_view);
10640 }
10641
10642 /**
10643  * pspp_sheet_view_convert_bin_window_to_widget_coords:
10644  * @tree_view: a #PsppSheetView
10645  * @bx: bin_window X coordinate
10646  * @by: bin_window Y coordinate
10647  * @wx: return location for widget X coordinate
10648  * @wy: return location for widget Y coordinate
10649  *
10650  * Converts bin_window coordinates (see pspp_sheet_view_get_bin_window())
10651  * to widget relative coordinates.
10652  *
10653  * Since: 2.12
10654  **/
10655 void
10656 pspp_sheet_view_convert_bin_window_to_widget_coords (PsppSheetView *tree_view,
10657                                                    gint         bx,
10658                                                    gint         by,
10659                                                    gint        *wx,
10660                                                    gint        *wy)
10661 {
10662   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10663
10664   if (wx)
10665     *wx = bx - gtk_adjustment_get_value (tree_view->priv->hadjustment);
10666   if (wy)
10667     *wy = by + TREE_VIEW_HEADER_HEIGHT (tree_view);
10668 }
10669
10670 /**
10671  * pspp_sheet_view_convert_tree_to_bin_window_coords:
10672  * @tree_view: a #PsppSheetView
10673  * @tx: tree X coordinate
10674  * @ty: tree Y coordinate
10675  * @bx: return location for X coordinate relative to bin_window
10676  * @by: return location for Y coordinate relative to bin_window
10677  *
10678  * Converts tree coordinates (coordinates in full scrollable area of the tree)
10679  * to bin_window coordinates.
10680  *
10681  * Since: 2.12
10682  **/
10683 void
10684 pspp_sheet_view_convert_tree_to_bin_window_coords (PsppSheetView *tree_view,
10685                                                  gint         tx,
10686                                                  gint         ty,
10687                                                  gint        *bx,
10688                                                  gint        *by)
10689 {
10690   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10691
10692   if (bx)
10693     *bx = tx;
10694   if (by)
10695     *by = ty - tree_view->priv->dy;
10696 }
10697
10698 /**
10699  * pspp_sheet_view_convert_bin_window_to_tree_coords:
10700  * @tree_view: a #PsppSheetView
10701  * @bx: X coordinate relative to bin_window
10702  * @by: Y coordinate relative to bin_window
10703  * @tx: return location for tree X coordinate
10704  * @ty: return location for tree Y coordinate
10705  *
10706  * Converts bin_window coordinates to coordinates for the
10707  * tree (the full scrollable area of the tree).
10708  *
10709  * Since: 2.12
10710  **/
10711 void
10712 pspp_sheet_view_convert_bin_window_to_tree_coords (PsppSheetView *tree_view,
10713                                                  gint         bx,
10714                                                  gint         by,
10715                                                  gint        *tx,
10716                                                  gint        *ty)
10717 {
10718   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10719
10720   if (tx)
10721     *tx = bx;
10722   if (ty)
10723     *ty = by + tree_view->priv->dy;
10724 }
10725
10726
10727
10728 /**
10729  * pspp_sheet_view_get_visible_range:
10730  * @tree_view: A #PsppSheetView
10731  * @start_path: (allow-none): Return location for start of region, or %NULL.
10732  * @end_path: (allow-none): Return location for end of region, or %NULL.
10733  *
10734  * Sets @start_path and @end_path to be the first and last visible path.
10735  * Note that there may be invisible paths in between.
10736  *
10737  * The paths should be freed with gtk_tree_path_free() after use.
10738  *
10739  * Returns: %TRUE, if valid paths were placed in @start_path and @end_path.
10740  *
10741  * Since: 2.8
10742  **/
10743 gboolean
10744 pspp_sheet_view_get_visible_range (PsppSheetView  *tree_view,
10745                                  GtkTreePath **start_path,
10746                                  GtkTreePath **end_path)
10747 {
10748   int node;
10749   gboolean retval;
10750   
10751   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
10752
10753   if (!tree_view->priv->row_count)
10754     return FALSE;
10755
10756   retval = TRUE;
10757
10758   if (start_path)
10759     {
10760       pspp_sheet_view_find_offset (tree_view,
10761                                    TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, 0),
10762                                    &node);
10763       if (node >= 0)
10764         *start_path = _pspp_sheet_view_find_path (tree_view, node);
10765       else
10766         retval = FALSE;
10767     }
10768
10769   if (end_path)
10770     {
10771       gint y;
10772
10773       if (tree_view->priv->height < gtk_adjustment_get_page_size (tree_view->priv->vadjustment))
10774         y = tree_view->priv->height - 1;
10775       else
10776         y = TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, gtk_adjustment_get_page_size (tree_view->priv->vadjustment)) - 1;
10777
10778       pspp_sheet_view_find_offset (tree_view, y, &node);
10779       if (node >= 0)
10780         *end_path = _pspp_sheet_view_find_path (tree_view, node);
10781       else
10782         retval = FALSE;
10783     }
10784
10785   return retval;
10786 }
10787
10788 static void
10789 unset_reorderable (PsppSheetView *tree_view)
10790 {
10791   if (tree_view->priv->reorderable)
10792     {
10793       tree_view->priv->reorderable = FALSE;
10794       g_object_notify (G_OBJECT (tree_view), "reorderable");
10795     }
10796 }
10797
10798 /**
10799  * pspp_sheet_view_enable_model_drag_source:
10800  * @tree_view: a #PsppSheetView
10801  * @start_button_mask: Mask of allowed buttons to start drag
10802  * @targets: the table of targets that the drag will support
10803  * @n_targets: the number of items in @targets
10804  * @actions: the bitmask of possible actions for a drag from this
10805  *    widget
10806  *
10807  * Turns @tree_view into a drag source for automatic DND. Calling this
10808  * method sets #PsppSheetView:reorderable to %FALSE.
10809  **/
10810 void
10811 pspp_sheet_view_enable_model_drag_source (PsppSheetView              *tree_view,
10812                                         GdkModifierType           start_button_mask,
10813                                         const GtkTargetEntry     *targets,
10814                                         gint                      n_targets,
10815                                         GdkDragAction             actions)
10816 {
10817   TreeViewDragInfo *di;
10818
10819   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10820
10821   gtk_drag_source_set (GTK_WIDGET (tree_view),
10822                        0,
10823                        targets,
10824                        n_targets,
10825                        actions);
10826
10827   di = ensure_info (tree_view);
10828
10829   di->start_button_mask = start_button_mask;
10830   di->source_actions = actions;
10831   di->source_set = TRUE;
10832
10833   unset_reorderable (tree_view);
10834 }
10835
10836 /**
10837  * pspp_sheet_view_enable_model_drag_dest:
10838  * @tree_view: a #PsppSheetView
10839  * @targets: the table of targets that the drag will support
10840  * @n_targets: the number of items in @targets
10841  * @actions: the bitmask of possible actions for a drag from this
10842  *    widget
10843  * 
10844  * Turns @tree_view into a drop destination for automatic DND. Calling
10845  * this method sets #PsppSheetView:reorderable to %FALSE.
10846  **/
10847 void
10848 pspp_sheet_view_enable_model_drag_dest (PsppSheetView              *tree_view,
10849                                       const GtkTargetEntry     *targets,
10850                                       gint                      n_targets,
10851                                       GdkDragAction             actions)
10852 {
10853   TreeViewDragInfo *di;
10854
10855   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10856
10857   gtk_drag_dest_set (GTK_WIDGET (tree_view),
10858                      0,
10859                      targets,
10860                      n_targets,
10861                      actions);
10862
10863   di = ensure_info (tree_view);
10864   di->dest_set = TRUE;
10865
10866   unset_reorderable (tree_view);
10867 }
10868
10869 /**
10870  * pspp_sheet_view_unset_rows_drag_source:
10871  * @tree_view: a #PsppSheetView
10872  *
10873  * Undoes the effect of
10874  * pspp_sheet_view_enable_model_drag_source(). Calling this method sets
10875  * #PsppSheetView:reorderable to %FALSE.
10876  **/
10877 void
10878 pspp_sheet_view_unset_rows_drag_source (PsppSheetView *tree_view)
10879 {
10880   TreeViewDragInfo *di;
10881
10882   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10883
10884   di = get_info (tree_view);
10885
10886   if (di)
10887     {
10888       if (di->source_set)
10889         {
10890           gtk_drag_source_unset (GTK_WIDGET (tree_view));
10891           di->source_set = FALSE;
10892         }
10893
10894       if (!di->dest_set && !di->source_set)
10895         remove_info (tree_view);
10896     }
10897   
10898   unset_reorderable (tree_view);
10899 }
10900
10901 /**
10902  * pspp_sheet_view_unset_rows_drag_dest:
10903  * @tree_view: a #PsppSheetView
10904  *
10905  * Undoes the effect of
10906  * pspp_sheet_view_enable_model_drag_dest(). Calling this method sets
10907  * #PsppSheetView:reorderable to %FALSE.
10908  **/
10909 void
10910 pspp_sheet_view_unset_rows_drag_dest (PsppSheetView *tree_view)
10911 {
10912   TreeViewDragInfo *di;
10913
10914   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10915
10916   di = get_info (tree_view);
10917
10918   if (di)
10919     {
10920       if (di->dest_set)
10921         {
10922           gtk_drag_dest_unset (GTK_WIDGET (tree_view));
10923           di->dest_set = FALSE;
10924         }
10925
10926       if (!di->dest_set && !di->source_set)
10927         remove_info (tree_view);
10928     }
10929
10930   unset_reorderable (tree_view);
10931 }
10932
10933 /**
10934  * pspp_sheet_view_set_drag_dest_row:
10935  * @tree_view: a #PsppSheetView
10936  * @path: (allow-none): The path of the row to highlight, or %NULL.
10937  * @pos: Specifies whether to drop before, after or into the row
10938  * 
10939  * Sets the row that is highlighted for feedback.
10940  **/
10941 void
10942 pspp_sheet_view_set_drag_dest_row (PsppSheetView            *tree_view,
10943                                  GtkTreePath            *path,
10944                                  PsppSheetViewDropPosition pos)
10945 {
10946   GtkTreePath *current_dest;
10947
10948   /* Note; this function is exported to allow a custom DND
10949    * implementation, so it can't touch TreeViewDragInfo
10950    */
10951
10952   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10953
10954   current_dest = NULL;
10955
10956   if (tree_view->priv->drag_dest_row)
10957     {
10958       current_dest = gtk_tree_row_reference_get_path (tree_view->priv->drag_dest_row);
10959       gtk_tree_row_reference_free (tree_view->priv->drag_dest_row);
10960     }
10961
10962   /* special case a drop on an empty model */
10963   tree_view->priv->empty_view_drop = 0;
10964
10965   if (pos == PSPP_SHEET_VIEW_DROP_BEFORE && path
10966       && gtk_tree_path_get_depth (path) == 1
10967       && gtk_tree_path_get_indices (path)[0] == 0)
10968     {
10969       gint n_children;
10970
10971       n_children = gtk_tree_model_iter_n_children (tree_view->priv->model,
10972                                                    NULL);
10973
10974       if (!n_children)
10975         tree_view->priv->empty_view_drop = 1;
10976     }
10977
10978   tree_view->priv->drag_dest_pos = pos;
10979
10980   if (path)
10981     {
10982       tree_view->priv->drag_dest_row =
10983         gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view), tree_view->priv->model, path);
10984       pspp_sheet_view_queue_draw_path (tree_view, path, NULL);
10985     }
10986   else
10987     tree_view->priv->drag_dest_row = NULL;
10988
10989   if (current_dest)
10990     {
10991       int node, new_node;
10992
10993       _pspp_sheet_view_find_node (tree_view, current_dest, &node);
10994       _pspp_sheet_view_queue_draw_node (tree_view, node, NULL);
10995
10996       if (node >= 0)
10997         {
10998           new_node = pspp_sheet_view_node_next (tree_view, node);
10999           if (new_node >= 0)
11000             _pspp_sheet_view_queue_draw_node (tree_view, new_node, NULL);
11001
11002           new_node = pspp_sheet_view_node_prev (tree_view, node);
11003           if (new_node >= 0)
11004             _pspp_sheet_view_queue_draw_node (tree_view, new_node, NULL);
11005         }
11006       gtk_tree_path_free (current_dest);
11007     }
11008 }
11009
11010 /**
11011  * pspp_sheet_view_get_drag_dest_row:
11012  * @tree_view: a #PsppSheetView
11013  * @path: (allow-none): Return location for the path of the highlighted row, or %NULL.
11014  * @pos: (allow-none): Return location for the drop position, or %NULL
11015  * 
11016  * Gets information about the row that is highlighted for feedback.
11017  **/
11018 void
11019 pspp_sheet_view_get_drag_dest_row (PsppSheetView              *tree_view,
11020                                  GtkTreePath             **path,
11021                                  PsppSheetViewDropPosition  *pos)
11022 {
11023   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11024
11025   if (path)
11026     {
11027       if (tree_view->priv->drag_dest_row)
11028         *path = gtk_tree_row_reference_get_path (tree_view->priv->drag_dest_row);
11029       else
11030         {
11031           if (tree_view->priv->empty_view_drop)
11032             *path = gtk_tree_path_new_from_indices (0, -1);
11033           else
11034             *path = NULL;
11035         }
11036     }
11037
11038   if (pos)
11039     *pos = tree_view->priv->drag_dest_pos;
11040 }
11041
11042 /**
11043  * pspp_sheet_view_get_dest_row_at_pos:
11044  * @tree_view: a #PsppSheetView
11045  * @drag_x: the position to determine the destination row for
11046  * @drag_y: the position to determine the destination row for
11047  * @path: (allow-none): Return location for the path of the highlighted row, or %NULL.
11048  * @pos: (allow-none): Return location for the drop position, or %NULL
11049  * 
11050  * Determines the destination row for a given position.  @drag_x and
11051  * @drag_y are expected to be in widget coordinates.  This function is only
11052  * meaningful if @tree_view is realized.  Therefore this function will always
11053  * return %FALSE if @tree_view is not realized or does not have a model.
11054  * 
11055  * Return value: whether there is a row at the given position, %TRUE if this
11056  * is indeed the case.
11057  **/
11058 gboolean
11059 pspp_sheet_view_get_dest_row_at_pos (PsppSheetView             *tree_view,
11060                                    gint                     drag_x,
11061                                    gint                     drag_y,
11062                                    GtkTreePath            **path,
11063                                    PsppSheetViewDropPosition *pos)
11064 {
11065   gint cell_y;
11066   gint bin_x, bin_y;
11067   gdouble offset_into_row;
11068   gdouble third;
11069   GdkRectangle cell;
11070   PsppSheetViewColumn *column = NULL;
11071   GtkTreePath *tmp_path = NULL;
11072
11073   /* Note; this function is exported to allow a custom DND
11074    * implementation, so it can't touch TreeViewDragInfo
11075    */
11076
11077   g_return_val_if_fail (tree_view != NULL, FALSE);
11078   g_return_val_if_fail (drag_x >= 0, FALSE);
11079   g_return_val_if_fail (drag_y >= 0, FALSE);
11080
11081   if (path)
11082     *path = NULL;
11083
11084   if (tree_view->priv->bin_window == NULL)
11085     return FALSE;
11086
11087   if (tree_view->priv->row_count == 0)
11088     return FALSE;
11089
11090   /* If in the top third of a row, we drop before that row; if
11091    * in the bottom third, drop after that row; if in the middle,
11092    * and the row has children, drop into the row.
11093    */
11094   pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view, drag_x, drag_y,
11095                                                      &bin_x, &bin_y);
11096
11097   if (!pspp_sheet_view_get_path_at_pos (tree_view,
11098                                       bin_x,
11099                                       bin_y,
11100                                       &tmp_path,
11101                                       &column,
11102                                       NULL,
11103                                       &cell_y))
11104     return FALSE;
11105
11106   pspp_sheet_view_get_background_area (tree_view, tmp_path, column,
11107                                      &cell);
11108
11109   offset_into_row = cell_y;
11110
11111   if (path)
11112     *path = tmp_path;
11113   else
11114     gtk_tree_path_free (tmp_path);
11115
11116   tmp_path = NULL;
11117
11118   third = cell.height / 3.0;
11119
11120   if (pos)
11121     {
11122       if (offset_into_row < third)
11123         {
11124           *pos = PSPP_SHEET_VIEW_DROP_BEFORE;
11125         }
11126       else if (offset_into_row < (cell.height / 2.0))
11127         {
11128           *pos = PSPP_SHEET_VIEW_DROP_INTO_OR_BEFORE;
11129         }
11130       else if (offset_into_row < third * 2.0)
11131         {
11132           *pos = PSPP_SHEET_VIEW_DROP_INTO_OR_AFTER;
11133         }
11134       else
11135         {
11136           *pos = PSPP_SHEET_VIEW_DROP_AFTER;
11137         }
11138     }
11139
11140   return TRUE;
11141 }
11142
11143
11144 #if GTK3_TRANSITION
11145 /* KEEP IN SYNC WITH PSPP_SHEET_VIEW_BIN_EXPOSE */
11146 /**
11147  * pspp_sheet_view_create_row_drag_icon:
11148  * @tree_view: a #PsppSheetView
11149  * @path: a #GtkTreePath in @tree_view
11150  *
11151  * Creates a #GdkPixmap representation of the row at @path.  
11152  * This image is used for a drag icon.
11153  *
11154  * Return value: a newly-allocated pixmap of the drag icon.
11155  **/
11156 GdkPixmap *
11157 pspp_sheet_view_create_row_drag_icon (PsppSheetView  *tree_view,
11158                                     GtkTreePath  *path)
11159 {
11160   GtkTreeIter   iter;
11161   int node;
11162   gint cell_offset;
11163   GList *list;
11164   GdkRectangle background_area;
11165   GdkRectangle expose_area;
11166   GtkWidget *widget;
11167   /* start drawing inside the black outline */
11168   gint x = 1, y = 1;
11169   GdkDrawable *drawable;
11170   gint bin_window_width;
11171   gboolean rtl;
11172
11173   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
11174   g_return_val_if_fail (path != NULL, NULL);
11175
11176   widget = GTK_WIDGET (tree_view);
11177
11178   if (!gtk_widget_get_realized (widget))
11179     return NULL;
11180
11181   _pspp_sheet_view_find_node (tree_view,
11182                             path,
11183                             &node);
11184
11185   if (node < 0)
11186     return NULL;
11187
11188   if (!gtk_tree_model_get_iter (tree_view->priv->model,
11189                                 &iter,
11190                                 path))
11191     return NULL;
11192   
11193   cell_offset = x;
11194
11195   background_area.y = y;
11196   background_area.height = ROW_HEIGHT (tree_view);
11197
11198   bin_window_width = gdk_window_get_width (tree_view->priv->bin_window);
11199
11200   drawable = gdk_pixmap_new (tree_view->priv->bin_window,
11201                              bin_window_width + 2,
11202                              background_area.height + 2,
11203                              -1);
11204
11205   expose_area.x = 0;
11206   expose_area.y = 0;
11207   expose_area.width = bin_window_width + 2;
11208   expose_area.height = background_area.height + 2;
11209
11210 #if GTK3_TRANSITION
11211   gdk_draw_rectangle (drawable,
11212                       widget->style->base_gc [gtk_widget_get_state (widget)],
11213                       TRUE,
11214                       0, 0,
11215                       bin_window_width + 2,
11216                       background_area.height + 2);
11217 #endif
11218
11219   rtl = gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL;
11220
11221   for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns));
11222       list;
11223       list = (rtl ? list->prev : list->next))
11224     {
11225       PsppSheetViewColumn *column = list->data;
11226       GdkRectangle cell_area;
11227       gint vertical_separator;
11228
11229       if (!column->visible)
11230         continue;
11231
11232       pspp_sheet_view_column_cell_set_cell_data (column, tree_view->priv->model, &iter);
11233
11234       background_area.x = cell_offset;
11235       background_area.width = column->width;
11236
11237       gtk_widget_style_get (widget,
11238                             "vertical-separator", &vertical_separator,
11239                             NULL);
11240
11241       cell_area = background_area;
11242
11243       cell_area.y += vertical_separator / 2;
11244       cell_area.height -= vertical_separator;
11245
11246       if (pspp_sheet_view_column_cell_is_visible (column))
11247         _pspp_sheet_view_column_cell_render (column,
11248                                              drawable,
11249                                              &background_area,
11250                                              &cell_area,
11251                                              &expose_area,
11252                                              0);
11253       cell_offset += column->width;
11254     }
11255
11256 #if GTK3_TRANSITION
11257   gdk_draw_rectangle (drawable,
11258                       widget->style->black_gc,
11259                       FALSE,
11260                       0, 0,
11261                       bin_window_width + 1,
11262                       background_area.height + 1);
11263 #endif
11264
11265   return drawable;
11266 }
11267 #endif
11268
11269 /**
11270  * pspp_sheet_view_set_destroy_count_func:
11271  * @tree_view: A #PsppSheetView
11272  * @func: (allow-none): Function to be called when a view row is destroyed, or %NULL
11273  * @data: (allow-none): User data to be passed to @func, or %NULL
11274  * @destroy: (allow-none): Destroy notifier for @data, or %NULL
11275  *
11276  * This function should almost never be used.  It is meant for private use by
11277  * ATK for determining the number of visible children that are removed when a row is deleted.
11278  **/
11279 void
11280 pspp_sheet_view_set_destroy_count_func (PsppSheetView             *tree_view,
11281                                       PsppSheetDestroyCountFunc  func,
11282                                       gpointer                 data,
11283                                       GDestroyNotify           destroy)
11284 {
11285   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11286
11287   if (tree_view->priv->destroy_count_destroy)
11288     tree_view->priv->destroy_count_destroy (tree_view->priv->destroy_count_data);
11289
11290   tree_view->priv->destroy_count_func = func;
11291   tree_view->priv->destroy_count_data = data;
11292   tree_view->priv->destroy_count_destroy = destroy;
11293 }
11294
11295
11296 /*
11297  * Interactive search
11298  */
11299
11300 /**
11301  * pspp_sheet_view_set_enable_search:
11302  * @tree_view: A #PsppSheetView
11303  * @enable_search: %TRUE, if the user can search interactively
11304  *
11305  * If @enable_search is set, then the user can type in text to search through
11306  * the tree interactively (this is sometimes called "typeahead find").
11307  * 
11308  * Note that even if this is %FALSE, the user can still initiate a search 
11309  * using the "start-interactive-search" key binding.
11310  */
11311 void
11312 pspp_sheet_view_set_enable_search (PsppSheetView *tree_view,
11313                                  gboolean     enable_search)
11314 {
11315   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11316
11317   enable_search = !!enable_search;
11318   
11319   if (tree_view->priv->enable_search != enable_search)
11320     {
11321        tree_view->priv->enable_search = enable_search;
11322        g_object_notify (G_OBJECT (tree_view), "enable-search");
11323     }
11324 }
11325
11326 /**
11327  * pspp_sheet_view_get_enable_search:
11328  * @tree_view: A #PsppSheetView
11329  *
11330  * Returns whether or not the tree allows to start interactive searching 
11331  * by typing in text.
11332  *
11333  * Return value: whether or not to let the user search interactively
11334  */
11335 gboolean
11336 pspp_sheet_view_get_enable_search (PsppSheetView *tree_view)
11337 {
11338   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
11339
11340   return tree_view->priv->enable_search;
11341 }
11342
11343
11344 /**
11345  * pspp_sheet_view_get_search_column:
11346  * @tree_view: A #PsppSheetView
11347  *
11348  * Gets the column searched on by the interactive search code.
11349  *
11350  * Return value: the column the interactive search code searches in.
11351  */
11352 gint
11353 pspp_sheet_view_get_search_column (PsppSheetView *tree_view)
11354 {
11355   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), -1);
11356
11357   return (tree_view->priv->search_column);
11358 }
11359
11360 /**
11361  * pspp_sheet_view_set_search_column:
11362  * @tree_view: A #PsppSheetView
11363  * @column: the column of the model to search in, or -1 to disable searching
11364  *
11365  * Sets @column as the column where the interactive search code should
11366  * search in for the current model. 
11367  * 
11368  * If the search column is set, users can use the "start-interactive-search"
11369  * key binding to bring up search popup. The enable-search property controls
11370  * whether simply typing text will also start an interactive search.
11371  *
11372  * Note that @column refers to a column of the current model. The search 
11373  * column is reset to -1 when the model is changed.
11374  */
11375 void
11376 pspp_sheet_view_set_search_column (PsppSheetView *tree_view,
11377                                  gint         column)
11378 {
11379   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11380   g_return_if_fail (column >= -1);
11381
11382   if (tree_view->priv->search_column == column)
11383     return;
11384
11385   tree_view->priv->search_column = column;
11386   g_object_notify (G_OBJECT (tree_view), "search-column");
11387 }
11388
11389 /**
11390  * pspp_sheet_view_get_search_equal_func:
11391  * @tree_view: A #PsppSheetView
11392  *
11393  * Returns the compare function currently in use.
11394  *
11395  * Return value: the currently used compare function for the search code.
11396  */
11397
11398 PsppSheetViewSearchEqualFunc
11399 pspp_sheet_view_get_search_equal_func (PsppSheetView *tree_view)
11400 {
11401   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
11402
11403   return tree_view->priv->search_equal_func;
11404 }
11405
11406 /**
11407  * pspp_sheet_view_set_search_equal_func:
11408  * @tree_view: A #PsppSheetView
11409  * @search_equal_func: the compare function to use during the search
11410  * @search_user_data: (allow-none): user data to pass to @search_equal_func, or %NULL
11411  * @search_destroy: (allow-none): Destroy notifier for @search_user_data, or %NULL
11412  *
11413  * Sets the compare function for the interactive search capabilities; note
11414  * that somewhat like strcmp() returning 0 for equality
11415  * #PsppSheetViewSearchEqualFunc returns %FALSE on matches.
11416  **/
11417 void
11418 pspp_sheet_view_set_search_equal_func (PsppSheetView                *tree_view,
11419                                      PsppSheetViewSearchEqualFunc  search_equal_func,
11420                                      gpointer                    search_user_data,
11421                                      GDestroyNotify              search_destroy)
11422 {
11423   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11424   g_return_if_fail (search_equal_func != NULL);
11425
11426   if (tree_view->priv->search_destroy)
11427     tree_view->priv->search_destroy (tree_view->priv->search_user_data);
11428
11429   tree_view->priv->search_equal_func = search_equal_func;
11430   tree_view->priv->search_user_data = search_user_data;
11431   tree_view->priv->search_destroy = search_destroy;
11432   if (tree_view->priv->search_equal_func == NULL)
11433     tree_view->priv->search_equal_func = pspp_sheet_view_search_equal_func;
11434 }
11435
11436 /**
11437  * pspp_sheet_view_get_search_entry:
11438  * @tree_view: A #PsppSheetView
11439  *
11440  * Returns the #GtkEntry which is currently in use as interactive search
11441  * entry for @tree_view.  In case the built-in entry is being used, %NULL
11442  * will be returned.
11443  *
11444  * Return value: the entry currently in use as search entry.
11445  *
11446  * Since: 2.10
11447  */
11448 GtkEntry *
11449 pspp_sheet_view_get_search_entry (PsppSheetView *tree_view)
11450 {
11451   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
11452
11453   if (tree_view->priv->search_custom_entry_set)
11454     return GTK_ENTRY (tree_view->priv->search_entry);
11455
11456   return NULL;
11457 }
11458
11459 /**
11460  * pspp_sheet_view_set_search_entry:
11461  * @tree_view: A #PsppSheetView
11462  * @entry: (allow-none): the entry the interactive search code of @tree_view should use or %NULL
11463  *
11464  * Sets the entry which the interactive search code will use for this
11465  * @tree_view.  This is useful when you want to provide a search entry
11466  * in our interface at all time at a fixed position.  Passing %NULL for
11467  * @entry will make the interactive search code use the built-in popup
11468  * entry again.
11469  *
11470  * Since: 2.10
11471  */
11472 void
11473 pspp_sheet_view_set_search_entry (PsppSheetView *tree_view,
11474                                 GtkEntry    *entry)
11475 {
11476   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11477   g_return_if_fail (entry == NULL || GTK_IS_ENTRY (entry));
11478
11479   if (tree_view->priv->search_custom_entry_set)
11480     {
11481       if (tree_view->priv->search_entry_changed_id)
11482         {
11483           g_signal_handler_disconnect (tree_view->priv->search_entry,
11484                                        tree_view->priv->search_entry_changed_id);
11485           tree_view->priv->search_entry_changed_id = 0;
11486         }
11487       g_signal_handlers_disconnect_by_func (tree_view->priv->search_entry,
11488                                             G_CALLBACK (pspp_sheet_view_search_key_press_event),
11489                                             tree_view);
11490
11491       g_object_unref (tree_view->priv->search_entry);
11492     }
11493   else if (tree_view->priv->search_window)
11494     {
11495       gtk_widget_destroy (tree_view->priv->search_window);
11496
11497       tree_view->priv->search_window = NULL;
11498     }
11499
11500   if (entry)
11501     {
11502       tree_view->priv->search_entry = g_object_ref (entry);
11503       tree_view->priv->search_custom_entry_set = TRUE;
11504
11505       if (tree_view->priv->search_entry_changed_id == 0)
11506         {
11507           tree_view->priv->search_entry_changed_id =
11508             g_signal_connect (tree_view->priv->search_entry, "changed",
11509                               G_CALLBACK (pspp_sheet_view_search_init),
11510                               tree_view);
11511         }
11512       
11513         g_signal_connect (tree_view->priv->search_entry, "key-press-event",
11514                           G_CALLBACK (pspp_sheet_view_search_key_press_event),
11515                           tree_view);
11516
11517         pspp_sheet_view_search_init (tree_view->priv->search_entry, tree_view);
11518     }
11519   else
11520     {
11521       tree_view->priv->search_entry = NULL;
11522       tree_view->priv->search_custom_entry_set = FALSE;
11523     }
11524 }
11525
11526 /**
11527  * pspp_sheet_view_set_search_position_func:
11528  * @tree_view: A #PsppSheetView
11529  * @func: (allow-none): the function to use to position the search dialog, or %NULL
11530  *    to use the default search position function
11531  * @data: (allow-none): user data to pass to @func, or %NULL
11532  * @destroy: (allow-none): Destroy notifier for @data, or %NULL
11533  *
11534  * Sets the function to use when positioning the search dialog.
11535  *
11536  * Since: 2.10
11537  **/
11538 void
11539 pspp_sheet_view_set_search_position_func (PsppSheetView                   *tree_view,
11540                                         PsppSheetViewSearchPositionFunc  func,
11541                                         gpointer                       user_data,
11542                                         GDestroyNotify                 destroy)
11543 {
11544   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11545
11546   if (tree_view->priv->search_position_destroy)
11547     tree_view->priv->search_position_destroy (tree_view->priv->search_position_user_data);
11548
11549   tree_view->priv->search_position_func = func;
11550   tree_view->priv->search_position_user_data = user_data;
11551   tree_view->priv->search_position_destroy = destroy;
11552   if (tree_view->priv->search_position_func == NULL)
11553     tree_view->priv->search_position_func = pspp_sheet_view_search_position_func;
11554 }
11555
11556 /**
11557  * pspp_sheet_view_get_search_position_func:
11558  * @tree_view: A #PsppSheetView
11559  *
11560  * Returns the positioning function currently in use.
11561  *
11562  * Return value: the currently used function for positioning the search dialog.
11563  *
11564  * Since: 2.10
11565  */
11566 PsppSheetViewSearchPositionFunc
11567 pspp_sheet_view_get_search_position_func (PsppSheetView *tree_view)
11568 {
11569   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
11570
11571   return tree_view->priv->search_position_func;
11572 }
11573
11574
11575 static void
11576 pspp_sheet_view_search_dialog_hide (GtkWidget   *search_dialog,
11577                                   PsppSheetView *tree_view)
11578 {
11579   if (tree_view->priv->disable_popdown)
11580     return;
11581
11582   if (tree_view->priv->search_entry_changed_id)
11583     {
11584       g_signal_handler_disconnect (tree_view->priv->search_entry,
11585                                    tree_view->priv->search_entry_changed_id);
11586       tree_view->priv->search_entry_changed_id = 0;
11587     }
11588   if (tree_view->priv->typeselect_flush_timeout)
11589     {
11590       g_source_remove (tree_view->priv->typeselect_flush_timeout);
11591       tree_view->priv->typeselect_flush_timeout = 0;
11592     }
11593         
11594   if (gtk_widget_get_visible (search_dialog))
11595     {
11596       /* send focus-in event */
11597       send_focus_change (GTK_WIDGET (tree_view->priv->search_entry), FALSE);
11598       gtk_widget_hide (search_dialog);
11599       gtk_entry_set_text (GTK_ENTRY (tree_view->priv->search_entry), "");
11600       send_focus_change (GTK_WIDGET (tree_view), TRUE);
11601     }
11602 }
11603
11604 static void
11605 pspp_sheet_view_search_position_func (PsppSheetView *tree_view,
11606                                     GtkWidget   *search_dialog,
11607                                     gpointer     user_data)
11608 {
11609   gint x, y;
11610   gint tree_x, tree_y;
11611   gint tree_width, tree_height;
11612   GdkWindow *tree_window = gtk_widget_get_window (GTK_WIDGET (tree_view));
11613   GdkScreen *screen = gdk_window_get_screen (tree_window);
11614   GtkRequisition requisition;
11615   gint monitor_num;
11616   GdkRectangle monitor;
11617
11618   monitor_num = gdk_screen_get_monitor_at_window (screen, tree_window);
11619   gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
11620
11621   gtk_widget_realize (search_dialog);
11622
11623   gdk_window_get_origin (tree_window, &tree_x, &tree_y);
11624   tree_width = gdk_window_get_width (tree_window);
11625   tree_height = gdk_window_get_height (tree_window);
11626
11627   gtk_widget_size_request (search_dialog, &requisition);
11628
11629   if (tree_x + tree_width > gdk_screen_get_width (screen))
11630     x = gdk_screen_get_width (screen) - requisition.width;
11631   else if (tree_x + tree_width - requisition.width < 0)
11632     x = 0;
11633   else
11634     x = tree_x + tree_width - requisition.width;
11635
11636   if (tree_y + tree_height + requisition.height > gdk_screen_get_height (screen))
11637     y = gdk_screen_get_height (screen) - requisition.height;
11638   else if (tree_y + tree_height < 0) /* isn't really possible ... */
11639     y = 0;
11640   else
11641     y = tree_y + tree_height;
11642
11643   gtk_window_move (GTK_WINDOW (search_dialog), x, y);
11644 }
11645
11646 static void
11647 pspp_sheet_view_search_disable_popdown (GtkEntry *entry,
11648                                       GtkMenu  *menu,
11649                                       gpointer  data)
11650 {
11651   PsppSheetView *tree_view = (PsppSheetView *)data;
11652
11653   tree_view->priv->disable_popdown = 1;
11654   g_signal_connect (menu, "hide",
11655                     G_CALLBACK (pspp_sheet_view_search_enable_popdown), data);
11656 }
11657
11658 #if GTK3_TRANSITION
11659 /* Because we're visible but offscreen, we just set a flag in the preedit
11660  * callback.
11661  */
11662 static void
11663 pspp_sheet_view_search_preedit_changed (GtkIMContext *im_context,
11664                                       PsppSheetView  *tree_view)
11665 {
11666   tree_view->priv->imcontext_changed = 1;
11667   if (tree_view->priv->typeselect_flush_timeout)
11668     {
11669       g_source_remove (tree_view->priv->typeselect_flush_timeout);
11670       tree_view->priv->typeselect_flush_timeout =
11671         gdk_threads_add_timeout (PSPP_SHEET_VIEW_SEARCH_DIALOG_TIMEOUT,
11672                        (GSourceFunc) pspp_sheet_view_search_entry_flush_timeout,
11673                        tree_view);
11674     }
11675
11676 }
11677 #endif
11678
11679 static void
11680 pspp_sheet_view_search_activate (GtkEntry    *entry,
11681                                PsppSheetView *tree_view)
11682 {
11683   GtkTreePath *path;
11684   int node;
11685
11686   pspp_sheet_view_search_dialog_hide (tree_view->priv->search_window,
11687                                     tree_view);
11688
11689   /* If we have a row selected and it's the cursor row, we activate
11690    * the row XXX */
11691   if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
11692     {
11693       path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
11694       
11695       _pspp_sheet_view_find_node (tree_view, path, &node);
11696       
11697       if (node >= 0 && pspp_sheet_view_node_is_selected (tree_view, node))
11698         pspp_sheet_view_row_activated (tree_view, path, tree_view->priv->focus_column);
11699       
11700       gtk_tree_path_free (path);
11701     }
11702 }
11703
11704 static gboolean
11705 pspp_sheet_view_real_search_enable_popdown (gpointer data)
11706 {
11707   PsppSheetView *tree_view = (PsppSheetView *)data;
11708
11709   tree_view->priv->disable_popdown = 0;
11710
11711   return FALSE;
11712 }
11713
11714 static void
11715 pspp_sheet_view_search_enable_popdown (GtkWidget *widget,
11716                                      gpointer   data)
11717 {
11718   gdk_threads_add_timeout_full (G_PRIORITY_HIGH, 200, pspp_sheet_view_real_search_enable_popdown, g_object_ref (data), g_object_unref);
11719 }
11720
11721 static gboolean
11722 pspp_sheet_view_search_delete_event (GtkWidget *widget,
11723                                    GdkEventAny *event,
11724                                    PsppSheetView *tree_view)
11725 {
11726   g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
11727
11728   pspp_sheet_view_search_dialog_hide (widget, tree_view);
11729
11730   return TRUE;
11731 }
11732
11733 static gboolean
11734 pspp_sheet_view_search_button_press_event (GtkWidget *widget,
11735                                          GdkEventButton *event,
11736                                          PsppSheetView *tree_view)
11737 {
11738   g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
11739
11740   pspp_sheet_view_search_dialog_hide (widget, tree_view);
11741
11742   if (event->window == tree_view->priv->bin_window)
11743     pspp_sheet_view_button_press (GTK_WIDGET (tree_view), event);
11744
11745   return TRUE;
11746 }
11747
11748 static gboolean
11749 pspp_sheet_view_search_scroll_event (GtkWidget *widget,
11750                                    GdkEventScroll *event,
11751                                    PsppSheetView *tree_view)
11752 {
11753   gboolean retval = FALSE;
11754
11755   if (event->direction == GDK_SCROLL_UP)
11756     {
11757       pspp_sheet_view_search_move (widget, tree_view, TRUE);
11758       retval = TRUE;
11759     }
11760   else if (event->direction == GDK_SCROLL_DOWN)
11761     {
11762       pspp_sheet_view_search_move (widget, tree_view, FALSE);
11763       retval = TRUE;
11764     }
11765
11766   /* renew the flush timeout */
11767   if (retval && tree_view->priv->typeselect_flush_timeout
11768       && !tree_view->priv->search_custom_entry_set)
11769     {
11770       g_source_remove (tree_view->priv->typeselect_flush_timeout);
11771       tree_view->priv->typeselect_flush_timeout =
11772         gdk_threads_add_timeout (PSPP_SHEET_VIEW_SEARCH_DIALOG_TIMEOUT,
11773                        (GSourceFunc) pspp_sheet_view_search_entry_flush_timeout,
11774                        tree_view);
11775     }
11776
11777   return retval;
11778 }
11779
11780 static gboolean
11781 pspp_sheet_view_search_key_press_event (GtkWidget *widget,
11782                                       GdkEventKey *event,
11783                                       PsppSheetView *tree_view)
11784 {
11785   gboolean retval = FALSE;
11786
11787   g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
11788   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
11789
11790   /* close window and cancel the search */
11791   if (!tree_view->priv->search_custom_entry_set
11792       && (event->keyval == GDK_Escape ||
11793           event->keyval == GDK_Tab ||
11794             event->keyval == GDK_KP_Tab ||
11795             event->keyval == GDK_ISO_Left_Tab))
11796     {
11797       pspp_sheet_view_search_dialog_hide (widget, tree_view);
11798       return TRUE;
11799     }
11800
11801   /* select previous matching iter */
11802   if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
11803     {
11804       if (!pspp_sheet_view_search_move (widget, tree_view, TRUE))
11805         gtk_widget_error_bell (widget);
11806
11807       retval = TRUE;
11808     }
11809
11810   if (((event->state & (GTK_DEFAULT_ACCEL_MOD_MASK | GDK_SHIFT_MASK)) == (GTK_DEFAULT_ACCEL_MOD_MASK | GDK_SHIFT_MASK))
11811       && (event->keyval == GDK_g || event->keyval == GDK_G))
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   /* select next matching iter */
11820   if (event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
11821     {
11822       if (!pspp_sheet_view_search_move (widget, tree_view, FALSE))
11823         gtk_widget_error_bell (widget);
11824
11825       retval = TRUE;
11826     }
11827
11828   if (((event->state & (GTK_DEFAULT_ACCEL_MOD_MASK | GDK_SHIFT_MASK)) == GTK_DEFAULT_ACCEL_MOD_MASK)
11829       && (event->keyval == GDK_g || event->keyval == GDK_G))
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   /* renew the flush timeout */
11838   if (retval && tree_view->priv->typeselect_flush_timeout
11839       && !tree_view->priv->search_custom_entry_set)
11840     {
11841       g_source_remove (tree_view->priv->typeselect_flush_timeout);
11842       tree_view->priv->typeselect_flush_timeout =
11843         gdk_threads_add_timeout (PSPP_SHEET_VIEW_SEARCH_DIALOG_TIMEOUT,
11844                        (GSourceFunc) pspp_sheet_view_search_entry_flush_timeout,
11845                        tree_view);
11846     }
11847
11848   return retval;
11849 }
11850
11851 /*  this function returns FALSE if there is a search string but
11852  *  nothing was found, and TRUE otherwise.
11853  */
11854 static gboolean
11855 pspp_sheet_view_search_move (GtkWidget   *window,
11856                            PsppSheetView *tree_view,
11857                            gboolean     up)
11858 {
11859   gboolean ret;
11860   gint len;
11861   gint count = 0;
11862   const gchar *text;
11863   GtkTreeIter iter;
11864   GtkTreeModel *model;
11865   PsppSheetSelection *selection;
11866
11867   text = gtk_entry_get_text (GTK_ENTRY (tree_view->priv->search_entry));
11868
11869   g_return_val_if_fail (text != NULL, FALSE);
11870
11871   len = strlen (text);
11872
11873   if (up && tree_view->priv->selected_iter == 1)
11874     return strlen (text) < 1;
11875
11876   len = strlen (text);
11877
11878   if (len < 1)
11879     return TRUE;
11880
11881   model = pspp_sheet_view_get_model (tree_view);
11882   selection = pspp_sheet_view_get_selection (tree_view);
11883
11884   /* search */
11885   pspp_sheet_selection_unselect_all (selection);
11886   if (!gtk_tree_model_get_iter_first (model, &iter))
11887     return TRUE;
11888
11889   ret = pspp_sheet_view_search_iter (model, selection, &iter, text,
11890                                    &count, up?((tree_view->priv->selected_iter) - 1):((tree_view->priv->selected_iter + 1)));
11891
11892   if (ret)
11893     {
11894       /* found */
11895       tree_view->priv->selected_iter += up?(-1):(1);
11896       return TRUE;
11897     }
11898   else
11899     {
11900       /* return to old iter */
11901       count = 0;
11902       gtk_tree_model_get_iter_first (model, &iter);
11903       pspp_sheet_view_search_iter (model, selection,
11904                                  &iter, text,
11905                                  &count, tree_view->priv->selected_iter);
11906       return FALSE;
11907     }
11908 }
11909
11910 static gboolean
11911 pspp_sheet_view_search_equal_func (GtkTreeModel *model,
11912                                  gint          column,
11913                                  const gchar  *key,
11914                                  GtkTreeIter  *iter,
11915                                  gpointer      search_data)
11916 {
11917   gboolean retval = TRUE;
11918   const gchar *str;
11919   gchar *normalized_string;
11920   gchar *normalized_key;
11921   gchar *case_normalized_string = NULL;
11922   gchar *case_normalized_key = NULL;
11923   GValue value = {0,};
11924   GValue transformed = {0,};
11925
11926   gtk_tree_model_get_value (model, iter, column, &value);
11927
11928   g_value_init (&transformed, G_TYPE_STRING);
11929
11930   if (!g_value_transform (&value, &transformed))
11931     {
11932       g_value_unset (&value);
11933       return TRUE;
11934     }
11935
11936   g_value_unset (&value);
11937
11938   str = g_value_get_string (&transformed);
11939   if (!str)
11940     {
11941       g_value_unset (&transformed);
11942       return TRUE;
11943     }
11944
11945   normalized_string = g_utf8_normalize (str, -1, G_NORMALIZE_ALL);
11946   normalized_key = g_utf8_normalize (key, -1, G_NORMALIZE_ALL);
11947
11948   if (normalized_string && normalized_key)
11949     {
11950       case_normalized_string = g_utf8_casefold (normalized_string, -1);
11951       case_normalized_key = g_utf8_casefold (normalized_key, -1);
11952
11953       if (strncmp (case_normalized_key, case_normalized_string, strlen (case_normalized_key)) == 0)
11954         retval = FALSE;
11955     }
11956
11957   g_value_unset (&transformed);
11958   g_free (normalized_key);
11959   g_free (normalized_string);
11960   g_free (case_normalized_key);
11961   g_free (case_normalized_string);
11962
11963   return retval;
11964 }
11965
11966 static gboolean
11967 pspp_sheet_view_search_iter (GtkTreeModel     *model,
11968                              PsppSheetSelection *selection,
11969                              GtkTreeIter      *iter,
11970                              const gchar      *text,
11971                              gint             *count,
11972                              gint              n)
11973 {
11974   int node = -1;
11975   GtkTreePath *path;
11976
11977   PsppSheetView *tree_view = pspp_sheet_selection_get_tree_view (selection);
11978
11979   path = gtk_tree_model_get_path (model, iter);
11980   _pspp_sheet_view_find_node (tree_view, path, &node);
11981
11982   do
11983     {
11984       gboolean done = FALSE;
11985
11986       if (! tree_view->priv->search_equal_func (model, tree_view->priv->search_column, text, iter, tree_view->priv->search_user_data))
11987         {
11988           (*count)++;
11989           if (*count == n)
11990             {
11991               pspp_sheet_view_scroll_to_cell (tree_view, path, NULL,
11992                                               TRUE, 0.5, 0.0);
11993               pspp_sheet_selection_select_iter (selection, iter);
11994               pspp_sheet_view_real_set_cursor (tree_view, path, FALSE, TRUE, 0);
11995
11996               if (path)
11997                 gtk_tree_path_free (path);
11998
11999               return TRUE;
12000             }
12001         }
12002
12003
12004       do
12005         {
12006           node = pspp_sheet_view_node_next (tree_view, node);
12007
12008           if (node >= 0)
12009             {
12010               gboolean has_next;
12011
12012               has_next = gtk_tree_model_iter_next (model, iter);
12013
12014               done = TRUE;
12015               gtk_tree_path_next (path);
12016
12017               /* sanity check */
12018               TREE_VIEW_INTERNAL_ASSERT (has_next, FALSE);
12019             }
12020           else
12021             {
12022               if (path)
12023                 gtk_tree_path_free (path);
12024
12025               /* we've run out of tree, done with this func */
12026               return FALSE;
12027             }
12028         }
12029       while (!done);
12030     }
12031   while (1);
12032
12033   return FALSE;
12034 }
12035
12036 static void
12037 pspp_sheet_view_search_init (GtkWidget   *entry,
12038                            PsppSheetView *tree_view)
12039 {
12040   gint ret;
12041   gint count = 0;
12042   const gchar *text;
12043   GtkTreeIter iter;
12044   GtkTreeModel *model;
12045   PsppSheetSelection *selection;
12046
12047   g_return_if_fail (GTK_IS_ENTRY (entry));
12048   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
12049
12050   text = gtk_entry_get_text (GTK_ENTRY (entry));
12051
12052   model = pspp_sheet_view_get_model (tree_view);
12053   selection = pspp_sheet_view_get_selection (tree_view);
12054
12055   /* search */
12056   pspp_sheet_selection_unselect_all (selection);
12057   if (tree_view->priv->typeselect_flush_timeout
12058       && !tree_view->priv->search_custom_entry_set)
12059     {
12060       g_source_remove (tree_view->priv->typeselect_flush_timeout);
12061       tree_view->priv->typeselect_flush_timeout =
12062         gdk_threads_add_timeout (PSPP_SHEET_VIEW_SEARCH_DIALOG_TIMEOUT,
12063                        (GSourceFunc) pspp_sheet_view_search_entry_flush_timeout,
12064                        tree_view);
12065     }
12066
12067   if (*text == '\0')
12068     return;
12069
12070   if (!gtk_tree_model_get_iter_first (model, &iter))
12071     return;
12072
12073   ret = pspp_sheet_view_search_iter (model, selection,
12074                                    &iter, text,
12075                                    &count, 1);
12076
12077   if (ret)
12078     tree_view->priv->selected_iter = 1;
12079 }
12080
12081 static void
12082 pspp_sheet_view_remove_widget (GtkCellEditable *cell_editable,
12083                              PsppSheetView     *tree_view)
12084 {
12085   if (tree_view->priv->edited_column == NULL)
12086     return;
12087
12088   _pspp_sheet_view_column_stop_editing (tree_view->priv->edited_column);
12089   tree_view->priv->edited_column = NULL;
12090
12091   if (gtk_widget_has_focus (GTK_WIDGET (cell_editable)))
12092     gtk_widget_grab_focus (GTK_WIDGET (tree_view));
12093
12094   g_signal_handlers_disconnect_by_func (cell_editable,
12095                                         pspp_sheet_view_remove_widget,
12096                                         tree_view);
12097   g_signal_handlers_disconnect_by_func (cell_editable,
12098                                         pspp_sheet_view_editable_button_press_event,
12099                                         tree_view);
12100   g_signal_handlers_disconnect_by_func (cell_editable,
12101                                         pspp_sheet_view_editable_clicked,
12102                                         tree_view);
12103
12104   gtk_container_remove (GTK_CONTAINER (tree_view),
12105                         GTK_WIDGET (cell_editable));  
12106
12107   /* FIXME should only redraw a single node */
12108   gtk_widget_queue_draw (GTK_WIDGET (tree_view));
12109 }
12110
12111 static gboolean
12112 pspp_sheet_view_start_editing (PsppSheetView *tree_view,
12113                              GtkTreePath *cursor_path)
12114 {
12115   GtkTreeIter iter;
12116   GdkRectangle background_area;
12117   GdkRectangle cell_area;
12118   GtkCellEditable *editable_widget = NULL;
12119   gchar *path_string;
12120   guint flags = 0; /* can be 0, as the flags are primarily for rendering */
12121   gint retval = FALSE;
12122   int cursor_node;
12123
12124   g_assert (tree_view->priv->focus_column);
12125
12126   if (!gtk_widget_get_realized (GTK_WIDGET (tree_view)))
12127     return FALSE;
12128
12129   _pspp_sheet_view_find_node (tree_view, cursor_path, &cursor_node);
12130   if (cursor_node < 0)
12131     return FALSE;
12132
12133   path_string = gtk_tree_path_to_string (cursor_path);
12134   gtk_tree_model_get_iter (tree_view->priv->model, &iter, cursor_path);
12135
12136   pspp_sheet_view_column_cell_set_cell_data (tree_view->priv->focus_column,
12137                                            tree_view->priv->model,
12138                                            &iter);
12139   pspp_sheet_view_get_background_area (tree_view,
12140                                      cursor_path,
12141                                      tree_view->priv->focus_column,
12142                                      &background_area);
12143   pspp_sheet_view_get_cell_area (tree_view,
12144                                cursor_path,
12145                                tree_view->priv->focus_column,
12146                                &cell_area);
12147
12148   if (_pspp_sheet_view_column_cell_event (tree_view->priv->focus_column,
12149                                         &editable_widget,
12150                                         NULL,
12151                                         path_string,
12152                                         &background_area,
12153                                         &cell_area,
12154                                         flags))
12155     {
12156       retval = TRUE;
12157       if (editable_widget != NULL)
12158         {
12159           gint left, right;
12160           GdkRectangle area;
12161           GtkCellRenderer *cell;
12162
12163           area = cell_area;
12164           cell = _pspp_sheet_view_column_get_edited_cell (tree_view->priv->focus_column);
12165
12166           _pspp_sheet_view_column_get_neighbor_sizes (tree_view->priv->focus_column, cell, &left, &right);
12167
12168           area.x += left;
12169           area.width -= right + left;
12170
12171           pspp_sheet_view_real_start_editing (tree_view,
12172                                             tree_view->priv->focus_column,
12173                                             cursor_path,
12174                                             editable_widget,
12175                                             &area,
12176                                             NULL,
12177                                             flags);
12178         }
12179
12180     }
12181   g_free (path_string);
12182   return retval;
12183 }
12184
12185 static gboolean
12186 pspp_sheet_view_editable_button_press_event (GtkWidget *widget,
12187                                              GdkEventButton *event,
12188                                              PsppSheetView *sheet_view)
12189 {
12190   gint node;
12191
12192   node = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget),
12193                                              "pspp-sheet-view-node"));
12194   return pspp_sheet_view_row_head_clicked (sheet_view,
12195                                            node,
12196                                            sheet_view->priv->edited_column,
12197                                            event);
12198 }
12199
12200 static void
12201 pspp_sheet_view_editable_clicked (GtkButton *button,
12202                                   PsppSheetView *sheet_view)
12203 {
12204   pspp_sheet_view_editable_button_press_event (GTK_WIDGET (button), NULL,
12205                                                sheet_view);
12206 }
12207
12208 static gboolean
12209 is_all_selected (GtkWidget *widget)
12210 {
12211   GtkEntryBuffer *buffer;
12212   gint start_pos, end_pos;
12213
12214   if (!GTK_IS_ENTRY (widget))
12215     return FALSE;
12216
12217   buffer = gtk_entry_get_buffer (GTK_ENTRY (widget));
12218   return (gtk_editable_get_selection_bounds (GTK_EDITABLE (widget),
12219                                              &start_pos, &end_pos)
12220           && start_pos == 0
12221           && end_pos == gtk_entry_buffer_get_length (buffer));
12222 }
12223
12224 static gboolean
12225 is_at_left (GtkWidget *widget)
12226 {
12227   return (GTK_IS_ENTRY (widget)
12228           && gtk_editable_get_position (GTK_EDITABLE (widget)) == 0);
12229 }
12230
12231 static gboolean
12232 is_at_right (GtkWidget *widget)
12233 {
12234   GtkEntryBuffer *buffer;
12235   gint length;
12236
12237   if (!GTK_IS_ENTRY (widget))
12238     return FALSE;
12239
12240   buffer = gtk_entry_get_buffer (GTK_ENTRY (widget));
12241   length = gtk_entry_buffer_get_length (buffer);
12242   return gtk_editable_get_position (GTK_EDITABLE (widget)) == length;
12243 }
12244
12245 static gboolean
12246 pspp_sheet_view_event (GtkWidget *widget,
12247                        GdkEventKey *event,
12248                        PsppSheetView *tree_view)
12249 {
12250   PsppSheetViewColumn *column;
12251   GtkTreePath *path;
12252   gboolean handled;
12253   gboolean cancel;
12254   guint keyval;
12255   gint row;
12256
12257   /* Intercept only key press events.
12258      It would make sense to use "key-press-event" instead of "event", but
12259      GtkEntry attaches its own signal handler to "key-press-event" that runs
12260      before ours and overrides our desired behavior for GDK_Up and GDK_Down.
12261   */
12262   if (event->type != GDK_KEY_PRESS)
12263     return FALSE;
12264
12265   keyval = event->keyval;
12266   cancel = FALSE;
12267   switch (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_MOD1_MASK))
12268     {
12269     case 0:
12270       switch (event->keyval)
12271         {
12272         case GDK_Left:      case GDK_KP_Left:
12273         case GDK_Home:      case GDK_KP_Home:
12274           if (!is_all_selected (widget) && !is_at_left (widget))
12275             return FALSE;
12276           break;
12277
12278         case GDK_Right:     case GDK_KP_Right:
12279         case GDK_End:       case GDK_KP_End:
12280           if (!is_all_selected (widget) && !is_at_right (widget))
12281             return FALSE;
12282           break;
12283
12284         case GDK_Up:        case GDK_KP_Up:
12285         case GDK_Down:      case GDK_KP_Down:
12286           break;
12287
12288         case GDK_Page_Up:   case GDK_KP_Page_Up:
12289         case GDK_Page_Down: case GDK_KP_Page_Down:
12290           break;
12291
12292         case GDK_Escape:
12293           cancel = TRUE;
12294           break;
12295
12296         case GDK_Return:
12297           keyval = GDK_Down;
12298           break;
12299
12300         case GDK_Tab:       case GDK_KP_Tab:
12301         case GDK_ISO_Left_Tab:
12302           keyval = GDK_Tab;
12303           break;
12304
12305         default:
12306           return FALSE;
12307         }
12308       break;
12309
12310     case GDK_SHIFT_MASK:
12311       switch (event->keyval)
12312         {
12313         case GDK_Tab:
12314         case GDK_ISO_Left_Tab:
12315           keyval = GDK_Tab;
12316           break;
12317
12318         default:
12319           return FALSE;
12320         }
12321       break;
12322
12323     case GDK_CONTROL_MASK:
12324       switch (event->keyval)
12325         {
12326         case GDK_Left:      case GDK_KP_Left:
12327           if (!is_all_selected (widget) && !is_at_left (widget))
12328             return FALSE;
12329           break;
12330
12331         case GDK_Right:     case GDK_KP_Right:
12332           if (!is_all_selected (widget) && !is_at_right (widget))
12333             return FALSE;
12334           break;
12335
12336         case GDK_Up:        case GDK_KP_Up:
12337         case GDK_Down:      case GDK_KP_Down:
12338           break;
12339
12340         default:
12341           return FALSE;
12342         }
12343       break;
12344
12345     default:
12346       return FALSE;
12347     }
12348
12349   row = tree_view->priv->edited_row;
12350   column = tree_view->priv->edited_column;
12351   path = gtk_tree_path_new_from_indices (row, -1);
12352
12353   pspp_sheet_view_stop_editing (tree_view, cancel);
12354   gtk_widget_grab_focus (GTK_WIDGET (tree_view));
12355
12356   pspp_sheet_view_set_cursor (tree_view, path, column, FALSE);
12357   gtk_tree_path_free (path);
12358
12359   handled = gtk_binding_set_activate (edit_bindings, keyval, event->state,
12360                                       G_OBJECT (tree_view));
12361   if (handled)
12362     g_signal_stop_emission_by_name (widget, "event");
12363
12364   pspp_sheet_view_get_cursor (tree_view, &path, NULL);
12365   pspp_sheet_view_start_editing (tree_view, path);
12366   gtk_tree_path_free (path);
12367
12368   return handled;
12369 }
12370
12371 static void
12372 pspp_sheet_view_override_cell_keypresses (GtkWidget *widget,
12373                                           gpointer data)
12374 {
12375   PsppSheetView *sheet_view = data;
12376
12377   g_signal_connect (widget, "event",
12378                     G_CALLBACK (pspp_sheet_view_event),
12379                     sheet_view);
12380
12381   if (GTK_IS_CONTAINER (widget))
12382     gtk_container_foreach (GTK_CONTAINER (widget),
12383                            pspp_sheet_view_override_cell_keypresses,
12384                            data);
12385 }
12386
12387 static void
12388 pspp_sheet_view_real_start_editing (PsppSheetView       *tree_view,
12389                                   PsppSheetViewColumn *column,
12390                                   GtkTreePath       *path,
12391                                   GtkCellEditable   *cell_editable,
12392                                   GdkRectangle      *cell_area,
12393                                   GdkEvent          *event,
12394                                   guint              flags)
12395 {
12396   PsppSheetSelectionMode mode = pspp_sheet_selection_get_mode (tree_view->priv->selection);
12397   gint pre_val = gtk_adjustment_get_value (tree_view->priv->vadjustment);
12398   gint row;
12399
12400   g_return_if_fail (gtk_tree_path_get_depth (path) == 1);
12401
12402   tree_view->priv->edited_column = column;
12403   _pspp_sheet_view_column_start_editing (column, GTK_CELL_EDITABLE (cell_editable));
12404
12405   row = gtk_tree_path_get_indices (path)[0];
12406   tree_view->priv->edited_row = row;
12407   pspp_sheet_view_real_set_cursor (tree_view, path, FALSE, TRUE, 0);
12408   cell_area->y += pre_val - (int)gtk_adjustment_get_value (tree_view->priv->vadjustment);
12409
12410   pspp_sheet_selection_unselect_all_columns (tree_view->priv->selection);
12411   pspp_sheet_selection_select_column (tree_view->priv->selection, column);
12412   tree_view->priv->anchor_column = column;
12413
12414   PSPP_SHEET_VIEW_SET_FLAG (tree_view, PSPP_SHEET_VIEW_DRAW_KEYFOCUS);
12415
12416   pspp_sheet_view_put (tree_view,
12417                        GTK_WIDGET (cell_editable),
12418                        path,
12419                        column);
12420
12421   gtk_cell_editable_start_editing (GTK_CELL_EDITABLE (cell_editable),
12422                                    (GdkEvent *)event);
12423
12424   gtk_widget_grab_focus (GTK_WIDGET (cell_editable));
12425   g_signal_connect (cell_editable, "remove-widget",
12426                     G_CALLBACK (pspp_sheet_view_remove_widget), tree_view);
12427   if (mode == PSPP_SHEET_SELECTION_RECTANGLE && column->row_head &&
12428       GTK_IS_BUTTON (cell_editable))
12429     {
12430       g_signal_connect (cell_editable, "button-press-event",
12431                         G_CALLBACK (pspp_sheet_view_editable_button_press_event),
12432                         tree_view);
12433       g_object_set_data (G_OBJECT (cell_editable), "pspp-sheet-view-node",
12434                          GINT_TO_POINTER (row));
12435       g_signal_connect (cell_editable, "clicked",
12436                         G_CALLBACK (pspp_sheet_view_editable_clicked),
12437                         tree_view);
12438     }
12439
12440   pspp_sheet_view_override_cell_keypresses (GTK_WIDGET (cell_editable),
12441                                             tree_view);
12442 }
12443
12444 void
12445 pspp_sheet_view_stop_editing (PsppSheetView *tree_view,
12446                               gboolean     cancel_editing)
12447 {
12448   PsppSheetViewColumn *column;
12449   GtkCellRenderer *cell;
12450
12451   if (tree_view->priv->edited_column == NULL)
12452     return;
12453
12454   /*
12455    * This is very evil. We need to do this, because
12456    * gtk_cell_editable_editing_done may trigger pspp_sheet_view_row_changed
12457    * later on. If pspp_sheet_view_row_changed notices
12458    * tree_view->priv->edited_column != NULL, it'll call
12459    * pspp_sheet_view_stop_editing again. Bad things will happen then.
12460    *
12461    * Please read that again if you intend to modify anything here.
12462    */
12463
12464   column = tree_view->priv->edited_column;
12465   tree_view->priv->edited_column = NULL;
12466
12467   cell = _pspp_sheet_view_column_get_edited_cell (column);
12468   gtk_cell_renderer_stop_editing (cell, cancel_editing);
12469
12470   if (!cancel_editing)
12471     gtk_cell_editable_editing_done (column->editable_widget);
12472
12473   tree_view->priv->edited_column = column;
12474
12475   gtk_cell_editable_remove_widget (column->editable_widget);
12476 }
12477
12478
12479 /**
12480  * pspp_sheet_view_set_hover_selection:
12481  * @tree_view: a #PsppSheetView
12482  * @hover: %TRUE to enable hover selection mode
12483  *
12484  * Enables of disables the hover selection mode of @tree_view.
12485  * Hover selection makes the selected row follow the pointer.
12486  * Currently, this works only for the selection modes 
12487  * %PSPP_SHEET_SELECTION_SINGLE and %PSPP_SHEET_SELECTION_BROWSE.
12488  * 
12489  * Since: 2.6
12490  **/
12491 void     
12492 pspp_sheet_view_set_hover_selection (PsppSheetView *tree_view,
12493                                    gboolean     hover)
12494 {
12495   hover = hover != FALSE;
12496
12497   if (hover != tree_view->priv->hover_selection)
12498     {
12499       tree_view->priv->hover_selection = hover;
12500
12501       g_object_notify (G_OBJECT (tree_view), "hover-selection");
12502     }
12503 }
12504
12505 /**
12506  * pspp_sheet_view_get_hover_selection:
12507  * @tree_view: a #PsppSheetView
12508  * 
12509  * Returns whether hover selection mode is turned on for @tree_view.
12510  * 
12511  * Return value: %TRUE if @tree_view is in hover selection mode
12512  *
12513  * Since: 2.6 
12514  **/
12515 gboolean 
12516 pspp_sheet_view_get_hover_selection (PsppSheetView *tree_view)
12517 {
12518   return tree_view->priv->hover_selection;
12519 }
12520
12521 /**
12522  * pspp_sheet_view_set_rubber_banding:
12523  * @tree_view: a #PsppSheetView
12524  * @enable: %TRUE to enable rubber banding
12525  *
12526  * Enables or disables rubber banding in @tree_view.  If the selection mode is
12527  * #PSPP_SHEET_SELECTION_MULTIPLE or #PSPP_SHEET_SELECTION_RECTANGLE, rubber
12528  * banding will allow the user to select multiple rows by dragging the mouse.
12529  * 
12530  * Since: 2.10
12531  **/
12532 void
12533 pspp_sheet_view_set_rubber_banding (PsppSheetView *tree_view,
12534                                   gboolean     enable)
12535 {
12536   enable = enable != FALSE;
12537
12538   if (enable != tree_view->priv->rubber_banding_enable)
12539     {
12540       tree_view->priv->rubber_banding_enable = enable;
12541
12542       g_object_notify (G_OBJECT (tree_view), "rubber-banding");
12543     }
12544 }
12545
12546 /**
12547  * pspp_sheet_view_get_rubber_banding:
12548  * @tree_view: a #PsppSheetView
12549  * 
12550  * Returns whether rubber banding is turned on for @tree_view.  If the
12551  * selection mode is #PSPP_SHEET_SELECTION_MULTIPLE or
12552  * #PSPP_SHEET_SELECTION_RECTANGLE, rubber banding will allow the user to
12553  * select multiple rows by dragging the mouse.
12554  * 
12555  * Return value: %TRUE if rubber banding in @tree_view is enabled.
12556  *
12557  * Since: 2.10
12558  **/
12559 gboolean
12560 pspp_sheet_view_get_rubber_banding (PsppSheetView *tree_view)
12561 {
12562   return tree_view->priv->rubber_banding_enable;
12563 }
12564
12565 /**
12566  * pspp_sheet_view_is_rubber_banding_active:
12567  * @tree_view: a #PsppSheetView
12568  * 
12569  * Returns whether a rubber banding operation is currently being done
12570  * in @tree_view.
12571  *
12572  * Return value: %TRUE if a rubber banding operation is currently being
12573  * done in @tree_view.
12574  *
12575  * Since: 2.12
12576  **/
12577 gboolean
12578 pspp_sheet_view_is_rubber_banding_active (PsppSheetView *tree_view)
12579 {
12580   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
12581
12582   if (tree_view->priv->rubber_banding_enable
12583       && tree_view->priv->rubber_band_status == RUBBER_BAND_ACTIVE)
12584     return TRUE;
12585
12586   return FALSE;
12587 }
12588
12589 static void
12590 pspp_sheet_view_grab_notify (GtkWidget *widget,
12591                            gboolean   was_grabbed)
12592 {
12593   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
12594
12595   tree_view->priv->in_grab = !was_grabbed;
12596
12597   if (!was_grabbed)
12598     {
12599       tree_view->priv->pressed_button = -1;
12600
12601       if (tree_view->priv->rubber_band_status)
12602         pspp_sheet_view_stop_rubber_band (tree_view);
12603     }
12604 }
12605
12606 static void
12607 pspp_sheet_view_state_changed (GtkWidget      *widget,
12608                              GtkStateType    previous_state)
12609 {
12610   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
12611
12612   if (gtk_widget_get_realized (widget))
12613     {
12614       GtkStyle *style = gtk_widget_get_style (widget);
12615       gdk_window_set_background (tree_view->priv->bin_window, &style->base[gtk_widget_get_state (widget)]);
12616     }
12617
12618   gtk_widget_queue_draw (widget);
12619 }
12620
12621 /**
12622  * pspp_sheet_view_get_grid_lines:
12623  * @tree_view: a #PsppSheetView
12624  *
12625  * Returns which grid lines are enabled in @tree_view.
12626  *
12627  * Return value: a #PsppSheetViewGridLines value indicating which grid lines
12628  * are enabled.
12629  *
12630  * Since: 2.10
12631  */
12632 PsppSheetViewGridLines
12633 pspp_sheet_view_get_grid_lines (PsppSheetView *tree_view)
12634 {
12635   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), 0);
12636
12637   return tree_view->priv->grid_lines;
12638 }
12639
12640 /**
12641  * pspp_sheet_view_set_grid_lines:
12642  * @tree_view: a #PsppSheetView
12643  * @grid_lines: a #PsppSheetViewGridLines value indicating which grid lines to
12644  * enable.
12645  *
12646  * Sets which grid lines to draw in @tree_view.
12647  *
12648  * Since: 2.10
12649  */
12650 void
12651 pspp_sheet_view_set_grid_lines (PsppSheetView           *tree_view,
12652                               PsppSheetViewGridLines   grid_lines)
12653 {
12654   PsppSheetViewPrivate *priv;
12655   PsppSheetViewGridLines old_grid_lines;
12656
12657   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
12658
12659   priv = tree_view->priv;
12660
12661   old_grid_lines = priv->grid_lines;
12662   priv->grid_lines = grid_lines;
12663   
12664   if (old_grid_lines != grid_lines)
12665     {
12666       gtk_widget_queue_draw (GTK_WIDGET (tree_view));
12667       
12668       g_object_notify (G_OBJECT (tree_view), "enable-grid-lines");
12669     }
12670 }
12671
12672 /**
12673  * pspp_sheet_view_get_special_cells:
12674  * @tree_view: a #PsppSheetView
12675  *
12676  * Returns which grid lines are enabled in @tree_view.
12677  *
12678  * Return value: a #PsppSheetViewSpecialCells value indicating whether rows in
12679  * the sheet view contain special cells.
12680  */
12681 PsppSheetViewSpecialCells
12682 pspp_sheet_view_get_special_cells (PsppSheetView *tree_view)
12683 {
12684   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), 0);
12685
12686   return tree_view->priv->special_cells;
12687 }
12688
12689 /**
12690  * pspp_sheet_view_set_special_cells:
12691  * @tree_view: a #PsppSheetView
12692  * @special_cells: a #PsppSheetViewSpecialCells value indicating whether rows in
12693  * the sheet view contain special cells.
12694  *
12695  * Sets whether rows in the sheet view contain special cells, controlling the
12696  * rendering of row selections.
12697  */
12698 void
12699 pspp_sheet_view_set_special_cells (PsppSheetView           *tree_view,
12700                               PsppSheetViewSpecialCells   special_cells)
12701 {
12702   PsppSheetViewPrivate *priv;
12703
12704   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
12705
12706   priv = tree_view->priv;
12707
12708   if (priv->special_cells != special_cells)
12709     {
12710       priv->special_cells = special_cells;
12711       gtk_widget_queue_draw (GTK_WIDGET (tree_view));
12712       g_object_notify (G_OBJECT (tree_view), "special-cells");
12713     }
12714 }
12715
12716 int
12717 pspp_sheet_view_get_fixed_height (const PsppSheetView *tree_view)
12718 {
12719   /* XXX (re)calculate fixed_height if necessary */
12720   return tree_view->priv->fixed_height;
12721 }
12722
12723 void
12724 pspp_sheet_view_set_fixed_height (PsppSheetView *tree_view,
12725                                   int fixed_height)
12726 {
12727   g_return_if_fail (fixed_height > 0);
12728
12729   if (tree_view->priv->fixed_height != fixed_height)
12730     {
12731       tree_view->priv->fixed_height = fixed_height;
12732       g_object_notify (G_OBJECT (tree_view), "fixed-height");
12733     }
12734   if (!tree_view->priv->fixed_height_set)
12735     {
12736       tree_view->priv->fixed_height_set = TRUE;
12737       g_object_notify (G_OBJECT (tree_view), "fixed-height-set");
12738     }
12739 }
12740
12741 /**
12742  * pspp_sheet_view_set_tooltip_row:
12743  * @tree_view: a #PsppSheetView
12744  * @tooltip: a #GtkTooltip
12745  * @path: a #GtkTreePath
12746  *
12747  * Sets the tip area of @tooltip to be the area covered by the row at @path.
12748  * See also pspp_sheet_view_set_tooltip_column() for a simpler alternative.
12749  * See also gtk_tooltip_set_tip_area().
12750  *
12751  * Since: 2.12
12752  */
12753 void
12754 pspp_sheet_view_set_tooltip_row (PsppSheetView *tree_view,
12755                                GtkTooltip  *tooltip,
12756                                GtkTreePath *path)
12757 {
12758   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
12759   g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
12760
12761   pspp_sheet_view_set_tooltip_cell (tree_view, tooltip, path, NULL, NULL);
12762 }
12763
12764 /**
12765  * pspp_sheet_view_set_tooltip_cell:
12766  * @tree_view: a #PsppSheetView
12767  * @tooltip: a #GtkTooltip
12768  * @path: (allow-none): a #GtkTreePath or %NULL
12769  * @column: (allow-none): a #PsppSheetViewColumn or %NULL
12770  * @cell: (allow-none): a #GtkCellRenderer or %NULL
12771  *
12772  * Sets the tip area of @tooltip to the area @path, @column and @cell have
12773  * in common.  For example if @path is %NULL and @column is set, the tip
12774  * area will be set to the full area covered by @column.  See also
12775  * gtk_tooltip_set_tip_area().
12776  *
12777  * See also pspp_sheet_view_set_tooltip_column() for a simpler alternative.
12778  *
12779  * Since: 2.12
12780  */
12781 void
12782 pspp_sheet_view_set_tooltip_cell (PsppSheetView       *tree_view,
12783                                 GtkTooltip        *tooltip,
12784                                 GtkTreePath       *path,
12785                                 PsppSheetViewColumn *column,
12786                                 GtkCellRenderer   *cell)
12787 {
12788   GdkRectangle rect;
12789
12790   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
12791   g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
12792   g_return_if_fail (column == NULL || PSPP_IS_SHEET_VIEW_COLUMN (column));
12793   g_return_if_fail (cell == NULL || GTK_IS_CELL_RENDERER (cell));
12794
12795   /* Determine x values. */
12796   if (column && cell)
12797     {
12798       GdkRectangle tmp;
12799       gint start, width;
12800
12801       pspp_sheet_view_get_cell_area (tree_view, path, column, &tmp);
12802       pspp_sheet_view_column_cell_get_position (column, cell, &start, &width);
12803
12804       pspp_sheet_view_convert_bin_window_to_widget_coords (tree_view,
12805                                                          tmp.x + start, 0,
12806                                                          &rect.x, NULL);
12807       rect.width = width;
12808     }
12809   else if (column)
12810     {
12811       GdkRectangle tmp;
12812
12813       pspp_sheet_view_get_background_area (tree_view, NULL, column, &tmp);
12814       pspp_sheet_view_convert_bin_window_to_widget_coords (tree_view,
12815                                                          tmp.x, 0,
12816                                                          &rect.x, NULL);
12817       rect.width = tmp.width;
12818     }
12819   else
12820     {
12821       GtkAllocation allocation;
12822       gtk_widget_get_allocation (GTK_WIDGET (tree_view), &allocation);
12823       rect.x = 0;
12824       rect.width = allocation.width;
12825     }
12826
12827   /* Determine y values. */
12828   if (path)
12829     {
12830       GdkRectangle tmp;
12831
12832       pspp_sheet_view_get_background_area (tree_view, path, NULL, &tmp);
12833       pspp_sheet_view_convert_bin_window_to_widget_coords (tree_view,
12834                                                          0, tmp.y,
12835                                                          NULL, &rect.y);
12836       rect.height = tmp.height;
12837     }
12838   else
12839     {
12840       rect.y = 0;
12841       rect.height = gtk_adjustment_get_page_size (tree_view->priv->vadjustment);
12842     }
12843
12844   gtk_tooltip_set_tip_area (tooltip, &rect);
12845 }
12846
12847 /**
12848  * pspp_sheet_view_get_tooltip_context:
12849  * @tree_view: a #PsppSheetView
12850  * @x: the x coordinate (relative to widget coordinates)
12851  * @y: the y coordinate (relative to widget coordinates)
12852  * @keyboard_tip: whether this is a keyboard tooltip or not
12853  * @model: (allow-none): a pointer to receive a #GtkTreeModel or %NULL
12854  * @path: (allow-none): a pointer to receive a #GtkTreePath or %NULL
12855  * @iter: (allow-none): a pointer to receive a #GtkTreeIter or %NULL
12856  *
12857  * This function is supposed to be used in a #GtkWidget::query-tooltip
12858  * signal handler for #PsppSheetView.  The @x, @y and @keyboard_tip values
12859  * which are received in the signal handler, should be passed to this
12860  * function without modification.
12861  *
12862  * The return value indicates whether there is a tree view row at the given
12863  * coordinates (%TRUE) or not (%FALSE) for mouse tooltips.  For keyboard
12864  * tooltips the row returned will be the cursor row.  When %TRUE, then any of
12865  * @model, @path and @iter which have been provided will be set to point to
12866  * that row and the corresponding model.  @x and @y will always be converted
12867  * to be relative to @tree_view's bin_window if @keyboard_tooltip is %FALSE.
12868  *
12869  * Return value: whether or not the given tooltip context points to a row.
12870  *
12871  * Since: 2.12
12872  */
12873 gboolean
12874 pspp_sheet_view_get_tooltip_context (PsppSheetView   *tree_view,
12875                                    gint          *x,
12876                                    gint          *y,
12877                                    gboolean       keyboard_tip,
12878                                    GtkTreeModel **model,
12879                                    GtkTreePath  **path,
12880                                    GtkTreeIter   *iter)
12881 {
12882   GtkTreePath *tmppath = NULL;
12883
12884   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
12885   g_return_val_if_fail (x != NULL, FALSE);
12886   g_return_val_if_fail (y != NULL, FALSE);
12887
12888   if (keyboard_tip)
12889     {
12890       pspp_sheet_view_get_cursor (tree_view, &tmppath, NULL);
12891
12892       if (!tmppath)
12893         return FALSE;
12894     }
12895   else
12896     {
12897       pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view, *x, *y,
12898                                                          x, y);
12899
12900       if (!pspp_sheet_view_get_path_at_pos (tree_view, *x, *y,
12901                                           &tmppath, NULL, NULL, NULL))
12902         return FALSE;
12903     }
12904
12905   if (model)
12906     *model = pspp_sheet_view_get_model (tree_view);
12907
12908   if (iter)
12909     gtk_tree_model_get_iter (pspp_sheet_view_get_model (tree_view),
12910                              iter, tmppath);
12911
12912   if (path)
12913     *path = tmppath;
12914   else
12915     gtk_tree_path_free (tmppath);
12916
12917   return TRUE;
12918 }
12919
12920 static gboolean
12921 pspp_sheet_view_set_tooltip_query_cb (GtkWidget  *widget,
12922                                     gint        x,
12923                                     gint        y,
12924                                     gboolean    keyboard_tip,
12925                                     GtkTooltip *tooltip,
12926                                     gpointer    data)
12927 {
12928   GValue value = { 0, };
12929   GValue transformed = { 0, };
12930   GtkTreeIter iter;
12931   GtkTreePath *path;
12932   GtkTreeModel *model;
12933   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
12934
12935   if (!pspp_sheet_view_get_tooltip_context (PSPP_SHEET_VIEW (widget),
12936                                           &x, &y,
12937                                           keyboard_tip,
12938                                           &model, &path, &iter))
12939     return FALSE;
12940
12941   gtk_tree_model_get_value (model, &iter,
12942                             tree_view->priv->tooltip_column, &value);
12943
12944   g_value_init (&transformed, G_TYPE_STRING);
12945
12946   if (!g_value_transform (&value, &transformed))
12947     {
12948       g_value_unset (&value);
12949       gtk_tree_path_free (path);
12950
12951       return FALSE;
12952     }
12953
12954   g_value_unset (&value);
12955
12956   if (!g_value_get_string (&transformed))
12957     {
12958       g_value_unset (&transformed);
12959       gtk_tree_path_free (path);
12960
12961       return FALSE;
12962     }
12963
12964   gtk_tooltip_set_markup (tooltip, g_value_get_string (&transformed));
12965   pspp_sheet_view_set_tooltip_row (tree_view, tooltip, path);
12966
12967   gtk_tree_path_free (path);
12968   g_value_unset (&transformed);
12969
12970   return TRUE;
12971 }
12972
12973 /**
12974  * pspp_sheet_view_set_tooltip_column:
12975  * @tree_view: a #PsppSheetView
12976  * @column: an integer, which is a valid column number for @tree_view's model
12977  *
12978  * If you only plan to have simple (text-only) tooltips on full rows, you
12979  * can use this function to have #PsppSheetView handle these automatically
12980  * for you. @column should be set to the column in @tree_view's model
12981  * containing the tooltip texts, or -1 to disable this feature.
12982  *
12983  * When enabled, #GtkWidget::has-tooltip will be set to %TRUE and
12984  * @tree_view will connect a #GtkWidget::query-tooltip signal handler.
12985  *
12986  * Note that the signal handler sets the text with gtk_tooltip_set_markup(),
12987  * so &amp;, &lt;, etc have to be escaped in the text.
12988  *
12989  * Since: 2.12
12990  */
12991 void
12992 pspp_sheet_view_set_tooltip_column (PsppSheetView *tree_view,
12993                                   gint         column)
12994 {
12995   g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
12996
12997   if (column == tree_view->priv->tooltip_column)
12998     return;
12999
13000   if (column == -1)
13001     {
13002       g_signal_handlers_disconnect_by_func (tree_view,
13003                                             pspp_sheet_view_set_tooltip_query_cb,
13004                                             NULL);
13005       gtk_widget_set_has_tooltip (GTK_WIDGET (tree_view), FALSE);
13006     }
13007   else
13008     {
13009       if (tree_view->priv->tooltip_column == -1)
13010         {
13011           g_signal_connect (tree_view, "query-tooltip",
13012                             G_CALLBACK (pspp_sheet_view_set_tooltip_query_cb), NULL);
13013           gtk_widget_set_has_tooltip (GTK_WIDGET (tree_view), TRUE);
13014         }
13015     }
13016
13017   tree_view->priv->tooltip_column = column;
13018   g_object_notify (G_OBJECT (tree_view), "tooltip-column");
13019 }
13020
13021 /**
13022  * pspp_sheet_view_get_tooltip_column:
13023  * @tree_view: a #PsppSheetView
13024  *
13025  * Returns the column of @tree_view's model which is being used for
13026  * displaying tooltips on @tree_view's rows.
13027  *
13028  * Return value: the index of the tooltip column that is currently being
13029  * used, or -1 if this is disabled.
13030  *
13031  * Since: 2.12
13032  */
13033 gint
13034 pspp_sheet_view_get_tooltip_column (PsppSheetView *tree_view)
13035 {
13036   g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), 0);
13037
13038   return tree_view->priv->tooltip_column;
13039 }
13040
13041 gboolean
13042 _gtk_boolean_handled_accumulator (GSignalInvocationHint *ihint,
13043                                   GValue                *return_accu,
13044                                   const GValue          *handler_return,
13045                                   gpointer               dummy)
13046 {
13047   gboolean continue_emission;
13048   gboolean signal_handled;
13049   
13050   signal_handled = g_value_get_boolean (handler_return);
13051   g_value_set_boolean (return_accu, signal_handled);
13052   continue_emission = !signal_handled;
13053   
13054   return continue_emission;
13055 }
13056
13057
13058 GType
13059 pspp_sheet_view_grid_lines_get_type (void)
13060 {
13061     static GType etype = 0;
13062     if (G_UNLIKELY(etype == 0)) {
13063         static const GEnumValue values[] = {
13064             { PSPP_SHEET_VIEW_GRID_LINES_NONE, "PSPP_SHEET_VIEW_GRID_LINES_NONE", "none" },
13065             { PSPP_SHEET_VIEW_GRID_LINES_HORIZONTAL, "PSPP_SHEET_VIEW_GRID_LINES_HORIZONTAL", "horizontal" },
13066             { PSPP_SHEET_VIEW_GRID_LINES_VERTICAL, "PSPP_SHEET_VIEW_GRID_LINES_VERTICAL", "vertical" },
13067             { PSPP_SHEET_VIEW_GRID_LINES_BOTH, "PSPP_SHEET_VIEW_GRID_LINES_BOTH", "both" },
13068             { 0, NULL, NULL }
13069         };
13070         etype = g_enum_register_static (g_intern_static_string ("PsppSheetViewGridLines"), values);
13071     }
13072     return etype;
13073 }
13074
13075 GType
13076 pspp_sheet_view_special_cells_get_type (void)
13077 {
13078     static GType etype = 0;
13079     if (G_UNLIKELY(etype == 0)) {
13080         static const GEnumValue values[] = {
13081             { PSPP_SHEET_VIEW_SPECIAL_CELLS_DETECT, "PSPP_SHEET_VIEW_SPECIAL_CELLS_DETECT", "detect" },
13082             { PSPP_SHEET_VIEW_SPECIAL_CELLS_YES, "PSPP_SHEET_VIEW_SPECIAL_CELLS_YES", "yes" },
13083             { PSPP_SHEET_VIEW_SPECIAL_CELLS_NO, "PSPP_SHEET_VIEW_SPECIAL_CELLS_NO", "no" },
13084             { 0, NULL, NULL }
13085         };
13086         etype = g_enum_register_static (g_intern_static_string ("PsppSheetViewSpecialCells"), values);
13087     }
13088     return etype;
13089 }