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