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