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