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