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