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