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