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