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