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