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