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