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