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