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