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