fixed initial show of names in header window. Variable names only showed up after...
[pspp.git] / src / ui / gui / pspp-sheet-view.c
blob7c80ec0c0284938e0d356d283a01e43e395b780e
1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2011, 2012, 2013 Free Software Foundation, Inc.
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.
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.
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/>. */
17 /* gtktreeview.c
18 * Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford <jrb@redhat.com>
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.
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.
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.
36 #include <config.h>
38 #include "ui/gui/pspp-sheet-private.h"
40 #include <gtk/gtk.h>
41 #include <gdk/gdk.h>
42 #include <gdk/gdkkeysyms.h>
43 #include <gdk/gdkkeysyms-compat.h>
44 #include <string.h>
46 #include "ui/gui/psppire-marshal.h"
47 #include "ui/gui/pspp-sheet-selection.h"
49 #define P_(STRING) STRING
50 #define GTK_PARAM_READABLE G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
51 #define GTK_PARAM_READWRITE G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
53 /* Many keyboard shortcuts for Mac are the same as for X
54 * except they use Command key instead of Control (e.g. Cut,
55 * Copy, Paste). This symbol is for those simple cases. */
56 #ifndef GDK_WINDOWING_QUARTZ
57 #define GTK_DEFAULT_ACCEL_MOD_MASK GDK_CONTROL_MASK
58 #else
59 #define GTK_DEFAULT_ACCEL_MOD_MASK GDK_META_MASK
60 #endif
62 #define PSPP_SHEET_VIEW_PRIORITY_VALIDATE (GDK_PRIORITY_REDRAW + 5)
63 #define PSPP_SHEET_VIEW_PRIORITY_SCROLL_SYNC (PSPP_SHEET_VIEW_PRIORITY_VALIDATE + 2)
64 #define PSPP_SHEET_VIEW_TIME_MS_PER_IDLE 30
65 #define SCROLL_EDGE_SIZE 15
66 #define EXPANDER_EXTRA_PADDING 4
67 #define PSPP_SHEET_VIEW_SEARCH_DIALOG_TIMEOUT 5000
69 /* The "background" areas of all rows/cells add up to cover the entire tree.
70 * The background includes all inter-row and inter-cell spacing.
71 * The "cell" areas are the cell_area passed in to gtk_cell_renderer_render(),
72 * i.e. just the cells, no spacing.
75 #define BACKGROUND_HEIGHT(tree_view) (tree_view->priv->fixed_height)
76 #define CELL_HEIGHT(tree_view, separator) ((BACKGROUND_HEIGHT (tree_view)) - (separator))
78 /* Translate from bin_window coordinates to rbtree (tree coordinates) and
79 * vice versa.
81 #define TREE_WINDOW_Y_TO_RBTREE_Y(tree_view,y) ((y) + tree_view->priv->dy)
82 #define RBTREE_Y_TO_TREE_WINDOW_Y(tree_view,y) ((y) - tree_view->priv->dy)
84 /* This is in bin_window coordinates */
85 #define BACKGROUND_FIRST_PIXEL(tree_view,node) (RBTREE_Y_TO_TREE_WINDOW_Y (tree_view, pspp_sheet_view_node_find_offset (tree_view, (node))))
86 #define CELL_FIRST_PIXEL(tree_view,node,separator) (BACKGROUND_FIRST_PIXEL (tree_view,node) + separator/2)
88 #define ROW_HEIGHT(tree_view) \
89 ((tree_view->priv->fixed_height > 0) ? (tree_view->priv->fixed_height) : (tree_view)->priv->expander_size)
92 typedef struct _PsppSheetViewChild PsppSheetViewChild;
93 struct _PsppSheetViewChild
95 GtkWidget *widget;
96 gint x;
97 gint y;
98 gint width;
99 gint height;
103 typedef struct _TreeViewDragInfo TreeViewDragInfo;
104 struct _TreeViewDragInfo
106 GdkModifierType start_button_mask;
107 GtkTargetList *_unused_source_target_list;
108 GdkDragAction source_actions;
110 GtkTargetList *_unused_dest_target_list;
112 guint source_set : 1;
113 guint dest_set : 1;
117 /* Signals */
118 enum
120 ROW_ACTIVATED,
121 COLUMNS_CHANGED,
122 CURSOR_CHANGED,
123 MOVE_CURSOR,
124 SELECT_ALL,
125 UNSELECT_ALL,
126 SELECT_CURSOR_ROW,
127 TOGGLE_CURSOR_ROW,
128 START_INTERACTIVE_SEARCH,
129 LAST_SIGNAL
132 /* Properties */
133 enum {
134 PROP_0,
135 PROP_MODEL,
136 PROP_HADJUSTMENT,
137 PROP_VADJUSTMENT,
138 PROP_HSCROLL_POLICY,
139 PROP_VSCROLL_POLICY,
140 PROP_HEADERS_VISIBLE,
141 PROP_HEADERS_CLICKABLE,
142 PROP_REORDERABLE,
143 PROP_RULES_HINT,
144 PROP_ENABLE_SEARCH,
145 PROP_SEARCH_COLUMN,
146 PROP_HOVER_SELECTION,
147 PROP_RUBBER_BANDING,
148 PROP_ENABLE_GRID_LINES,
149 PROP_TOOLTIP_COLUMN,
150 PROP_SPECIAL_CELLS,
151 PROP_FIXED_HEIGHT,
152 PROP_FIXED_HEIGHT_SET
155 /* object signals */
156 static void pspp_sheet_view_finalize (GObject *object);
157 static void pspp_sheet_view_set_property (GObject *object,
158 guint prop_id,
159 const GValue *value,
160 GParamSpec *pspec);
161 static void pspp_sheet_view_get_property (GObject *object,
162 guint prop_id,
163 GValue *value,
164 GParamSpec *pspec);
166 static void pspp_sheet_view_dispose (GObject *object);
168 /* gtkwidget signals */
169 static void pspp_sheet_view_realize (GtkWidget *widget);
170 static void pspp_sheet_view_unrealize (GtkWidget *widget);
171 static void pspp_sheet_view_map (GtkWidget *widget);
172 static void pspp_sheet_view_size_request (GtkWidget *widget,
173 GtkRequisition *requisition);
174 static void pspp_sheet_view_size_allocate (GtkWidget *widget,
175 GtkAllocation *allocation);
176 static gboolean pspp_sheet_view_draw (GtkWidget *widget,
177 cairo_t *cr);
178 static gboolean pspp_sheet_view_key_press (GtkWidget *widget,
179 GdkEventKey *event);
180 static gboolean pspp_sheet_view_key_release (GtkWidget *widget,
181 GdkEventKey *event);
182 static gboolean pspp_sheet_view_motion (GtkWidget *widget,
183 GdkEventMotion *event);
184 static gboolean pspp_sheet_view_enter_notify (GtkWidget *widget,
185 GdkEventCrossing *event);
186 static gboolean pspp_sheet_view_leave_notify (GtkWidget *widget,
187 GdkEventCrossing *event);
188 static gboolean pspp_sheet_view_button_press (GtkWidget *widget,
189 GdkEventButton *event);
190 static gboolean pspp_sheet_view_button_release (GtkWidget *widget,
191 GdkEventButton *event);
192 static gboolean pspp_sheet_view_grab_broken (GtkWidget *widget,
193 GdkEventGrabBroken *event);
195 static void pspp_sheet_view_set_focus_child (GtkContainer *container,
196 GtkWidget *child);
197 static gint pspp_sheet_view_focus_out (GtkWidget *widget,
198 GdkEventFocus *event);
199 static gint pspp_sheet_view_focus (GtkWidget *widget,
200 GtkDirectionType direction);
201 static void pspp_sheet_view_grab_focus (GtkWidget *widget);
202 static void pspp_sheet_view_style_set (GtkWidget *widget,
203 GtkStyle *previous_style);
204 static void pspp_sheet_view_grab_notify (GtkWidget *widget,
205 gboolean was_grabbed);
206 static void pspp_sheet_view_state_changed (GtkWidget *widget,
207 GtkStateType previous_state);
209 /* container signals */
210 static void pspp_sheet_view_remove (GtkContainer *container,
211 GtkWidget *widget);
212 static void pspp_sheet_view_forall (GtkContainer *container,
213 gboolean include_internals,
214 GtkCallback callback,
215 gpointer callback_data);
217 /* Source side drag signals */
218 static void pspp_sheet_view_drag_begin (GtkWidget *widget,
219 GdkDragContext *context);
220 static void pspp_sheet_view_drag_end (GtkWidget *widget,
221 GdkDragContext *context);
222 static void pspp_sheet_view_drag_data_get (GtkWidget *widget,
223 GdkDragContext *context,
224 GtkSelectionData *selection_data,
225 guint info,
226 guint time);
227 static void pspp_sheet_view_drag_data_delete (GtkWidget *widget,
228 GdkDragContext *context);
230 /* Target side drag signals */
231 static void pspp_sheet_view_drag_leave (GtkWidget *widget,
232 GdkDragContext *context,
233 guint time);
234 static gboolean pspp_sheet_view_drag_motion (GtkWidget *widget,
235 GdkDragContext *context,
236 gint x,
237 gint y,
238 guint time);
239 static gboolean pspp_sheet_view_drag_drop (GtkWidget *widget,
240 GdkDragContext *context,
241 gint x,
242 gint y,
243 guint time);
244 static void pspp_sheet_view_drag_data_received (GtkWidget *widget,
245 GdkDragContext *context,
246 gint x,
247 gint y,
248 GtkSelectionData *selection_data,
249 guint info,
250 guint time);
252 /* tree_model signals */
253 static void pspp_sheet_view_set_adjustments (PsppSheetView *tree_view,
254 GtkAdjustment *hadj,
255 GtkAdjustment *vadj);
256 static gboolean pspp_sheet_view_real_move_cursor (PsppSheetView *tree_view,
257 GtkMovementStep step,
258 gint count);
259 static gboolean pspp_sheet_view_real_select_all (PsppSheetView *tree_view);
260 static gboolean pspp_sheet_view_real_unselect_all (PsppSheetView *tree_view);
261 static gboolean pspp_sheet_view_real_select_cursor_row (PsppSheetView *tree_view,
262 gboolean start_editing,
263 PsppSheetSelectMode mode);
264 static gboolean pspp_sheet_view_real_toggle_cursor_row (PsppSheetView *tree_view);
265 static void pspp_sheet_view_row_changed (GtkTreeModel *model,
266 GtkTreePath *path,
267 GtkTreeIter *iter,
268 gpointer data);
269 static void pspp_sheet_view_row_inserted (GtkTreeModel *model,
270 GtkTreePath *path,
271 GtkTreeIter *iter,
272 gpointer data);
273 static void pspp_sheet_view_row_deleted (GtkTreeModel *model,
274 GtkTreePath *path,
275 gpointer data);
276 static void pspp_sheet_view_rows_reordered (GtkTreeModel *model,
277 GtkTreePath *parent,
278 GtkTreeIter *iter,
279 gint *new_order,
280 gpointer data);
282 /* Incremental reflow */
283 static gint validate_row (PsppSheetView *tree_view,
284 int node,
285 GtkTreeIter *iter,
286 GtkTreePath *path);
287 static void validate_visible_area (PsppSheetView *tree_view);
288 static gboolean validate_rows_handler (PsppSheetView *tree_view);
289 static gboolean presize_handler_callback (gpointer data);
290 static void install_presize_handler (PsppSheetView *tree_view);
291 static void install_scroll_sync_handler (PsppSheetView *tree_view);
292 static void pspp_sheet_view_set_top_row (PsppSheetView *tree_view,
293 GtkTreePath *path,
294 gint offset);
295 static void pspp_sheet_view_dy_to_top_row (PsppSheetView *tree_view);
296 static void pspp_sheet_view_top_row_to_dy (PsppSheetView *tree_view);
297 static void invalidate_empty_focus (PsppSheetView *tree_view);
299 /* Internal functions */
300 static GtkAdjustment *pspp_sheet_view_do_get_hadjustment (PsppSheetView *);
301 static GtkAdjustment *pspp_sheet_view_do_get_vadjustment (PsppSheetView *);
302 static void pspp_sheet_view_do_set_hadjustment (PsppSheetView *tree_view,
303 GtkAdjustment *adjustment);
304 static void pspp_sheet_view_do_set_vadjustment (PsppSheetView *tree_view,
305 GtkAdjustment *adjustment);
306 static void pspp_sheet_view_add_move_binding (GtkBindingSet *binding_set,
307 guint keyval,
308 guint modmask,
309 gboolean add_shifted_binding,
310 GtkMovementStep step,
311 gint count);
312 static void pspp_sheet_view_queue_draw_path (PsppSheetView *tree_view,
313 GtkTreePath *path,
314 const GdkRectangle *clip_rect);
315 static gint pspp_sheet_view_new_column_width (PsppSheetView *tree_view,
316 gint i,
317 gint *x);
318 static void pspp_sheet_view_adjustment_changed (GtkAdjustment *adjustment,
319 PsppSheetView *tree_view);
320 static void pspp_sheet_view_clamp_node_visible (PsppSheetView *tree_view,
321 int node);
322 static void pspp_sheet_view_clamp_column_visible (PsppSheetView *tree_view,
323 PsppSheetViewColumn *column,
324 gboolean focus_to_cell);
325 static gboolean pspp_sheet_view_maybe_begin_dragging_row (PsppSheetView *tree_view,
326 GdkEventMotion *event);
327 static void pspp_sheet_view_focus_to_cursor (PsppSheetView *tree_view);
328 static gboolean pspp_sheet_view_move_cursor_up_down (PsppSheetView *tree_view,
329 gint count,
330 PsppSheetSelectMode mode);
331 static void pspp_sheet_view_move_cursor_page_up_down (PsppSheetView *tree_view,
332 gint count,
333 PsppSheetSelectMode mode);
334 static void pspp_sheet_view_move_cursor_left_right (PsppSheetView *tree_view,
335 gint count,
336 PsppSheetSelectMode mode);
337 static void pspp_sheet_view_move_cursor_line_start_end (PsppSheetView *tree_view,
338 gint count,
339 PsppSheetSelectMode mode);
340 static void pspp_sheet_view_move_cursor_tab (PsppSheetView *tree_view,
341 gint count);
342 static void pspp_sheet_view_move_cursor_start_end (PsppSheetView *tree_view,
343 gint count,
344 PsppSheetSelectMode mode);
345 static void pspp_sheet_view_real_set_cursor (PsppSheetView *tree_view,
346 GtkTreePath *path,
347 gboolean clear_and_select,
348 gboolean clamp_node,
349 PsppSheetSelectMode mode);
350 static gboolean pspp_sheet_view_has_special_cell (PsppSheetView *tree_view);
351 static void pspp_sheet_view_stop_rubber_band (PsppSheetView *tree_view);
352 static void update_prelight (PsppSheetView *tree_view,
353 int x,
354 int y);
355 static void initialize_fixed_height_mode (PsppSheetView *tree_view);
357 /* interactive search */
358 static void pspp_sheet_view_ensure_interactive_directory (PsppSheetView *tree_view);
359 static void pspp_sheet_view_search_dialog_hide (GtkWidget *search_dialog,
360 PsppSheetView *tree_view);
361 static void pspp_sheet_view_search_position_func (PsppSheetView *tree_view,
362 GtkWidget *search_dialog,
363 gpointer user_data);
364 static void pspp_sheet_view_search_disable_popdown (GtkEntry *entry,
365 GtkMenu *menu,
366 gpointer data);
367 #if GTK3_TRANSITION
368 static void pspp_sheet_view_search_preedit_changed (GtkIMContext *im_context,
369 PsppSheetView *tree_view);
370 #endif
371 static void pspp_sheet_view_search_activate (GtkEntry *entry,
372 PsppSheetView *tree_view);
373 static gboolean pspp_sheet_view_real_search_enable_popdown(gpointer data);
374 static void pspp_sheet_view_search_enable_popdown (GtkWidget *widget,
375 gpointer data);
376 static gboolean pspp_sheet_view_search_delete_event (GtkWidget *widget,
377 GdkEventAny *event,
378 PsppSheetView *tree_view);
379 static gboolean pspp_sheet_view_search_button_press_event (GtkWidget *widget,
380 GdkEventButton *event,
381 PsppSheetView *tree_view);
382 static gboolean pspp_sheet_view_search_scroll_event (GtkWidget *entry,
383 GdkEventScroll *event,
384 PsppSheetView *tree_view);
385 static gboolean pspp_sheet_view_search_key_press_event (GtkWidget *entry,
386 GdkEventKey *event,
387 PsppSheetView *tree_view);
388 static gboolean pspp_sheet_view_search_move (GtkWidget *window,
389 PsppSheetView *tree_view,
390 gboolean up);
391 static gboolean pspp_sheet_view_search_equal_func (GtkTreeModel *model,
392 gint column,
393 const gchar *key,
394 GtkTreeIter *iter,
395 gpointer search_data);
396 static gboolean pspp_sheet_view_search_iter (GtkTreeModel *model,
397 PsppSheetSelection *selection,
398 GtkTreeIter *iter,
399 const gchar *text,
400 gint *count,
401 gint n);
402 static void pspp_sheet_view_search_init (GtkWidget *entry,
403 PsppSheetView *tree_view);
404 static void pspp_sheet_view_put (PsppSheetView *tree_view,
405 GtkWidget *child_widget,
406 gint x,
407 gint y,
408 gint width,
409 gint height);
410 static gboolean pspp_sheet_view_start_editing (PsppSheetView *tree_view,
411 GtkTreePath *cursor_path);
412 static gboolean pspp_sheet_view_editable_button_press_event (GtkWidget *,
413 GdkEventButton *,
414 PsppSheetView *);
415 static void pspp_sheet_view_editable_clicked (GtkButton *, PsppSheetView *);
416 static void pspp_sheet_view_real_start_editing (PsppSheetView *tree_view,
417 PsppSheetViewColumn *column,
418 GtkTreePath *path,
419 GtkCellEditable *cell_editable,
420 GdkRectangle *cell_area,
421 GdkEvent *event,
422 guint flags);
423 static gboolean pspp_sheet_view_real_start_interactive_search (PsppSheetView *tree_view,
424 gboolean keybinding);
425 static gboolean pspp_sheet_view_start_interactive_search (PsppSheetView *tree_view);
426 static PsppSheetViewColumn *pspp_sheet_view_get_drop_column (PsppSheetView *tree_view,
427 PsppSheetViewColumn *column,
428 gint drop_position);
429 static void
430 pspp_sheet_view_adjust_cell_area (PsppSheetView *tree_view,
431 PsppSheetViewColumn *column,
432 const GdkRectangle *background_area,
433 gboolean subtract_focus_rect,
434 GdkRectangle *cell_area);
435 static gint pspp_sheet_view_find_offset (PsppSheetView *tree_view,
436 gint height,
437 int *new_node);
439 /* GtkBuildable */
440 static void pspp_sheet_view_buildable_add_child (GtkBuildable *tree_view,
441 GtkBuilder *builder,
442 GObject *child,
443 const gchar *type);
444 static void pspp_sheet_view_buildable_init (GtkBuildableIface *iface);
447 static gboolean scroll_row_timeout (gpointer data);
448 static void add_scroll_timeout (PsppSheetView *tree_view);
449 static void remove_scroll_timeout (PsppSheetView *tree_view);
451 static guint tree_view_signals [LAST_SIGNAL] = { 0 };
453 static GtkBindingSet *edit_bindings;
457 /* GType Methods
460 G_DEFINE_TYPE_WITH_CODE (PsppSheetView, pspp_sheet_view, GTK_TYPE_CONTAINER,
461 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
462 pspp_sheet_view_buildable_init)
463 G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL))
465 static void
466 pspp_sheet_view_get_preferred_width (GtkWidget *widget,
467 gint *minimal_width,
468 gint *natural_width)
470 GtkRequisition requisition;
472 pspp_sheet_view_size_request (widget, &requisition);
474 *minimal_width = *natural_width = requisition.width;
477 static void
478 pspp_sheet_view_get_preferred_height (GtkWidget *widget,
479 gint *minimal_height,
480 gint *natural_height)
482 GtkRequisition requisition;
484 pspp_sheet_view_size_request (widget, &requisition);
486 *minimal_height = *natural_height = requisition.height;
489 static void
490 pspp_sheet_view_class_init (PsppSheetViewClass *class)
492 GObjectClass *o_class;
493 GtkWidgetClass *widget_class;
494 GtkContainerClass *container_class;
495 GtkBindingSet *binding_set[2];
496 int i;
498 binding_set[0] = gtk_binding_set_by_class (class);
500 binding_set[1] = gtk_binding_set_new ("PsppSheetViewEditing");
501 edit_bindings = binding_set[1];
503 o_class = (GObjectClass *) class;
504 widget_class = (GtkWidgetClass *) class;
505 container_class = (GtkContainerClass *) class;
507 /* GObject signals */
508 o_class->set_property = pspp_sheet_view_set_property;
509 o_class->get_property = pspp_sheet_view_get_property;
510 o_class->finalize = pspp_sheet_view_finalize;
511 o_class->dispose = pspp_sheet_view_dispose;
513 /* GtkWidget signals */
514 widget_class->map = pspp_sheet_view_map;
515 widget_class->realize = pspp_sheet_view_realize;
516 widget_class->unrealize = pspp_sheet_view_unrealize;
517 widget_class->get_preferred_width = pspp_sheet_view_get_preferred_width;
518 widget_class->get_preferred_height = pspp_sheet_view_get_preferred_height;
519 widget_class->size_allocate = pspp_sheet_view_size_allocate;
520 widget_class->button_press_event = pspp_sheet_view_button_press;
521 widget_class->button_release_event = pspp_sheet_view_button_release;
522 widget_class->grab_broken_event = pspp_sheet_view_grab_broken;
523 /*widget_class->configure_event = pspp_sheet_view_configure;*/
524 widget_class->motion_notify_event = pspp_sheet_view_motion;
525 widget_class->draw = pspp_sheet_view_draw;
526 widget_class->key_press_event = pspp_sheet_view_key_press;
527 widget_class->key_release_event = pspp_sheet_view_key_release;
528 widget_class->enter_notify_event = pspp_sheet_view_enter_notify;
529 widget_class->leave_notify_event = pspp_sheet_view_leave_notify;
530 widget_class->focus_out_event = pspp_sheet_view_focus_out;
531 widget_class->drag_begin = pspp_sheet_view_drag_begin;
532 widget_class->drag_end = pspp_sheet_view_drag_end;
533 widget_class->drag_data_get = pspp_sheet_view_drag_data_get;
534 widget_class->drag_data_delete = pspp_sheet_view_drag_data_delete;
535 widget_class->drag_leave = pspp_sheet_view_drag_leave;
536 widget_class->drag_motion = pspp_sheet_view_drag_motion;
537 widget_class->drag_drop = pspp_sheet_view_drag_drop;
538 widget_class->drag_data_received = pspp_sheet_view_drag_data_received;
539 widget_class->focus = pspp_sheet_view_focus;
540 widget_class->grab_focus = pspp_sheet_view_grab_focus;
541 widget_class->style_set = pspp_sheet_view_style_set;
542 widget_class->grab_notify = pspp_sheet_view_grab_notify;
543 widget_class->state_changed = pspp_sheet_view_state_changed;
545 /* GtkContainer signals */
546 container_class->remove = pspp_sheet_view_remove;
547 container_class->forall = pspp_sheet_view_forall;
548 container_class->set_focus_child = pspp_sheet_view_set_focus_child;
550 class->set_scroll_adjustments = pspp_sheet_view_set_adjustments;
551 class->move_cursor = pspp_sheet_view_real_move_cursor;
552 class->select_all = pspp_sheet_view_real_select_all;
553 class->unselect_all = pspp_sheet_view_real_unselect_all;
554 class->select_cursor_row = pspp_sheet_view_real_select_cursor_row;
555 class->toggle_cursor_row = pspp_sheet_view_real_toggle_cursor_row;
556 class->start_interactive_search = pspp_sheet_view_start_interactive_search;
558 /* Properties */
560 g_object_class_install_property (o_class,
561 PROP_MODEL,
562 g_param_spec_object ("model",
563 P_("TreeView Model"),
564 P_("The model for the tree view"),
565 GTK_TYPE_TREE_MODEL,
566 GTK_PARAM_READWRITE));
568 g_object_class_override_property (o_class, PROP_HADJUSTMENT, "hadjustment");
569 g_object_class_override_property (o_class, PROP_VADJUSTMENT, "vadjustment");
570 g_object_class_override_property (o_class, PROP_HSCROLL_POLICY, "hscroll-policy");
571 g_object_class_override_property (o_class, PROP_VSCROLL_POLICY, "vscroll-policy");
573 g_object_class_install_property (o_class,
574 PROP_HEADERS_VISIBLE,
575 g_param_spec_boolean ("headers-visible",
576 P_("Headers Visible"),
577 P_("Show the column header buttons"),
578 TRUE,
579 GTK_PARAM_READWRITE));
581 g_object_class_install_property (o_class,
582 PROP_HEADERS_CLICKABLE,
583 g_param_spec_boolean ("headers-clickable",
584 P_("Headers Clickable"),
585 P_("Column headers respond to click events"),
586 TRUE,
587 GTK_PARAM_READWRITE));
589 g_object_class_install_property (o_class,
590 PROP_REORDERABLE,
591 g_param_spec_boolean ("reorderable",
592 P_("Reorderable"),
593 P_("View is reorderable"),
594 FALSE,
595 GTK_PARAM_READWRITE));
597 g_object_class_install_property (o_class,
598 PROP_RULES_HINT,
599 g_param_spec_boolean ("rules-hint",
600 P_("Rules Hint"),
601 P_("Set a hint to the theme engine to draw rows in alternating colors"),
602 FALSE,
603 GTK_PARAM_READWRITE));
605 g_object_class_install_property (o_class,
606 PROP_ENABLE_SEARCH,
607 g_param_spec_boolean ("enable-search",
608 P_("Enable Search"),
609 P_("View allows user to search through columns interactively"),
610 TRUE,
611 GTK_PARAM_READWRITE));
613 g_object_class_install_property (o_class,
614 PROP_SEARCH_COLUMN,
615 g_param_spec_int ("search-column",
616 P_("Search Column"),
617 P_("Model column to search through during interactive search"),
619 G_MAXINT,
621 GTK_PARAM_READWRITE));
624 * PsppSheetView:hover-selection:
626 * Enables of disables the hover selection mode of @tree_view.
627 * Hover selection makes the selected row follow the pointer.
628 * Currently, this works only for the selection modes
629 * %PSPP_SHEET_SELECTION_SINGLE and %PSPP_SHEET_SELECTION_BROWSE.
631 * This mode is primarily intended for treeviews in popups, e.g.
632 * in #GtkComboBox or #GtkEntryCompletion.
634 * Since: 2.6
636 g_object_class_install_property (o_class,
637 PROP_HOVER_SELECTION,
638 g_param_spec_boolean ("hover-selection",
639 P_("Hover Selection"),
640 P_("Whether the selection should follow the pointer"),
641 FALSE,
642 GTK_PARAM_READWRITE));
644 g_object_class_install_property (o_class,
645 PROP_RUBBER_BANDING,
646 g_param_spec_boolean ("rubber-banding",
647 P_("Rubber Banding"),
648 P_("Whether to enable selection of multiple items by dragging the mouse pointer"),
649 FALSE,
650 GTK_PARAM_READWRITE));
652 g_object_class_install_property (o_class,
653 PROP_ENABLE_GRID_LINES,
654 g_param_spec_enum ("enable-grid-lines",
655 P_("Enable Grid Lines"),
656 P_("Whether grid lines should be drawn in the tree view"),
657 PSPP_TYPE_SHEET_VIEW_GRID_LINES,
658 PSPP_SHEET_VIEW_GRID_LINES_NONE,
659 GTK_PARAM_READWRITE));
661 g_object_class_install_property (o_class,
662 PROP_TOOLTIP_COLUMN,
663 g_param_spec_int ("tooltip-column",
664 P_("Tooltip Column"),
665 P_("The column in the model containing the tooltip texts for the rows"),
667 G_MAXINT,
669 GTK_PARAM_READWRITE));
671 g_object_class_install_property (o_class,
672 PROP_SPECIAL_CELLS,
673 g_param_spec_enum ("special-cells",
674 P_("Special Cells"),
675 P_("Whether rows have special cells."),
676 PSPP_TYPE_SHEET_VIEW_SPECIAL_CELLS,
677 PSPP_SHEET_VIEW_SPECIAL_CELLS_DETECT,
678 GTK_PARAM_READWRITE));
680 g_object_class_install_property (o_class,
681 PROP_FIXED_HEIGHT,
682 g_param_spec_int ("fixed-height",
683 P_("Fixed Height"),
684 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."),
686 G_MAXINT,
688 GTK_PARAM_READWRITE));
690 g_object_class_install_property (o_class,
691 PROP_FIXED_HEIGHT_SET,
692 g_param_spec_boolean ("fixed-height-set",
693 P_("Fixed Height Set"),
694 P_("Whether fixed-height was set externally."),
695 FALSE,
696 GTK_PARAM_READWRITE));
698 /* Style properties */
699 #define _TREE_VIEW_EXPANDER_SIZE 12
700 #define _TREE_VIEW_VERTICAL_SEPARATOR 2
701 #define _TREE_VIEW_HORIZONTAL_SEPARATOR 2
703 gtk_widget_class_install_style_property (widget_class,
704 g_param_spec_int ("expander-size",
705 P_("Expander Size"),
706 P_("Size of the expander arrow"),
708 G_MAXINT,
709 _TREE_VIEW_EXPANDER_SIZE,
710 GTK_PARAM_READABLE));
712 gtk_widget_class_install_style_property (widget_class,
713 g_param_spec_int ("vertical-separator",
714 P_("Vertical Separator Width"),
715 P_("Vertical space between cells. Must be an even number"),
717 G_MAXINT,
718 _TREE_VIEW_VERTICAL_SEPARATOR,
719 GTK_PARAM_READABLE));
721 gtk_widget_class_install_style_property (widget_class,
722 g_param_spec_int ("horizontal-separator",
723 P_("Horizontal Separator Width"),
724 P_("Horizontal space between cells. Must be an even number"),
726 G_MAXINT,
727 _TREE_VIEW_HORIZONTAL_SEPARATOR,
728 GTK_PARAM_READABLE));
730 gtk_widget_class_install_style_property (widget_class,
731 g_param_spec_boolean ("allow-rules",
732 P_("Allow Rules"),
733 P_("Allow drawing of alternating color rows"),
734 TRUE,
735 GTK_PARAM_READABLE));
737 gtk_widget_class_install_style_property (widget_class,
738 g_param_spec_boxed ("even-row-color",
739 P_("Even Row Color"),
740 P_("Color to use for even rows"),
741 GDK_TYPE_COLOR,
742 GTK_PARAM_READABLE));
744 gtk_widget_class_install_style_property (widget_class,
745 g_param_spec_boxed ("odd-row-color",
746 P_("Odd Row Color"),
747 P_("Color to use for odd rows"),
748 GDK_TYPE_COLOR,
749 GTK_PARAM_READABLE));
751 gtk_widget_class_install_style_property (widget_class,
752 g_param_spec_boolean ("row-ending-details",
753 P_("Row Ending details"),
754 P_("Enable extended row background theming"),
755 FALSE,
756 GTK_PARAM_READABLE));
758 gtk_widget_class_install_style_property (widget_class,
759 g_param_spec_int ("grid-line-width",
760 P_("Grid line width"),
761 P_("Width, in pixels, of the tree view grid lines"),
762 0, G_MAXINT, 1,
763 GTK_PARAM_READABLE));
765 gtk_widget_class_install_style_property (widget_class,
766 g_param_spec_int ("tree-line-width",
767 P_("Tree line width"),
768 P_("Width, in pixels, of the tree view lines"),
769 0, G_MAXINT, 1,
770 GTK_PARAM_READABLE));
772 gtk_widget_class_install_style_property (widget_class,
773 g_param_spec_string ("tree-line-pattern",
774 P_("Tree line pattern"),
775 P_("Dash pattern used to draw the tree view lines"),
776 "\1\1",
777 GTK_PARAM_READABLE));
779 /* Signals */
780 #if GTK3_TRANSITION
782 * PsppSheetView::set-scroll-adjustments
783 * @horizontal: the horizontal #GtkAdjustment
784 * @vertical: the vertical #GtkAdjustment
786 * Set the scroll adjustments for the tree view. Usually scrolled containers
787 * like #GtkScrolledWindow will emit this signal to connect two instances
788 * of #GtkScrollbar to the scroll directions of the #PsppSheetView.
790 widget_class->set_scroll_adjustments_signal =
791 g_signal_new ("set-scroll-adjustments",
792 G_TYPE_FROM_CLASS (o_class),
793 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
794 G_STRUCT_OFFSET (PsppSheetViewClass, set_scroll_adjustments),
795 NULL, NULL,
796 psppire_marshal_VOID__OBJECT_OBJECT,
797 G_TYPE_NONE, 2,
798 GTK_TYPE_ADJUSTMENT,
799 GTK_TYPE_ADJUSTMENT);
800 #endif
803 * PsppSheetView::row-activated:
804 * @tree_view: the object on which the signal is emitted
805 * @path: the #GtkTreePath for the activated row
806 * @column: the #PsppSheetViewColumn in which the activation occurred
808 * The "row-activated" signal is emitted when the method
809 * pspp_sheet_view_row_activated() is called or the user double clicks
810 * a treeview row. It is also emitted when a non-editable row is
811 * selected and one of the keys: Space, Shift+Space, Return or
812 * Enter is pressed.
814 * For selection handling refer to the <link linkend="TreeWidget">tree
815 * widget conceptual overview</link> as well as #PsppSheetSelection.
817 tree_view_signals[ROW_ACTIVATED] =
818 g_signal_new ("row-activated",
819 G_TYPE_FROM_CLASS (o_class),
820 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
821 G_STRUCT_OFFSET (PsppSheetViewClass, row_activated),
822 NULL, NULL,
823 psppire_marshal_VOID__BOXED_OBJECT,
824 G_TYPE_NONE, 2,
825 GTK_TYPE_TREE_PATH,
826 PSPP_TYPE_SHEET_VIEW_COLUMN);
829 * PsppSheetView::columns-changed:
830 * @tree_view: the object on which the signal is emitted
832 * The number of columns of the treeview has changed.
834 tree_view_signals[COLUMNS_CHANGED] =
835 g_signal_new ("columns-changed",
836 G_TYPE_FROM_CLASS (o_class),
837 G_SIGNAL_RUN_LAST,
838 G_STRUCT_OFFSET (PsppSheetViewClass, columns_changed),
839 NULL, NULL,
840 g_cclosure_marshal_VOID__VOID,
841 G_TYPE_NONE, 0);
844 * PsppSheetView::cursor-changed:
845 * @tree_view: the object on which the signal is emitted
847 * The position of the cursor (focused cell) has changed.
849 tree_view_signals[CURSOR_CHANGED] =
850 g_signal_new ("cursor-changed",
851 G_TYPE_FROM_CLASS (o_class),
852 G_SIGNAL_RUN_LAST,
853 G_STRUCT_OFFSET (PsppSheetViewClass, cursor_changed),
854 NULL, NULL,
855 g_cclosure_marshal_VOID__VOID,
856 G_TYPE_NONE, 0);
858 tree_view_signals[MOVE_CURSOR] =
859 g_signal_new ("move-cursor",
860 G_TYPE_FROM_CLASS (o_class),
861 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
862 G_STRUCT_OFFSET (PsppSheetViewClass, move_cursor),
863 NULL, NULL,
864 psppire_marshal_BOOLEAN__ENUM_INT,
865 G_TYPE_BOOLEAN, 2,
866 GTK_TYPE_MOVEMENT_STEP,
867 G_TYPE_INT);
869 tree_view_signals[SELECT_ALL] =
870 g_signal_new ("select-all",
871 G_TYPE_FROM_CLASS (o_class),
872 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
873 G_STRUCT_OFFSET (PsppSheetViewClass, select_all),
874 NULL, NULL,
875 psppire_marshal_BOOLEAN__VOID,
876 G_TYPE_BOOLEAN, 0);
878 tree_view_signals[UNSELECT_ALL] =
879 g_signal_new ("unselect-all",
880 G_TYPE_FROM_CLASS (o_class),
881 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
882 G_STRUCT_OFFSET (PsppSheetViewClass, unselect_all),
883 NULL, NULL,
884 psppire_marshal_BOOLEAN__VOID,
885 G_TYPE_BOOLEAN, 0);
887 tree_view_signals[SELECT_CURSOR_ROW] =
888 g_signal_new ("select-cursor-row",
889 G_TYPE_FROM_CLASS (o_class),
890 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
891 G_STRUCT_OFFSET (PsppSheetViewClass, select_cursor_row),
892 NULL, NULL,
893 psppire_marshal_BOOLEAN__BOOLEAN,
894 G_TYPE_BOOLEAN, 2,
895 G_TYPE_BOOLEAN, G_TYPE_INT);
897 tree_view_signals[TOGGLE_CURSOR_ROW] =
898 g_signal_new ("toggle-cursor-row",
899 G_TYPE_FROM_CLASS (o_class),
900 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
901 G_STRUCT_OFFSET (PsppSheetViewClass, toggle_cursor_row),
902 NULL, NULL,
903 psppire_marshal_BOOLEAN__VOID,
904 G_TYPE_BOOLEAN, 0);
906 tree_view_signals[START_INTERACTIVE_SEARCH] =
907 g_signal_new ("start-interactive-search",
908 G_TYPE_FROM_CLASS (o_class),
909 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
910 G_STRUCT_OFFSET (PsppSheetViewClass, start_interactive_search),
911 NULL, NULL,
912 psppire_marshal_BOOLEAN__VOID,
913 G_TYPE_BOOLEAN, 0);
915 /* Key bindings */
916 for (i = 0; i < 2; i++)
918 pspp_sheet_view_add_move_binding (binding_set[i], GDK_Up, 0, TRUE,
919 GTK_MOVEMENT_DISPLAY_LINES, -1);
920 pspp_sheet_view_add_move_binding (binding_set[i], GDK_KP_Up, 0, TRUE,
921 GTK_MOVEMENT_DISPLAY_LINES, -1);
923 pspp_sheet_view_add_move_binding (binding_set[i], GDK_Down, 0, TRUE,
924 GTK_MOVEMENT_DISPLAY_LINES, 1);
925 pspp_sheet_view_add_move_binding (binding_set[i], GDK_KP_Down, 0, TRUE,
926 GTK_MOVEMENT_DISPLAY_LINES, 1);
928 pspp_sheet_view_add_move_binding (binding_set[i], GDK_p, GDK_CONTROL_MASK, FALSE,
929 GTK_MOVEMENT_DISPLAY_LINES, -1);
931 pspp_sheet_view_add_move_binding (binding_set[i], GDK_n, GDK_CONTROL_MASK, FALSE,
932 GTK_MOVEMENT_DISPLAY_LINES, 1);
934 pspp_sheet_view_add_move_binding (binding_set[i], GDK_Home, 0, TRUE,
935 GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1);
936 pspp_sheet_view_add_move_binding (binding_set[i], GDK_KP_Home, 0, TRUE,
937 GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1);
939 pspp_sheet_view_add_move_binding (binding_set[i], GDK_End, 0, TRUE,
940 GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1);
941 pspp_sheet_view_add_move_binding (binding_set[i], GDK_KP_End, 0, TRUE,
942 GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1);
944 pspp_sheet_view_add_move_binding (binding_set[i], GDK_Page_Up, 0, TRUE,
945 GTK_MOVEMENT_PAGES, -1);
946 pspp_sheet_view_add_move_binding (binding_set[i], GDK_KP_Page_Up, 0, TRUE,
947 GTK_MOVEMENT_PAGES, -1);
949 pspp_sheet_view_add_move_binding (binding_set[i], GDK_Page_Down, 0, TRUE,
950 GTK_MOVEMENT_PAGES, 1);
951 pspp_sheet_view_add_move_binding (binding_set[i], GDK_KP_Page_Down, 0, TRUE,
952 GTK_MOVEMENT_PAGES, 1);
955 gtk_binding_entry_add_signal (binding_set[i], GDK_Up, GDK_CONTROL_MASK, "move-cursor", 2,
956 G_TYPE_ENUM, GTK_MOVEMENT_BUFFER_ENDS,
957 G_TYPE_INT, -1);
959 gtk_binding_entry_add_signal (binding_set[i], GDK_Down, GDK_CONTROL_MASK, "move-cursor", 2,
960 G_TYPE_ENUM, GTK_MOVEMENT_BUFFER_ENDS,
961 G_TYPE_INT, 1);
963 gtk_binding_entry_add_signal (binding_set[i], GDK_Right, 0, "move-cursor", 2,
964 G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
965 G_TYPE_INT, 1);
967 gtk_binding_entry_add_signal (binding_set[i], GDK_Left, 0, "move-cursor", 2,
968 G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
969 G_TYPE_INT, -1);
971 gtk_binding_entry_add_signal (binding_set[i], GDK_Tab, 0, "move-cursor", 2,
972 G_TYPE_ENUM, GTK_MOVEMENT_LOGICAL_POSITIONS,
973 G_TYPE_INT, 1);
975 gtk_binding_entry_add_signal (binding_set[i], GDK_Tab, GDK_SHIFT_MASK, "move-cursor", 2,
976 G_TYPE_ENUM, GTK_MOVEMENT_LOGICAL_POSITIONS,
977 G_TYPE_INT, -1);
979 gtk_binding_entry_add_signal (binding_set[i], GDK_KP_Right, 0, "move-cursor", 2,
980 G_TYPE_ENUM, GTK_MOVEMENT_DISPLAY_LINE_ENDS,
981 G_TYPE_INT, 1);
983 gtk_binding_entry_add_signal (binding_set[i], GDK_KP_Left, 0, "move-cursor", 2,
984 G_TYPE_ENUM, GTK_MOVEMENT_DISPLAY_LINE_ENDS,
985 G_TYPE_INT, -1);
987 gtk_binding_entry_add_signal (binding_set[i], GDK_Right, GDK_CONTROL_MASK,
988 "move-cursor", 2,
989 G_TYPE_ENUM, GTK_MOVEMENT_DISPLAY_LINE_ENDS,
990 G_TYPE_INT, 1);
992 gtk_binding_entry_add_signal (binding_set[i], GDK_Left, GDK_CONTROL_MASK,
993 "move-cursor", 2,
994 G_TYPE_ENUM, GTK_MOVEMENT_DISPLAY_LINE_ENDS,
995 G_TYPE_INT, -1);
997 gtk_binding_entry_add_signal (binding_set[i], GDK_KP_Right, GDK_CONTROL_MASK,
998 "move-cursor", 2,
999 G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
1000 G_TYPE_INT, 1);
1002 gtk_binding_entry_add_signal (binding_set[i], GDK_KP_Left, GDK_CONTROL_MASK,
1003 "move-cursor", 2,
1004 G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
1005 G_TYPE_INT, -1);
1007 gtk_binding_entry_add_signal (binding_set[i], GDK_f, GDK_CONTROL_MASK, "start-interactive-search", 0);
1009 gtk_binding_entry_add_signal (binding_set[i], GDK_F, GDK_CONTROL_MASK, "start-interactive-search", 0);
1012 gtk_binding_entry_add_signal (binding_set[0], GDK_space, GDK_CONTROL_MASK, "toggle-cursor-row", 0);
1013 gtk_binding_entry_add_signal (binding_set[0], GDK_KP_Space, GDK_CONTROL_MASK, "toggle-cursor-row", 0);
1015 gtk_binding_entry_add_signal (binding_set[0], GDK_a, GDK_CONTROL_MASK, "select-all", 0);
1016 gtk_binding_entry_add_signal (binding_set[0], GDK_slash, GDK_CONTROL_MASK, "select-all", 0);
1018 gtk_binding_entry_add_signal (binding_set[0], GDK_A, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "unselect-all", 0);
1019 gtk_binding_entry_add_signal (binding_set[0], GDK_backslash, GDK_CONTROL_MASK, "unselect-all", 0);
1021 gtk_binding_entry_add_signal (binding_set[0], GDK_space, GDK_SHIFT_MASK, "select-cursor-row", 1,
1022 G_TYPE_BOOLEAN, TRUE,
1023 G_TYPE_INT, PSPP_SHEET_SELECT_MODE_EXTEND);
1024 gtk_binding_entry_add_signal (binding_set[0], GDK_KP_Space, GDK_SHIFT_MASK, "select-cursor-row", 1,
1025 G_TYPE_BOOLEAN, TRUE,
1026 G_TYPE_INT, PSPP_SHEET_SELECT_MODE_EXTEND);
1028 gtk_binding_entry_add_signal (binding_set[0], GDK_space, 0, "select-cursor-row", 1,
1029 G_TYPE_BOOLEAN, TRUE,
1030 G_TYPE_INT, 0);
1031 gtk_binding_entry_add_signal (binding_set[0], GDK_KP_Space, 0, "select-cursor-row", 1,
1032 G_TYPE_BOOLEAN, TRUE,
1033 G_TYPE_INT, 0);
1034 gtk_binding_entry_add_signal (binding_set[0], GDK_Return, 0, "select-cursor-row", 1,
1035 G_TYPE_BOOLEAN, TRUE,
1036 G_TYPE_INT, 0);
1037 gtk_binding_entry_add_signal (binding_set[0], GDK_ISO_Enter, 0, "select-cursor-row", 1,
1038 G_TYPE_BOOLEAN, TRUE,
1039 G_TYPE_INT, 0);
1040 gtk_binding_entry_add_signal (binding_set[0], GDK_KP_Enter, 0, "select-cursor-row", 1,
1041 G_TYPE_BOOLEAN, TRUE,
1042 G_TYPE_INT, 0);
1044 gtk_binding_entry_add_signal (binding_set[0], GDK_BackSpace, 0, "select-cursor-parent", 0);
1045 gtk_binding_entry_add_signal (binding_set[0], GDK_BackSpace, GDK_CONTROL_MASK, "select-cursor-parent", 0);
1047 g_type_class_add_private (o_class, sizeof (PsppSheetViewPrivate));
1050 static void
1051 pspp_sheet_view_buildable_init (GtkBuildableIface *iface)
1053 iface->add_child = pspp_sheet_view_buildable_add_child;
1056 static void
1057 pspp_sheet_view_init (PsppSheetView *tree_view)
1059 tree_view->priv = G_TYPE_INSTANCE_GET_PRIVATE (tree_view, PSPP_TYPE_SHEET_VIEW, PsppSheetViewPrivate);
1061 gtk_widget_set_can_focus (GTK_WIDGET (tree_view), TRUE);
1062 gtk_widget_set_redraw_on_allocate (GTK_WIDGET (tree_view), FALSE);
1064 tree_view->priv->flags = PSPP_SHEET_VIEW_DRAW_KEYFOCUS
1065 | PSPP_SHEET_VIEW_HEADERS_VISIBLE;
1067 /* We need some padding */
1068 tree_view->priv->selected = range_tower_create ();
1069 tree_view->priv->dy = 0;
1070 tree_view->priv->cursor_offset = 0;
1071 tree_view->priv->n_columns = 0;
1072 tree_view->priv->header_height = 1;
1073 tree_view->priv->x_drag = 0;
1074 tree_view->priv->drag_pos = -1;
1075 tree_view->priv->header_has_focus = FALSE;
1076 tree_view->priv->pressed_button = -1;
1077 tree_view->priv->press_start_x = -1;
1078 tree_view->priv->press_start_y = -1;
1079 tree_view->priv->reorderable = FALSE;
1080 tree_view->priv->presize_handler_timer = 0;
1081 tree_view->priv->scroll_sync_timer = 0;
1082 tree_view->priv->fixed_height = -1;
1083 tree_view->priv->fixed_height_set = FALSE;
1084 pspp_sheet_view_set_adjustments (tree_view, NULL, NULL);
1085 tree_view->priv->selection = _pspp_sheet_selection_new_with_tree_view (tree_view);
1086 tree_view->priv->enable_search = TRUE;
1087 tree_view->priv->search_column = -1;
1088 tree_view->priv->search_position_func = pspp_sheet_view_search_position_func;
1089 tree_view->priv->search_equal_func = pspp_sheet_view_search_equal_func;
1090 tree_view->priv->search_custom_entry_set = FALSE;
1091 tree_view->priv->typeselect_flush_timeout = 0;
1092 tree_view->priv->init_hadjust_value = TRUE;
1093 tree_view->priv->width = 0;
1095 tree_view->priv->hover_selection = FALSE;
1097 tree_view->priv->rubber_banding_enable = FALSE;
1099 tree_view->priv->grid_lines = PSPP_SHEET_VIEW_GRID_LINES_NONE;
1101 tree_view->priv->tooltip_column = -1;
1103 tree_view->priv->special_cells = PSPP_SHEET_VIEW_SPECIAL_CELLS_DETECT;
1105 tree_view->priv->post_validation_flag = FALSE;
1107 tree_view->priv->last_button_x = -1;
1108 tree_view->priv->last_button_y = -1;
1110 tree_view->priv->event_last_x = -10000;
1111 tree_view->priv->event_last_y = -10000;
1113 tree_view->priv->prelight_node = -1;
1114 tree_view->priv->rubber_band_start_node = -1;
1115 tree_view->priv->rubber_band_end_node = -1;
1117 tree_view->priv->anchor_column = NULL;
1119 tree_view->priv->button_style = NULL;
1121 tree_view->dispose_has_run = FALSE;
1123 pspp_sheet_view_do_set_vadjustment (tree_view, NULL);
1124 pspp_sheet_view_do_set_hadjustment (tree_view, NULL);
1129 /* GObject Methods
1132 static void
1133 pspp_sheet_view_set_property (GObject *object,
1134 guint prop_id,
1135 const GValue *value,
1136 GParamSpec *pspec)
1138 PsppSheetView *tree_view;
1140 tree_view = PSPP_SHEET_VIEW (object);
1142 switch (prop_id)
1144 case PROP_MODEL:
1145 pspp_sheet_view_set_model (tree_view, g_value_get_object (value));
1146 break;
1147 case PROP_HADJUSTMENT:
1148 pspp_sheet_view_do_set_hadjustment (tree_view, g_value_get_object (value));
1149 break;
1150 case PROP_VADJUSTMENT:
1151 pspp_sheet_view_do_set_vadjustment (tree_view, g_value_get_object (value));
1152 break;
1153 case PROP_HSCROLL_POLICY:
1154 tree_view->priv->hscroll_policy = g_value_get_enum (value);
1155 gtk_widget_queue_resize (GTK_WIDGET (tree_view));
1156 break;
1157 case PROP_VSCROLL_POLICY:
1158 tree_view->priv->vscroll_policy = g_value_get_enum (value);
1159 gtk_widget_queue_resize (GTK_WIDGET (tree_view));
1160 break;
1161 case PROP_HEADERS_VISIBLE:
1162 pspp_sheet_view_set_headers_visible (tree_view, g_value_get_boolean (value));
1163 break;
1164 case PROP_HEADERS_CLICKABLE:
1165 pspp_sheet_view_set_headers_clickable (tree_view, g_value_get_boolean (value));
1166 break;
1167 case PROP_REORDERABLE:
1168 pspp_sheet_view_set_reorderable (tree_view, g_value_get_boolean (value));
1169 break;
1170 case PROP_RULES_HINT:
1171 pspp_sheet_view_set_rules_hint (tree_view, g_value_get_boolean (value));
1172 break;
1173 case PROP_ENABLE_SEARCH:
1174 pspp_sheet_view_set_enable_search (tree_view, g_value_get_boolean (value));
1175 break;
1176 case PROP_SEARCH_COLUMN:
1177 pspp_sheet_view_set_search_column (tree_view, g_value_get_int (value));
1178 break;
1179 case PROP_HOVER_SELECTION:
1180 tree_view->priv->hover_selection = g_value_get_boolean (value);
1181 break;
1182 case PROP_RUBBER_BANDING:
1183 tree_view->priv->rubber_banding_enable = g_value_get_boolean (value);
1184 break;
1185 case PROP_ENABLE_GRID_LINES:
1186 pspp_sheet_view_set_grid_lines (tree_view, g_value_get_enum (value));
1187 break;
1188 case PROP_TOOLTIP_COLUMN:
1189 pspp_sheet_view_set_tooltip_column (tree_view, g_value_get_int (value));
1190 break;
1191 case PROP_SPECIAL_CELLS:
1192 pspp_sheet_view_set_special_cells (tree_view, g_value_get_enum (value));
1193 break;
1194 case PROP_FIXED_HEIGHT:
1195 pspp_sheet_view_set_fixed_height (tree_view, g_value_get_int (value));
1196 break;
1197 case PROP_FIXED_HEIGHT_SET:
1198 if (g_value_get_boolean (value))
1200 if (!tree_view->priv->fixed_height_set
1201 && tree_view->priv->fixed_height >= 0)
1203 tree_view->priv->fixed_height_set = true;
1204 g_object_notify (G_OBJECT (tree_view), "fixed-height-set");
1207 else
1209 if (tree_view->priv->fixed_height_set)
1211 tree_view->priv->fixed_height_set = false;
1212 g_object_notify (G_OBJECT (tree_view), "fixed-height-set");
1213 install_presize_handler (tree_view);
1216 break;
1217 default:
1218 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1219 break;
1223 static void
1224 pspp_sheet_view_get_property (GObject *object,
1225 guint prop_id,
1226 GValue *value,
1227 GParamSpec *pspec)
1229 PsppSheetView *tree_view;
1231 tree_view = PSPP_SHEET_VIEW (object);
1233 switch (prop_id)
1235 case PROP_MODEL:
1236 g_value_set_object (value, tree_view->priv->model);
1237 break;
1238 case PROP_HADJUSTMENT:
1239 g_value_set_object (value, tree_view->priv->hadjustment);
1240 break;
1241 case PROP_VADJUSTMENT:
1242 g_value_set_object (value, tree_view->priv->vadjustment);
1243 break;
1244 case PROP_HSCROLL_POLICY:
1245 g_value_set_enum (value, tree_view->priv->hscroll_policy);
1246 break;
1247 case PROP_VSCROLL_POLICY:
1248 g_value_set_enum (value, tree_view->priv->vscroll_policy);
1249 break;
1250 case PROP_HEADERS_VISIBLE:
1251 g_value_set_boolean (value, pspp_sheet_view_get_headers_visible (tree_view));
1252 break;
1253 case PROP_HEADERS_CLICKABLE:
1254 g_value_set_boolean (value, pspp_sheet_view_get_headers_clickable (tree_view));
1255 break;
1256 case PROP_REORDERABLE:
1257 g_value_set_boolean (value, tree_view->priv->reorderable);
1258 break;
1259 case PROP_RULES_HINT:
1260 g_value_set_boolean (value, tree_view->priv->has_rules);
1261 break;
1262 case PROP_ENABLE_SEARCH:
1263 g_value_set_boolean (value, tree_view->priv->enable_search);
1264 break;
1265 case PROP_SEARCH_COLUMN:
1266 g_value_set_int (value, tree_view->priv->search_column);
1267 break;
1268 case PROP_HOVER_SELECTION:
1269 g_value_set_boolean (value, tree_view->priv->hover_selection);
1270 break;
1271 case PROP_RUBBER_BANDING:
1272 g_value_set_boolean (value, tree_view->priv->rubber_banding_enable);
1273 break;
1274 case PROP_ENABLE_GRID_LINES:
1275 g_value_set_enum (value, tree_view->priv->grid_lines);
1276 break;
1277 case PROP_TOOLTIP_COLUMN:
1278 g_value_set_int (value, tree_view->priv->tooltip_column);
1279 break;
1280 case PROP_SPECIAL_CELLS:
1281 g_value_set_enum (value, tree_view->priv->special_cells);
1282 break;
1283 case PROP_FIXED_HEIGHT:
1284 g_value_set_int (value, pspp_sheet_view_get_fixed_height (tree_view));
1285 break;
1286 case PROP_FIXED_HEIGHT_SET:
1287 g_value_set_boolean (value, tree_view->priv->fixed_height_set);
1288 break;
1289 default:
1290 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1291 break;
1295 static void
1296 pspp_sheet_view_dispose (GObject *object)
1298 PsppSheetView *tree_view = PSPP_SHEET_VIEW (object);
1300 if (tree_view->dispose_has_run)
1301 return;
1303 tree_view->dispose_has_run = TRUE;
1305 if (tree_view->priv->selection != NULL)
1307 _pspp_sheet_selection_set_tree_view (tree_view->priv->selection, NULL);
1308 g_object_unref (tree_view->priv->selection);
1309 tree_view->priv->selection = NULL;
1312 if (tree_view->priv->hadjustment)
1314 g_object_unref (tree_view->priv->hadjustment);
1315 tree_view->priv->hadjustment = NULL;
1317 if (tree_view->priv->vadjustment)
1319 g_object_unref (tree_view->priv->vadjustment);
1320 tree_view->priv->vadjustment = NULL;
1323 if (tree_view->priv->button_style)
1325 g_object_unref (tree_view->priv->button_style);
1326 tree_view->priv->button_style = NULL;
1330 G_OBJECT_CLASS (pspp_sheet_view_parent_class)->dispose (object);
1335 static void
1336 pspp_sheet_view_buildable_add_child (GtkBuildable *tree_view,
1337 GtkBuilder *builder,
1338 GObject *child,
1339 const gchar *type)
1341 pspp_sheet_view_append_column (PSPP_SHEET_VIEW (tree_view), PSPP_SHEET_VIEW_COLUMN (child));
1344 static void
1345 pspp_sheet_view_finalize (GObject *object)
1347 PsppSheetView *tree_view = PSPP_SHEET_VIEW (object);
1349 pspp_sheet_view_stop_editing (tree_view, TRUE);
1351 if (tree_view->priv->selected != NULL)
1353 range_tower_destroy (tree_view->priv->selected);
1354 tree_view->priv->selected = NULL;
1358 tree_view->priv->prelight_node = -1;
1361 if (tree_view->priv->scroll_to_path != NULL)
1363 gtk_tree_row_reference_free (tree_view->priv->scroll_to_path);
1364 tree_view->priv->scroll_to_path = NULL;
1367 if (tree_view->priv->drag_dest_row != NULL)
1369 gtk_tree_row_reference_free (tree_view->priv->drag_dest_row);
1370 tree_view->priv->drag_dest_row = NULL;
1373 if (tree_view->priv->top_row != NULL)
1375 gtk_tree_row_reference_free (tree_view->priv->top_row);
1376 tree_view->priv->top_row = NULL;
1379 if (tree_view->priv->column_drop_func_data &&
1380 tree_view->priv->column_drop_func_data_destroy)
1382 tree_view->priv->column_drop_func_data_destroy (tree_view->priv->column_drop_func_data);
1383 tree_view->priv->column_drop_func_data = NULL;
1386 if (tree_view->priv->destroy_count_destroy &&
1387 tree_view->priv->destroy_count_data)
1389 tree_view->priv->destroy_count_destroy (tree_view->priv->destroy_count_data);
1390 tree_view->priv->destroy_count_data = NULL;
1393 gtk_tree_row_reference_free (tree_view->priv->cursor);
1394 tree_view->priv->cursor = NULL;
1396 gtk_tree_row_reference_free (tree_view->priv->anchor);
1397 tree_view->priv->anchor = NULL;
1399 /* destroy interactive search dialog */
1400 if (tree_view->priv->search_window)
1402 gtk_widget_destroy (tree_view->priv->search_window);
1403 tree_view->priv->search_window = NULL;
1404 tree_view->priv->search_entry = NULL;
1405 if (tree_view->priv->typeselect_flush_timeout)
1407 g_source_remove (tree_view->priv->typeselect_flush_timeout);
1408 tree_view->priv->typeselect_flush_timeout = 0;
1412 if (tree_view->priv->search_destroy && tree_view->priv->search_user_data)
1414 tree_view->priv->search_destroy (tree_view->priv->search_user_data);
1415 tree_view->priv->search_user_data = NULL;
1418 if (tree_view->priv->search_position_destroy && tree_view->priv->search_position_user_data)
1420 tree_view->priv->search_position_destroy (tree_view->priv->search_position_user_data);
1421 tree_view->priv->search_position_user_data = NULL;
1424 pspp_sheet_view_set_model (tree_view, NULL);
1427 G_OBJECT_CLASS (pspp_sheet_view_parent_class)->finalize (object);
1432 /* GtkWidget Methods
1435 /* GtkWidget::map helper */
1436 static void
1437 pspp_sheet_view_map_buttons (PsppSheetView *tree_view)
1439 GList *list;
1441 g_return_if_fail (gtk_widget_get_mapped (GTK_WIDGET (tree_view)));
1443 if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_HEADERS_VISIBLE))
1445 PsppSheetViewColumn *column;
1447 for (list = tree_view->priv->columns; list; list = list->next)
1449 column = list->data;
1450 if (column->button != NULL &&
1451 gtk_widget_get_visible (column->button) &&
1452 !gtk_widget_get_mapped (column->button))
1453 gtk_widget_map (column->button);
1455 for (list = tree_view->priv->columns; list; list = list->next)
1457 column = list->data;
1458 if (column->visible == FALSE || column->window == NULL)
1459 continue;
1460 if (column->resizable)
1462 gdk_window_raise (column->window);
1463 gdk_window_show (column->window);
1465 else
1466 gdk_window_hide (column->window);
1468 gdk_window_show (tree_view->priv->header_window);
1472 static void
1473 pspp_sheet_view_map (GtkWidget *widget)
1475 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
1476 GList *tmp_list;
1478 gtk_widget_set_mapped (widget, TRUE);
1480 tmp_list = tree_view->priv->children;
1481 while (tmp_list)
1483 PsppSheetViewChild *child = tmp_list->data;
1484 tmp_list = tmp_list->next;
1486 if (gtk_widget_get_visible (child->widget))
1488 if (!gtk_widget_get_mapped (child->widget))
1489 gtk_widget_map (child->widget);
1492 gdk_window_show (tree_view->priv->bin_window);
1494 pspp_sheet_view_map_buttons (tree_view);
1496 gdk_window_show (gtk_widget_get_window (widget));
1499 static void
1500 pspp_sheet_view_realize (GtkWidget *widget)
1502 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
1503 GList *tmp_list;
1504 GdkWindowAttr attributes;
1505 gint attributes_mask;
1506 GtkAllocation allocation;
1507 GtkAllocation old_allocation;
1509 gtk_widget_set_realized (widget, TRUE);
1511 gtk_widget_get_allocation (widget, &allocation);
1512 gtk_widget_get_allocation (widget, &old_allocation);
1514 /* Make the main, clipping window */
1515 attributes.window_type = GDK_WINDOW_CHILD;
1516 attributes.x = allocation.x;
1517 attributes.y = allocation.y;
1518 attributes.width = allocation.width;
1519 attributes.height = allocation.height;
1520 attributes.wclass = GDK_INPUT_OUTPUT;
1521 attributes.visual = gtk_widget_get_visual (widget);
1522 attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK;
1524 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
1526 gtk_widget_set_window (widget,
1527 gdk_window_new (gtk_widget_get_parent_window (widget),
1528 &attributes, attributes_mask));
1529 gdk_window_set_user_data (gtk_widget_get_window (widget), widget);
1531 /* Make the window for the tree */
1532 attributes.x = 0;
1533 attributes.y = TREE_VIEW_HEADER_HEIGHT (tree_view);
1534 attributes.width = MAX (tree_view->priv->width, old_allocation.width);
1535 attributes.height = old_allocation.height;
1536 attributes.event_mask = (GDK_EXPOSURE_MASK |
1537 GDK_SCROLL_MASK |
1538 GDK_POINTER_MOTION_MASK |
1539 GDK_ENTER_NOTIFY_MASK |
1540 GDK_LEAVE_NOTIFY_MASK |
1541 GDK_BUTTON_PRESS_MASK |
1542 GDK_BUTTON_RELEASE_MASK |
1543 gtk_widget_get_events (widget));
1545 tree_view->priv->bin_window = gdk_window_new (gtk_widget_get_window (widget),
1546 &attributes, attributes_mask);
1547 gdk_window_set_user_data (tree_view->priv->bin_window, widget);
1549 /* Make the column header window */
1550 attributes.x = 0;
1551 attributes.y = 0;
1552 attributes.width = MAX (tree_view->priv->width, old_allocation.width);
1553 attributes.height = tree_view->priv->header_height;
1554 attributes.event_mask = (GDK_EXPOSURE_MASK |
1555 GDK_SCROLL_MASK |
1556 GDK_BUTTON_PRESS_MASK |
1557 GDK_BUTTON_RELEASE_MASK |
1558 GDK_KEY_PRESS_MASK |
1559 GDK_KEY_RELEASE_MASK |
1560 gtk_widget_get_events (widget));
1562 tree_view->priv->header_window = gdk_window_new (gtk_widget_get_window (widget),
1563 &attributes, attributes_mask);
1564 gdk_window_set_user_data (tree_view->priv->header_window, widget);
1566 /* Add them all up. */
1567 gtk_widget_set_style (widget,
1568 gtk_style_attach (gtk_widget_get_style (widget), gtk_widget_get_window (widget)));
1569 gdk_window_set_background (tree_view->priv->bin_window, &gtk_widget_get_style (widget)->base[gtk_widget_get_state (widget)]);
1570 gtk_style_set_background (gtk_widget_get_style (widget), tree_view->priv->header_window, GTK_STATE_NORMAL);
1572 tmp_list = tree_view->priv->children;
1573 while (tmp_list)
1575 PsppSheetViewChild *child = tmp_list->data;
1576 tmp_list = tmp_list->next;
1578 gtk_widget_set_parent_window (child->widget, tree_view->priv->bin_window);
1581 for (tmp_list = tree_view->priv->columns; tmp_list; tmp_list = tmp_list->next)
1582 _pspp_sheet_view_column_realize_button (PSPP_SHEET_VIEW_COLUMN (tmp_list->data));
1584 /* Need to call those here, since they create GCs */
1585 pspp_sheet_view_set_grid_lines (tree_view, tree_view->priv->grid_lines);
1587 install_presize_handler (tree_view);
1590 static void
1591 pspp_sheet_view_unrealize (GtkWidget *widget)
1593 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
1594 PsppSheetViewPrivate *priv = tree_view->priv;
1595 GList *list;
1597 GTK_WIDGET_CLASS (pspp_sheet_view_parent_class)->unrealize (widget);
1599 if (priv->scroll_timeout != 0)
1601 g_source_remove (priv->scroll_timeout);
1602 priv->scroll_timeout = 0;
1605 if (priv->open_dest_timeout != 0)
1607 g_source_remove (priv->open_dest_timeout);
1608 priv->open_dest_timeout = 0;
1611 if (priv->presize_handler_timer != 0)
1613 g_source_remove (priv->presize_handler_timer);
1614 priv->presize_handler_timer = 0;
1617 if (priv->validate_rows_timer != 0)
1619 g_source_remove (priv->validate_rows_timer);
1620 priv->validate_rows_timer = 0;
1623 if (priv->scroll_sync_timer != 0)
1625 g_source_remove (priv->scroll_sync_timer);
1626 priv->scroll_sync_timer = 0;
1629 if (priv->typeselect_flush_timeout)
1631 g_source_remove (priv->typeselect_flush_timeout);
1632 priv->typeselect_flush_timeout = 0;
1635 for (list = priv->columns; list; list = list->next)
1636 _pspp_sheet_view_column_unrealize_button (PSPP_SHEET_VIEW_COLUMN (list->data));
1638 gdk_window_set_user_data (priv->bin_window, NULL);
1639 gdk_window_destroy (priv->bin_window);
1640 priv->bin_window = NULL;
1642 gdk_window_set_user_data (priv->header_window, NULL);
1643 gdk_window_destroy (priv->header_window);
1644 priv->header_window = NULL;
1646 if (priv->drag_window)
1648 gdk_window_set_user_data (priv->drag_window, NULL);
1649 gdk_window_destroy (priv->drag_window);
1650 priv->drag_window = NULL;
1653 if (priv->drag_highlight_window)
1655 gdk_window_set_user_data (priv->drag_highlight_window, NULL);
1656 gdk_window_destroy (priv->drag_highlight_window);
1657 priv->drag_highlight_window = NULL;
1660 if (tree_view->priv->columns != NULL)
1662 list = tree_view->priv->columns;
1663 while (list)
1665 PsppSheetViewColumn *column;
1666 column = PSPP_SHEET_VIEW_COLUMN (list->data);
1667 list = list->next;
1668 pspp_sheet_view_remove_column (tree_view, column);
1670 tree_view->priv->columns = NULL;
1674 /* GtkWidget::size_request helper */
1675 static void
1676 pspp_sheet_view_size_request_columns (PsppSheetView *tree_view)
1678 GList *list;
1680 tree_view->priv->header_height = 0;
1682 if (tree_view->priv->model)
1684 for (list = tree_view->priv->columns; list; list = list->next)
1686 GtkRequisition requisition;
1687 PsppSheetViewColumn *column = list->data;
1689 pspp_sheet_view_column_size_request (column, &requisition);
1690 column->button_request = requisition.width;
1691 tree_view->priv->header_height = MAX (tree_view->priv->header_height, requisition.height);
1697 /* Called only by ::size_request */
1698 static void
1699 pspp_sheet_view_update_size (PsppSheetView *tree_view)
1701 GList *list;
1702 PsppSheetViewColumn *column;
1703 gint i;
1705 if (tree_view->priv->model == NULL)
1707 tree_view->priv->width = 0;
1708 tree_view->priv->prev_width = 0;
1709 tree_view->priv->height = 0;
1710 return;
1713 tree_view->priv->prev_width = tree_view->priv->width;
1714 tree_view->priv->width = 0;
1716 /* keep this in sync with size_allocate below */
1717 for (list = tree_view->priv->columns, i = 0; list; list = list->next, i++)
1719 gint real_requested_width = 0;
1720 column = list->data;
1721 if (!column->visible)
1722 continue;
1724 if (column->use_resized_width)
1726 real_requested_width = column->resized_width;
1728 else
1730 real_requested_width = column->fixed_width;
1733 if (column->min_width != -1)
1734 real_requested_width = MAX (real_requested_width, column->min_width);
1735 if (column->max_width != -1)
1736 real_requested_width = MIN (real_requested_width, column->max_width);
1738 tree_view->priv->width += real_requested_width;
1741 tree_view->priv->height = tree_view->priv->fixed_height * tree_view->priv->row_count;
1744 static void
1745 pspp_sheet_view_size_request (GtkWidget *widget,
1746 GtkRequisition *requisition)
1748 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
1749 GList *tmp_list;
1751 /* we validate some rows initially just to make sure we have some size.
1752 * In practice, with a lot of static lists, this should get a good width.
1754 initialize_fixed_height_mode (tree_view);
1755 pspp_sheet_view_size_request_columns (tree_view);
1756 pspp_sheet_view_update_size (PSPP_SHEET_VIEW (widget));
1758 requisition->width = tree_view->priv->width;
1759 requisition->height = tree_view->priv->height + TREE_VIEW_HEADER_HEIGHT (tree_view);
1761 tmp_list = tree_view->priv->children;
1763 while (tmp_list)
1765 PsppSheetViewChild *child = tmp_list->data;
1766 GtkRequisition child_requisition;
1768 tmp_list = tmp_list->next;
1770 if (gtk_widget_get_visible (child->widget))
1771 gtk_widget_size_request (child->widget, &child_requisition);
1775 static void
1776 invalidate_column (PsppSheetView *tree_view,
1777 PsppSheetViewColumn *column)
1779 gint column_offset = 0;
1780 GList *list;
1781 GtkWidget *widget = GTK_WIDGET (tree_view);
1782 gboolean rtl;
1784 if (!gtk_widget_get_realized (widget))
1785 return;
1787 rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
1788 for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns));
1789 list;
1790 list = (rtl ? list->prev : list->next))
1792 PsppSheetViewColumn *tmpcolumn = list->data;
1793 if (tmpcolumn == column)
1795 GdkRectangle invalid_rect;
1796 GtkAllocation allocation;
1798 gtk_widget_get_allocation (widget, &allocation);
1799 invalid_rect.x = column_offset;
1800 invalid_rect.y = 0;
1801 invalid_rect.width = column->width;
1802 invalid_rect.height = allocation.height;
1804 gdk_window_invalidate_rect (gtk_widget_get_window (widget), &invalid_rect, TRUE);
1805 break;
1808 column_offset += tmpcolumn->width;
1812 static void
1813 invalidate_last_column (PsppSheetView *tree_view)
1815 GList *last_column;
1816 gboolean rtl;
1818 rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
1820 for (last_column = (rtl ? g_list_first (tree_view->priv->columns) : g_list_last (tree_view->priv->columns));
1821 last_column;
1822 last_column = (rtl ? last_column->next : last_column->prev))
1824 if (PSPP_SHEET_VIEW_COLUMN (last_column->data)->visible)
1826 invalidate_column (tree_view, last_column->data);
1827 return;
1832 static gint
1833 pspp_sheet_view_get_real_requested_width_from_column (PsppSheetView *tree_view,
1834 PsppSheetViewColumn *column)
1836 gint real_requested_width;
1838 if (column->use_resized_width)
1840 real_requested_width = column->resized_width;
1842 else
1844 real_requested_width = column->fixed_width;
1847 if (column->min_width != -1)
1848 real_requested_width = MAX (real_requested_width, column->min_width);
1849 if (column->max_width != -1)
1850 real_requested_width = MIN (real_requested_width, column->max_width);
1852 return real_requested_width;
1855 static gboolean
1856 span_intersects (int a0, int a_width,
1857 int b0, int b_width)
1859 int a1 = a0 + a_width;
1860 int b1 = b0 + b_width;
1861 return (a0 >= b0 && a0 < b1) || (b0 >= a0 && b0 < a1);
1864 /* GtkWidget::size_allocate helper */
1865 static void
1866 pspp_sheet_view_size_allocate_columns (GtkWidget *widget,
1867 gboolean *width_changed)
1869 PsppSheetView *tree_view;
1870 GList *list, *first_column, *last_column;
1871 PsppSheetViewColumn *column;
1872 GtkAllocation col_allocation;
1873 GtkAllocation allocation;
1874 gint width = 0;
1875 gint extra, extra_per_column;
1876 gint full_requested_width = 0;
1877 gint number_of_expand_columns = 0;
1878 gboolean column_changed = FALSE;
1879 gboolean rtl;
1881 tree_view = PSPP_SHEET_VIEW (widget);
1883 for (last_column = g_list_last (tree_view->priv->columns);
1884 last_column && !(PSPP_SHEET_VIEW_COLUMN (last_column->data)->visible);
1885 last_column = last_column->prev)
1888 if (last_column == NULL)
1889 return;
1891 for (first_column = g_list_first (tree_view->priv->columns);
1892 first_column && !(PSPP_SHEET_VIEW_COLUMN (first_column->data)->visible);
1893 first_column = first_column->next)
1896 col_allocation.y = 0;
1897 col_allocation.height = tree_view->priv->header_height;
1899 rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
1901 /* find out how many extra space and expandable columns we have */
1902 for (list = tree_view->priv->columns; list != last_column->next; list = list->next)
1904 column = (PsppSheetViewColumn *)list->data;
1906 if (!column->visible)
1907 continue;
1909 full_requested_width += pspp_sheet_view_get_real_requested_width_from_column (tree_view, column);
1911 if (column->expand)
1912 number_of_expand_columns++;
1915 gtk_widget_get_allocation (widget, &allocation);
1916 extra = MAX (allocation.width - full_requested_width, 0);
1917 if (number_of_expand_columns > 0)
1918 extra_per_column = extra/number_of_expand_columns;
1919 else
1920 extra_per_column = 0;
1922 for (list = (rtl ? last_column : first_column);
1923 list != (rtl ? first_column->prev : last_column->next);
1924 list = (rtl ? list->prev : list->next))
1926 gint real_requested_width = 0;
1927 gint old_width;
1929 column = list->data;
1930 old_width = column->width;
1932 if (!column->visible)
1933 continue;
1935 /* We need to handle the dragged button specially.
1937 if (column == tree_view->priv->drag_column)
1939 GtkAllocation drag_allocation;
1940 drag_allocation.width = gdk_window_get_width (tree_view->priv->drag_window);
1941 drag_allocation.height = gdk_window_get_height (tree_view->priv->drag_window);
1942 drag_allocation.x = 0;
1943 drag_allocation.y = 0;
1944 pspp_sheet_view_column_size_allocate (tree_view->priv->drag_column,
1945 &drag_allocation);
1946 width += drag_allocation.width;
1947 continue;
1950 real_requested_width = pspp_sheet_view_get_real_requested_width_from_column (tree_view, column);
1952 col_allocation.x = width;
1953 column->width = real_requested_width;
1955 if (column->expand)
1957 if (number_of_expand_columns == 1)
1959 /* We add the remander to the last column as
1960 * */
1961 column->width += extra;
1963 else
1965 column->width += extra_per_column;
1966 extra -= extra_per_column;
1967 number_of_expand_columns --;
1971 if (column->width != old_width)
1972 g_object_notify (G_OBJECT (column), "width");
1974 col_allocation.width = column->width;
1975 width += column->width;
1977 if (column->width > old_width)
1978 column_changed = TRUE;
1980 pspp_sheet_view_column_size_allocate (column, &col_allocation);
1982 if (column->window)
1983 gdk_window_move_resize (column->window,
1984 col_allocation.x + (rtl ? 0 : col_allocation.width) - TREE_VIEW_DRAG_WIDTH/2,
1985 col_allocation.y,
1986 TREE_VIEW_DRAG_WIDTH, col_allocation.height);
1989 /* We change the width here. The user might have been resizing columns,
1990 * so the total width of the tree view changes.
1992 tree_view->priv->width = width;
1993 if (width_changed)
1994 *width_changed = TRUE;
1996 if (column_changed)
1997 gtk_widget_queue_draw (GTK_WIDGET (tree_view));
2000 static void
2001 pspp_sheet_view_size_allocate (GtkWidget *widget,
2002 GtkAllocation *allocation)
2004 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
2005 GList *tmp_list;
2006 gboolean width_changed = FALSE;
2007 GtkAllocation old_allocation;
2008 gtk_widget_get_allocation (widget, &old_allocation);
2010 if (allocation->width != old_allocation.width)
2011 width_changed = TRUE;
2014 gtk_widget_set_allocation (widget, allocation);
2016 tmp_list = tree_view->priv->children;
2018 while (tmp_list)
2020 GtkAllocation allocation;
2022 PsppSheetViewChild *child = tmp_list->data;
2023 tmp_list = tmp_list->next;
2025 /* totally ignore our child's requisition */
2026 allocation.x = child->x;
2027 allocation.y = child->y;
2028 allocation.width = child->width;
2029 allocation.height = child->height;
2030 gtk_widget_size_allocate (child->widget, &allocation);
2033 /* We size-allocate the columns first because the width of the
2034 * tree view (used in updating the adjustments below) might change.
2036 pspp_sheet_view_size_allocate_columns (widget, &width_changed);
2038 gtk_adjustment_set_page_size (tree_view->priv->hadjustment, allocation->width);
2039 gtk_adjustment_set_page_increment (tree_view->priv->hadjustment, allocation->width * 0.9);
2040 gtk_adjustment_set_step_increment (tree_view->priv->hadjustment, allocation->width * 0.1);
2041 gtk_adjustment_set_lower (tree_view->priv->hadjustment, 0);
2042 gtk_adjustment_set_upper (tree_view->priv->hadjustment, MAX (gtk_adjustment_get_page_size (tree_view->priv->hadjustment), tree_view->priv->width));
2044 if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL)
2046 if (allocation->width < tree_view->priv->width)
2048 if (tree_view->priv->init_hadjust_value)
2050 gtk_adjustment_set_value (tree_view->priv->hadjustment, MAX (tree_view->priv->width - allocation->width, 0));
2051 tree_view->priv->init_hadjust_value = FALSE;
2053 else if (allocation->width != old_allocation.width)
2055 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));
2057 else
2058 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));
2060 else
2062 gtk_adjustment_set_value (tree_view->priv->hadjustment, 0);
2063 tree_view->priv->init_hadjust_value = TRUE;
2066 else
2067 if (gtk_adjustment_get_value (tree_view->priv->hadjustment) + allocation->width > tree_view->priv->width)
2068 gtk_adjustment_set_value (tree_view->priv->hadjustment, MAX (tree_view->priv->width - allocation->width, 0));
2070 gtk_adjustment_changed (tree_view->priv->hadjustment);
2072 gtk_adjustment_set_page_size (tree_view->priv->vadjustment, allocation->height - TREE_VIEW_HEADER_HEIGHT (tree_view));
2073 gtk_adjustment_set_step_increment (tree_view->priv->vadjustment, gtk_adjustment_get_page_size (tree_view->priv->vadjustment) * 0.1);
2074 gtk_adjustment_set_page_increment (tree_view->priv->vadjustment, gtk_adjustment_get_page_size (tree_view->priv->vadjustment) * 0.9);
2075 gtk_adjustment_set_lower (tree_view->priv->vadjustment, 0);
2076 gtk_adjustment_set_upper (tree_view->priv->vadjustment, MAX (gtk_adjustment_get_page_size (tree_view->priv->vadjustment), tree_view->priv->height));
2078 gtk_adjustment_changed (tree_view->priv->vadjustment);
2080 /* now the adjustments and window sizes are in sync, we can sync toprow/dy again */
2081 if (tree_view->priv->height <= gtk_adjustment_get_page_size (tree_view->priv->vadjustment))
2082 gtk_adjustment_set_value (GTK_ADJUSTMENT (tree_view->priv->vadjustment), 0);
2083 else if (gtk_adjustment_get_value (tree_view->priv->vadjustment) + gtk_adjustment_get_page_size (tree_view->priv->vadjustment) > tree_view->priv->height)
2084 gtk_adjustment_set_value (GTK_ADJUSTMENT (tree_view->priv->vadjustment),
2085 tree_view->priv->height - gtk_adjustment_get_page_size (tree_view->priv->vadjustment));
2086 else if (gtk_tree_row_reference_valid (tree_view->priv->top_row))
2087 pspp_sheet_view_top_row_to_dy (tree_view);
2088 else
2089 pspp_sheet_view_dy_to_top_row (tree_view);
2091 if (gtk_widget_get_realized (widget))
2093 gdk_window_move_resize (gtk_widget_get_window (widget),
2094 allocation->x, allocation->y,
2095 allocation->width, allocation->height);
2096 gdk_window_move_resize (tree_view->priv->header_window,
2097 - (gint) gtk_adjustment_get_value (tree_view->priv->hadjustment),
2099 MAX (tree_view->priv->width, allocation->width),
2100 tree_view->priv->header_height);
2101 gdk_window_move_resize (tree_view->priv->bin_window,
2102 - (gint) gtk_adjustment_get_value (tree_view->priv->hadjustment),
2103 TREE_VIEW_HEADER_HEIGHT (tree_view),
2104 MAX (tree_view->priv->width, allocation->width),
2105 allocation->height - TREE_VIEW_HEADER_HEIGHT (tree_view));
2108 if (tree_view->priv->row_count == 0)
2109 invalidate_empty_focus (tree_view);
2111 if (gtk_widget_get_realized (widget))
2113 gboolean has_expand_column = FALSE;
2114 for (tmp_list = tree_view->priv->columns; tmp_list; tmp_list = tmp_list->next)
2116 if (pspp_sheet_view_column_get_expand (PSPP_SHEET_VIEW_COLUMN (tmp_list->data)))
2118 has_expand_column = TRUE;
2119 break;
2123 /* This little hack only works if we have an LTR locale, and no column has the */
2124 if (width_changed)
2126 if (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_LTR &&
2127 ! has_expand_column)
2128 invalidate_last_column (tree_view);
2129 else
2130 gtk_widget_queue_draw (widget);
2135 /* Grabs the focus and unsets the PSPP_SHEET_VIEW_DRAW_KEYFOCUS flag */
2136 static void
2137 grab_focus_and_unset_draw_keyfocus (PsppSheetView *tree_view)
2139 GtkWidget *widget = GTK_WIDGET (tree_view);
2141 if (gtk_widget_get_can_focus (widget) && !gtk_widget_has_focus (widget))
2142 gtk_widget_grab_focus (widget);
2143 PSPP_SHEET_VIEW_UNSET_FLAG (tree_view, PSPP_SHEET_VIEW_DRAW_KEYFOCUS);
2146 gboolean
2147 pspp_sheet_view_node_is_selected (PsppSheetView *tree_view,
2148 int node)
2150 return node >= 0 && range_tower_contains (tree_view->priv->selected, node);
2153 void
2154 pspp_sheet_view_node_select (PsppSheetView *tree_view,
2155 int node)
2157 range_tower_set1 (tree_view->priv->selected, node, 1);
2160 void
2161 pspp_sheet_view_node_unselect (PsppSheetView *tree_view,
2162 int node)
2164 range_tower_set0 (tree_view->priv->selected, node, 1);
2167 gint
2168 pspp_sheet_view_node_next (PsppSheetView *tree_view,
2169 gint node)
2171 return node + 1 < tree_view->priv->row_count ? node + 1 : -1;
2174 gint
2175 pspp_sheet_view_node_prev (PsppSheetView *tree_view,
2176 gint node)
2178 return node > 0 ? node - 1 : -1;
2181 static gboolean
2182 all_columns_selected (PsppSheetView *tree_view)
2184 GList *list;
2186 for (list = tree_view->priv->columns; list; list = list->next)
2188 PsppSheetViewColumn *column = list->data;
2189 if (column->selectable && !column->selected)
2190 return FALSE;
2193 return TRUE;
2196 static gboolean
2197 pspp_sheet_view_row_head_clicked (PsppSheetView *tree_view,
2198 gint node,
2199 PsppSheetViewColumn *column,
2200 GdkEventButton *event)
2202 PsppSheetSelection *selection;
2203 PsppSheetSelectionMode mode;
2204 GtkTreePath *path;
2205 gboolean update_anchor;
2206 gboolean handled;
2207 guint modifiers;
2209 g_return_val_if_fail (tree_view != NULL, FALSE);
2210 g_return_val_if_fail (column != NULL, FALSE);
2212 selection = tree_view->priv->selection;
2213 mode = pspp_sheet_selection_get_mode (selection);
2214 if (mode != PSPP_SHEET_SELECTION_RECTANGLE)
2215 return FALSE;
2217 if (!column->row_head)
2218 return FALSE;
2220 if (event)
2222 modifiers = event->state & gtk_accelerator_get_default_mod_mask ();
2223 if (event->type != GDK_BUTTON_PRESS
2224 || (modifiers != GDK_CONTROL_MASK && modifiers != GDK_SHIFT_MASK))
2225 return FALSE;
2227 else
2228 modifiers = 0;
2230 path = gtk_tree_path_new_from_indices (node, -1);
2231 if (event == NULL)
2233 pspp_sheet_selection_unselect_all (selection);
2234 pspp_sheet_selection_select_path (selection, path);
2235 pspp_sheet_selection_select_all_columns (selection);
2236 update_anchor = TRUE;
2237 handled = TRUE;
2239 else if (event->type == GDK_BUTTON_PRESS && event->button == 3)
2241 if (pspp_sheet_selection_count_selected_rows (selection) <= 1
2242 || !all_columns_selected (tree_view))
2244 pspp_sheet_selection_unselect_all (selection);
2245 pspp_sheet_selection_select_path (selection, path);
2246 pspp_sheet_selection_select_all_columns (selection);
2247 update_anchor = TRUE;
2248 handled = FALSE;
2250 else
2251 update_anchor = handled = FALSE;
2253 else if (event->type == GDK_BUTTON_PRESS && event->button == 1
2254 && modifiers == GDK_CONTROL_MASK)
2256 if (!all_columns_selected (tree_view))
2258 pspp_sheet_selection_unselect_all (selection);
2259 pspp_sheet_selection_select_all_columns (selection);
2262 if (pspp_sheet_selection_path_is_selected (selection, path))
2263 pspp_sheet_selection_unselect_path (selection, path);
2264 else
2265 pspp_sheet_selection_select_path (selection, path);
2266 update_anchor = TRUE;
2267 handled = TRUE;
2269 else if (event->type == GDK_BUTTON_PRESS && event->button == 1
2270 && modifiers == GDK_SHIFT_MASK)
2272 GtkTreeRowReference *anchor = tree_view->priv->anchor;
2273 GtkTreePath *anchor_path;
2275 if (all_columns_selected (tree_view)
2276 && gtk_tree_row_reference_valid (anchor))
2278 update_anchor = FALSE;
2279 anchor_path = gtk_tree_row_reference_get_path (anchor);
2281 else
2283 update_anchor = TRUE;
2284 anchor_path = gtk_tree_path_copy (path);
2287 pspp_sheet_selection_unselect_all (selection);
2288 pspp_sheet_selection_select_range (selection, anchor_path, path);
2289 pspp_sheet_selection_select_all_columns (selection);
2291 gtk_tree_path_free (anchor_path);
2293 handled = TRUE;
2295 else
2296 update_anchor = handled = FALSE;
2298 if (update_anchor)
2300 if (tree_view->priv->anchor)
2301 gtk_tree_row_reference_free (tree_view->priv->anchor);
2302 tree_view->priv->anchor =
2303 gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view),
2304 tree_view->priv->model,
2305 path);
2308 gtk_tree_path_free (path);
2309 return handled;
2312 static gboolean
2313 find_click (PsppSheetView *tree_view,
2314 gint x, gint y,
2315 gint *node,
2316 PsppSheetViewColumn **column,
2317 GdkRectangle *background_area,
2318 GdkRectangle *cell_area)
2320 gint y_offset;
2321 gboolean rtl;
2322 GList *list;
2323 gint new_y;
2325 /* find the node that was clicked */
2326 new_y = TREE_WINDOW_Y_TO_RBTREE_Y(tree_view, y);
2327 if (new_y < 0)
2328 new_y = 0;
2329 y_offset = -pspp_sheet_view_find_offset (tree_view, new_y, node);
2331 if (*node < 0)
2332 return FALSE;
2334 background_area->y = y_offset + y;
2335 background_area->height = ROW_HEIGHT (tree_view);
2336 background_area->x = 0;
2338 /* Let the column have a chance at selecting it. */
2339 rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
2340 for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns));
2341 list; list = (rtl ? list->prev : list->next))
2343 PsppSheetViewColumn *candidate = list->data;
2345 if (!candidate->visible)
2346 continue;
2348 background_area->width = candidate->width;
2349 if ((background_area->x > x) ||
2350 (background_area->x + background_area->width <= x))
2352 background_area->x += background_area->width;
2353 continue;
2356 /* we found the focus column */
2358 pspp_sheet_view_adjust_cell_area (tree_view, candidate, background_area,
2359 TRUE, cell_area);
2360 *column = candidate;
2361 return TRUE;
2364 return FALSE;
2367 static gboolean
2368 pspp_sheet_view_button_press (GtkWidget *widget,
2369 GdkEventButton *event)
2371 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
2372 GList *list;
2373 PsppSheetViewColumn *column = NULL;
2374 gint i;
2375 GdkRectangle background_area;
2376 GdkRectangle cell_area;
2377 gboolean rtl;
2379 rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
2380 pspp_sheet_view_stop_editing (tree_view, FALSE);
2383 /* Because grab_focus can cause reentrancy, we delay grab_focus until after
2384 * we're done handling the button press.
2387 if (event->window == tree_view->priv->bin_window)
2389 int node;
2390 GtkTreePath *path;
2391 gint dval;
2392 gint pre_val, aft_val;
2393 PsppSheetViewColumn *column = NULL;
2394 GtkCellRenderer *focus_cell = NULL;
2395 gboolean row_double_click = FALSE;
2397 /* Empty tree? */
2398 if (tree_view->priv->row_count == 0)
2400 grab_focus_and_unset_draw_keyfocus (tree_view);
2401 return TRUE;
2404 if (!find_click (tree_view, event->x, event->y, &node, &column,
2405 &background_area, &cell_area))
2407 grab_focus_and_unset_draw_keyfocus (tree_view);
2408 return FALSE;
2411 tree_view->priv->focus_column = column;
2413 if (pspp_sheet_view_row_head_clicked (tree_view, node, column, event))
2414 return TRUE;
2416 /* select */
2417 pre_val = gtk_adjustment_get_value (tree_view->priv->vadjustment);
2419 path = _pspp_sheet_view_find_path (tree_view, node);
2421 /* we only handle selection modifications on the first button press
2423 if (event->type == GDK_BUTTON_PRESS)
2425 PsppSheetSelectionMode mode = 0;
2427 if ((event->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
2428 mode |= PSPP_SHEET_SELECT_MODE_TOGGLE;
2429 if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK)
2430 mode |= PSPP_SHEET_SELECT_MODE_EXTEND;
2432 focus_cell = _pspp_sheet_view_column_get_cell_at_pos (column, event->x - background_area.x);
2433 if (focus_cell)
2434 pspp_sheet_view_column_focus_cell (column, focus_cell);
2436 if (event->state & GDK_CONTROL_MASK)
2438 pspp_sheet_view_real_set_cursor (tree_view, path, FALSE, TRUE, mode);
2439 pspp_sheet_view_real_toggle_cursor_row (tree_view);
2441 else if (event->state & GDK_SHIFT_MASK)
2443 pspp_sheet_view_real_set_cursor (tree_view, path, TRUE, TRUE, mode);
2444 pspp_sheet_view_real_select_cursor_row (tree_view, FALSE, mode);
2446 else
2448 pspp_sheet_view_real_set_cursor (tree_view, path, TRUE, TRUE, 0);
2451 if (tree_view->priv->anchor_column == NULL ||
2452 !(event->state & GDK_SHIFT_MASK))
2453 tree_view->priv->anchor_column = column;
2454 pspp_sheet_selection_unselect_all_columns (tree_view->priv->selection);
2455 pspp_sheet_selection_select_column_range (tree_view->priv->selection,
2456 tree_view->priv->anchor_column,
2457 column);
2460 /* the treeview may have been scrolled because of _set_cursor,
2461 * correct here
2464 aft_val = gtk_adjustment_get_value (tree_view->priv->vadjustment);
2465 dval = pre_val - aft_val;
2467 cell_area.y += dval;
2468 background_area.y += dval;
2470 /* Save press to possibly begin a drag
2472 if (!tree_view->priv->in_grab &&
2473 tree_view->priv->pressed_button < 0)
2475 tree_view->priv->pressed_button = event->button;
2476 tree_view->priv->press_start_x = event->x;
2477 tree_view->priv->press_start_y = event->y;
2478 tree_view->priv->press_start_node = node;
2480 if (tree_view->priv->rubber_banding_enable
2481 && (tree_view->priv->selection->type == PSPP_SHEET_SELECTION_MULTIPLE ||
2482 tree_view->priv->selection->type == PSPP_SHEET_SELECTION_RECTANGLE))
2484 tree_view->priv->press_start_y += tree_view->priv->dy;
2485 tree_view->priv->rubber_band_x = event->x;
2486 tree_view->priv->rubber_band_y = event->y + tree_view->priv->dy;
2487 tree_view->priv->rubber_band_status = RUBBER_BAND_MAYBE_START;
2489 if ((event->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
2490 tree_view->priv->rubber_band_ctrl = TRUE;
2491 if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK)
2492 tree_view->priv->rubber_band_shift = TRUE;
2497 /* Test if a double click happened on the same row. */
2498 if (event->button == 1 && event->type == GDK_BUTTON_PRESS)
2500 int double_click_time, double_click_distance;
2502 g_object_get (gtk_settings_get_for_screen (
2503 gtk_widget_get_screen (widget)),
2504 "gtk-double-click-time", &double_click_time,
2505 "gtk-double-click-distance", &double_click_distance,
2506 NULL);
2508 /* Same conditions as _gdk_event_button_generate */
2509 if (tree_view->priv->last_button_x != -1 &&
2510 (event->time < tree_view->priv->last_button_time + double_click_time) &&
2511 (ABS (event->x - tree_view->priv->last_button_x) <= double_click_distance) &&
2512 (ABS (event->y - tree_view->priv->last_button_y) <= double_click_distance))
2514 /* We do no longer compare paths of this row and the
2515 * row clicked previously. We use the double click
2516 * distance to decide whether this is a valid click,
2517 * allowing the mouse to slightly move over another row.
2519 row_double_click = TRUE;
2521 tree_view->priv->last_button_time = 0;
2522 tree_view->priv->last_button_x = -1;
2523 tree_view->priv->last_button_y = -1;
2525 else
2527 tree_view->priv->last_button_time = event->time;
2528 tree_view->priv->last_button_x = event->x;
2529 tree_view->priv->last_button_y = event->y;
2533 if (row_double_click)
2535 gtk_grab_remove (widget);
2536 pspp_sheet_view_row_activated (tree_view, path, column);
2538 if (tree_view->priv->pressed_button == event->button)
2539 tree_view->priv->pressed_button = -1;
2542 gtk_tree_path_free (path);
2544 /* If we activated the row through a double click we don't want to grab
2545 * focus back, as moving focus to another widget is pretty common.
2547 if (!row_double_click)
2548 grab_focus_and_unset_draw_keyfocus (tree_view);
2550 return TRUE;
2553 /* We didn't click in the window. Let's check to see if we clicked on a column resize window.
2555 for (i = 0, list = tree_view->priv->columns; list; list = list->next, i++)
2557 column = list->data;
2558 if (event->window == column->window &&
2559 column->resizable &&
2560 column->window)
2562 gpointer drag_data;
2564 if (gdk_pointer_grab (column->window, FALSE,
2565 GDK_POINTER_MOTION_HINT_MASK |
2566 GDK_BUTTON1_MOTION_MASK |
2567 GDK_BUTTON_RELEASE_MASK,
2568 NULL, NULL, event->time))
2569 return FALSE;
2571 gtk_grab_add (widget);
2572 PSPP_SHEET_VIEW_SET_FLAG (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_RESIZE);
2573 column->resized_width = column->width;
2575 /* block attached dnd signal handler */
2576 drag_data = g_object_get_data (G_OBJECT (widget), "gtk-site-data");
2577 if (drag_data)
2578 g_signal_handlers_block_matched (widget,
2579 G_SIGNAL_MATCH_DATA,
2580 0, 0, NULL, NULL,
2581 drag_data);
2583 tree_view->priv->drag_pos = i;
2584 tree_view->priv->x_drag = column->allocation.x + (rtl ? 0 : column->allocation.width);
2586 if (!gtk_widget_has_focus (widget))
2587 gtk_widget_grab_focus (widget);
2589 return TRUE;
2592 return FALSE;
2595 /* GtkWidget::button_release_event helper */
2596 static gboolean
2597 pspp_sheet_view_button_release_drag_column (GtkWidget *widget,
2598 GdkEventButton *event)
2600 PsppSheetView *tree_view;
2601 GList *l;
2602 gboolean rtl;
2604 tree_view = PSPP_SHEET_VIEW (widget);
2606 rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
2607 gdk_display_pointer_ungrab (gtk_widget_get_display (widget), GDK_CURRENT_TIME);
2608 gdk_display_keyboard_ungrab (gtk_widget_get_display (widget), GDK_CURRENT_TIME);
2610 /* Move the button back */
2611 g_return_val_if_fail (tree_view->priv->drag_column->button, FALSE);
2613 g_object_ref (tree_view->priv->drag_column->button);
2614 gtk_container_remove (GTK_CONTAINER (tree_view), tree_view->priv->drag_column->button);
2615 gtk_widget_set_parent_window (tree_view->priv->drag_column->button, tree_view->priv->header_window);
2616 gtk_widget_set_parent (tree_view->priv->drag_column->button, GTK_WIDGET (tree_view));
2617 g_object_unref (tree_view->priv->drag_column->button);
2618 gtk_widget_queue_resize (widget);
2619 if (tree_view->priv->drag_column->resizable)
2621 gdk_window_raise (tree_view->priv->drag_column->window);
2622 gdk_window_show (tree_view->priv->drag_column->window);
2624 else
2625 gdk_window_hide (tree_view->priv->drag_column->window);
2627 gtk_widget_grab_focus (tree_view->priv->drag_column->button);
2629 if (rtl)
2631 if (tree_view->priv->cur_reorder &&
2632 tree_view->priv->cur_reorder->right_column != tree_view->priv->drag_column)
2633 pspp_sheet_view_move_column_after (tree_view, tree_view->priv->drag_column,
2634 tree_view->priv->cur_reorder->right_column);
2636 else
2638 if (tree_view->priv->cur_reorder &&
2639 tree_view->priv->cur_reorder->left_column != tree_view->priv->drag_column)
2640 pspp_sheet_view_move_column_after (tree_view, tree_view->priv->drag_column,
2641 tree_view->priv->cur_reorder->left_column);
2643 tree_view->priv->drag_column = NULL;
2644 gdk_window_hide (tree_view->priv->drag_window);
2646 for (l = tree_view->priv->column_drag_info; l != NULL; l = l->next)
2647 g_slice_free (PsppSheetViewColumnReorder, l->data);
2648 g_list_free (tree_view->priv->column_drag_info);
2649 tree_view->priv->column_drag_info = NULL;
2650 tree_view->priv->cur_reorder = NULL;
2652 if (tree_view->priv->drag_highlight_window)
2653 gdk_window_hide (tree_view->priv->drag_highlight_window);
2655 /* Reset our flags */
2656 tree_view->priv->drag_column_window_state = DRAG_COLUMN_WINDOW_STATE_UNSET;
2657 PSPP_SHEET_VIEW_UNSET_FLAG (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_DRAG);
2659 return TRUE;
2662 /* GtkWidget::button_release_event helper */
2663 static gboolean
2664 pspp_sheet_view_button_release_column_resize (GtkWidget *widget,
2665 GdkEventButton *event)
2667 PsppSheetView *tree_view;
2668 gpointer drag_data;
2670 tree_view = PSPP_SHEET_VIEW (widget);
2672 tree_view->priv->drag_pos = -1;
2674 /* unblock attached dnd signal handler */
2675 drag_data = g_object_get_data (G_OBJECT (widget), "gtk-site-data");
2676 if (drag_data)
2677 g_signal_handlers_unblock_matched (widget,
2678 G_SIGNAL_MATCH_DATA,
2679 0, 0, NULL, NULL,
2680 drag_data);
2682 PSPP_SHEET_VIEW_UNSET_FLAG (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_RESIZE);
2683 gtk_grab_remove (widget);
2684 gdk_display_pointer_ungrab (gdk_window_get_display (event->window),
2685 event->time);
2686 return TRUE;
2689 static gboolean
2690 pspp_sheet_view_button_release_edit (PsppSheetView *tree_view,
2691 GdkEventButton *event)
2693 GtkCellEditable *cell_editable;
2694 gchar *path_string;
2695 GtkTreePath *path;
2696 gint left, right;
2697 GtkTreeIter iter;
2698 PsppSheetViewColumn *column;
2699 GdkRectangle background_area;
2700 GdkRectangle cell_area;
2701 GdkRectangle area;
2702 guint modifiers;
2703 guint flags;
2704 int node;
2706 if (event->window != tree_view->priv->bin_window)
2707 return FALSE;
2709 /* Ignore a released button, if that button wasn't depressed */
2710 if (tree_view->priv->pressed_button != event->button)
2711 return FALSE;
2713 if (!find_click (tree_view, event->x, event->y, &node, &column, &background_area,
2714 &cell_area))
2715 return FALSE;
2717 /* decide if we edit */
2718 path = _pspp_sheet_view_find_path (tree_view, node);
2719 modifiers = event->state & gtk_accelerator_get_default_mod_mask ();
2720 if (event->button != 1 || modifiers)
2721 return FALSE;
2723 gtk_tree_model_get_iter (tree_view->priv->model, &iter, path);
2724 pspp_sheet_view_column_cell_set_cell_data (column,
2725 tree_view->priv->model,
2726 &iter);
2728 if (!pspp_sheet_view_column_get_quick_edit (column)
2729 && _pspp_sheet_view_column_has_editable_cell (column))
2730 return FALSE;
2732 flags = 0; /* FIXME: get the right flags */
2733 path_string = gtk_tree_path_to_string (path);
2735 if (!_pspp_sheet_view_column_cell_event (column,
2736 &cell_editable,
2737 (GdkEvent *)event,
2738 path_string,
2739 &background_area,
2740 &cell_area, flags))
2741 return FALSE;
2743 if (cell_editable == NULL)
2744 return FALSE;
2746 pspp_sheet_view_real_set_cursor (tree_view, path,
2747 TRUE, TRUE, 0); /* XXX mode? */
2748 gtk_widget_queue_draw (GTK_WIDGET (tree_view));
2750 area = cell_area;
2751 _pspp_sheet_view_column_get_neighbor_sizes (
2752 column, _pspp_sheet_view_column_get_edited_cell (column), &left, &right);
2754 area.x += left;
2755 area.width -= right + left;
2757 pspp_sheet_view_real_start_editing (tree_view,
2758 column,
2759 path,
2760 cell_editable,
2761 &area,
2762 (GdkEvent *)event,
2763 flags);
2764 g_free (path_string);
2765 gtk_tree_path_free (path);
2766 return TRUE;
2769 static gboolean
2770 pspp_sheet_view_button_release (GtkWidget *widget,
2771 GdkEventButton *event)
2773 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
2775 pspp_sheet_view_stop_editing (tree_view, FALSE);
2776 if (tree_view->priv->rubber_band_status != RUBBER_BAND_ACTIVE
2777 && pspp_sheet_view_button_release_edit (tree_view, event))
2779 if (tree_view->priv->pressed_button == event->button)
2780 tree_view->priv->pressed_button = -1;
2782 tree_view->priv->rubber_band_status = RUBBER_BAND_OFF;
2783 return TRUE;
2786 if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_DRAG))
2787 return pspp_sheet_view_button_release_drag_column (widget, event);
2789 if (tree_view->priv->rubber_band_status)
2790 pspp_sheet_view_stop_rubber_band (tree_view);
2792 if (tree_view->priv->pressed_button == event->button)
2793 tree_view->priv->pressed_button = -1;
2795 if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_RESIZE))
2796 return pspp_sheet_view_button_release_column_resize (widget, event);
2798 return FALSE;
2801 static gboolean
2802 pspp_sheet_view_grab_broken (GtkWidget *widget,
2803 GdkEventGrabBroken *event)
2805 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
2807 if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_DRAG))
2808 pspp_sheet_view_button_release_drag_column (widget, (GdkEventButton *)event);
2810 if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_RESIZE))
2811 pspp_sheet_view_button_release_column_resize (widget, (GdkEventButton *)event);
2813 return TRUE;
2816 /* GtkWidget::motion_event function set.
2819 static void
2820 do_prelight (PsppSheetView *tree_view,
2821 int node,
2822 /* these are in bin_window coords */
2823 gint x,
2824 gint y)
2826 int prev_node = tree_view->priv->prelight_node;
2828 if (prev_node != node)
2830 tree_view->priv->prelight_node = node;
2832 if (prev_node >= 0)
2833 _pspp_sheet_view_queue_draw_node (tree_view, prev_node, NULL);
2835 if (node >= 0)
2836 _pspp_sheet_view_queue_draw_node (tree_view, node, NULL);
2841 static void
2842 prelight_or_select (PsppSheetView *tree_view,
2843 int node,
2844 /* these are in bin_window coords */
2845 gint x,
2846 gint y)
2848 PsppSheetSelectionMode mode = pspp_sheet_selection_get_mode (tree_view->priv->selection);
2850 if (tree_view->priv->hover_selection &&
2851 (mode == PSPP_SHEET_SELECTION_SINGLE || mode == PSPP_SHEET_SELECTION_BROWSE) &&
2852 !(tree_view->priv->edited_column &&
2853 tree_view->priv->edited_column->editable_widget))
2855 if (node >= 0)
2857 if (!pspp_sheet_view_node_is_selected (tree_view, node))
2859 GtkTreePath *path;
2861 path = _pspp_sheet_view_find_path (tree_view, node);
2862 pspp_sheet_selection_select_path (tree_view->priv->selection, path);
2863 if (pspp_sheet_view_node_is_selected (tree_view, node))
2865 PSPP_SHEET_VIEW_UNSET_FLAG (tree_view, PSPP_SHEET_VIEW_DRAW_KEYFOCUS);
2866 pspp_sheet_view_real_set_cursor (tree_view, path, FALSE, FALSE, 0); /* XXX mode? */
2868 gtk_tree_path_free (path);
2872 else if (mode == PSPP_SHEET_SELECTION_SINGLE)
2873 pspp_sheet_selection_unselect_all (tree_view->priv->selection);
2876 do_prelight (tree_view, node, x, y);
2879 static void
2880 ensure_unprelighted (PsppSheetView *tree_view)
2882 do_prelight (tree_view,
2884 -1000, -1000); /* coords not possibly over an arrow */
2886 g_assert (tree_view->priv->prelight_node < 0);
2889 static void
2890 update_prelight (PsppSheetView *tree_view,
2891 gint x,
2892 gint y)
2894 int new_y;
2895 int node;
2897 if (tree_view->priv->row_count == 0)
2898 return;
2900 if (x == -10000)
2902 ensure_unprelighted (tree_view);
2903 return;
2906 new_y = TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, y);
2907 if (new_y < 0)
2908 new_y = 0;
2910 pspp_sheet_view_find_offset (tree_view, new_y, &node);
2912 if (node >= 0)
2913 prelight_or_select (tree_view, node, x, y);
2919 /* Our motion arrow is either a box (in the case of the original spot)
2920 * or an arrow. It is expander_size wide.
2923 * 11111111111111
2924 * 01111111111110
2925 * 00111111111100
2926 * 00011111111000
2927 * 00001111110000
2928 * 00000111100000
2929 * 00000111100000
2930 * 00000111100000
2931 * ~ ~ ~ ~ ~ ~ ~
2932 * 00000111100000
2933 * 00000111100000
2934 * 00000111100000
2935 * 00001111110000
2936 * 00011111111000
2937 * 00111111111100
2938 * 01111111111110
2939 * 11111111111111
2942 static void
2943 pspp_sheet_view_motion_draw_column_motion_arrow (PsppSheetView *tree_view)
2945 #if GTK3_TRANSITION
2946 PsppSheetViewColumnReorder *reorder = tree_view->priv->cur_reorder;
2947 GtkWidget *widget = GTK_WIDGET (tree_view);
2948 GdkBitmap *mask = NULL;
2949 gint x;
2950 gint y;
2951 gint width;
2952 gint height;
2953 gint arrow_type = DRAG_COLUMN_WINDOW_STATE_UNSET;
2954 GdkWindowAttr attributes;
2955 guint attributes_mask;
2957 if (!reorder ||
2958 reorder->left_column == tree_view->priv->drag_column ||
2959 reorder->right_column == tree_view->priv->drag_column)
2960 arrow_type = DRAG_COLUMN_WINDOW_STATE_ORIGINAL;
2961 else if (reorder->left_column || reorder->right_column)
2963 GdkRectangle visible_rect;
2964 pspp_sheet_view_get_visible_rect (tree_view, &visible_rect);
2965 if (reorder->left_column)
2966 x = reorder->left_column->allocation.x + reorder->left_column->allocation.width;
2967 else
2968 x = reorder->right_column->allocation.x;
2970 if (x < visible_rect.x)
2971 arrow_type = DRAG_COLUMN_WINDOW_STATE_ARROW_LEFT;
2972 else if (x > visible_rect.x + visible_rect.width)
2973 arrow_type = DRAG_COLUMN_WINDOW_STATE_ARROW_RIGHT;
2974 else
2975 arrow_type = DRAG_COLUMN_WINDOW_STATE_ARROW;
2978 /* We want to draw the rectangle over the initial location. */
2979 if (arrow_type == DRAG_COLUMN_WINDOW_STATE_ORIGINAL)
2981 GdkGC *gc;
2982 GdkColor col;
2984 if (tree_view->priv->drag_column_window_state != DRAG_COLUMN_WINDOW_STATE_ORIGINAL)
2986 if (tree_view->priv->drag_highlight_window)
2988 gdk_window_set_user_data (tree_view->priv->drag_highlight_window,
2989 NULL);
2990 gdk_window_destroy (tree_view->priv->drag_highlight_window);
2993 attributes.window_type = GDK_WINDOW_CHILD;
2994 attributes.wclass = GDK_INPUT_OUTPUT;
2995 attributes.x = tree_view->priv->drag_column_x;
2996 attributes.y = 0;
2997 width = attributes.width = tree_view->priv->drag_column->allocation.width;
2998 height = attributes.height = tree_view->priv->drag_column->allocation.height;
2999 attributes.visual = gtk_widget_get_visual (GTK_WIDGET (tree_view));
3000 attributes.colormap = gtk_widget_get_colormap (GTK_WIDGET (tree_view));
3001 attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK;
3002 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
3003 tree_view->priv->drag_highlight_window = gdk_window_new (tree_view->priv->header_window, &attributes, attributes_mask);
3004 gdk_window_set_user_data (tree_view->priv->drag_highlight_window, GTK_WIDGET (tree_view));
3006 mask = gdk_pixmap_new (tree_view->priv->drag_highlight_window, width, height, 1);
3007 gc = gdk_gc_new (mask);
3008 col.pixel = 1;
3009 gdk_gc_set_foreground (gc, &col);
3010 gdk_draw_rectangle (mask, gc, TRUE, 0, 0, width, height);
3011 col.pixel = 0;
3012 gdk_gc_set_foreground(gc, &col);
3013 gdk_draw_rectangle (mask, gc, TRUE, 2, 2, width - 4, height - 4);
3014 g_object_unref (gc);
3016 gdk_window_shape_combine_mask (tree_view->priv->drag_highlight_window,
3017 mask, 0, 0);
3018 if (mask) g_object_unref (mask);
3019 tree_view->priv->drag_column_window_state = DRAG_COLUMN_WINDOW_STATE_ORIGINAL;
3022 else if (arrow_type == DRAG_COLUMN_WINDOW_STATE_ARROW)
3024 gint i, j = 1;
3025 GdkGC *gc;
3026 GdkColor col;
3028 width = tree_view->priv->expander_size;
3030 /* Get x, y, width, height of arrow */
3031 gdk_window_get_origin (tree_view->priv->header_window, &x, &y);
3032 if (reorder->left_column)
3034 x += reorder->left_column->allocation.x + reorder->left_column->allocation.width - width/2;
3035 height = reorder->left_column->allocation.height;
3037 else
3039 x += reorder->right_column->allocation.x - width/2;
3040 height = reorder->right_column->allocation.height;
3042 y -= tree_view->priv->expander_size/2; /* The arrow takes up only half the space */
3043 height += tree_view->priv->expander_size;
3045 /* Create the new window */
3046 if (tree_view->priv->drag_column_window_state != DRAG_COLUMN_WINDOW_STATE_ARROW)
3048 if (tree_view->priv->drag_highlight_window)
3050 gdk_window_set_user_data (tree_view->priv->drag_highlight_window,
3051 NULL);
3052 gdk_window_destroy (tree_view->priv->drag_highlight_window);
3055 attributes.window_type = GDK_WINDOW_TEMP;
3056 attributes.wclass = GDK_INPUT_OUTPUT;
3057 attributes.visual = gtk_widget_get_visual (GTK_WIDGET (tree_view));
3058 attributes.colormap = gtk_widget_get_colormap (GTK_WIDGET (tree_view));
3059 attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK;
3060 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
3061 attributes.x = x;
3062 attributes.y = y;
3063 attributes.width = width;
3064 attributes.height = height;
3065 tree_view->priv->drag_highlight_window = gdk_window_new (gtk_widget_get_root_window (widget),
3066 &attributes, attributes_mask);
3067 gdk_window_set_user_data (tree_view->priv->drag_highlight_window, GTK_WIDGET (tree_view));
3069 mask = gdk_pixmap_new (tree_view->priv->drag_highlight_window, width, height, 1);
3070 gc = gdk_gc_new (mask);
3071 col.pixel = 1;
3072 gdk_gc_set_foreground (gc, &col);
3073 gdk_draw_rectangle (mask, gc, TRUE, 0, 0, width, height);
3075 /* Draw the 2 arrows as per above */
3076 col.pixel = 0;
3077 gdk_gc_set_foreground (gc, &col);
3078 for (i = 0; i < width; i ++)
3080 if (i == (width/2 - 1))
3081 continue;
3082 gdk_draw_line (mask, gc, i, j, i, height - j);
3083 if (i < (width/2 - 1))
3084 j++;
3085 else
3086 j--;
3088 g_object_unref (gc);
3089 gdk_window_shape_combine_mask (tree_view->priv->drag_highlight_window,
3090 mask, 0, 0);
3091 if (mask) g_object_unref (mask);
3094 tree_view->priv->drag_column_window_state = DRAG_COLUMN_WINDOW_STATE_ARROW;
3095 gdk_window_move (tree_view->priv->drag_highlight_window, x, y);
3097 else if (arrow_type == DRAG_COLUMN_WINDOW_STATE_ARROW_LEFT ||
3098 arrow_type == DRAG_COLUMN_WINDOW_STATE_ARROW_RIGHT)
3100 gint i, j = 1;
3101 GdkGC *gc;
3102 GdkColor col;
3104 width = tree_view->priv->expander_size;
3106 /* Get x, y, width, height of arrow */
3107 width = width/2; /* remember, the arrow only takes half the available width */
3108 gdk_window_get_origin (gtk_widget_get_window (widget), &x, &y);
3109 if (arrow_type == DRAG_COLUMN_WINDOW_STATE_ARROW_RIGHT)
3110 x += widget->allocation.width - width;
3112 if (reorder->left_column)
3113 height = reorder->left_column->allocation.height;
3114 else
3115 height = reorder->right_column->allocation.height;
3117 y -= tree_view->priv->expander_size;
3118 height += 2*tree_view->priv->expander_size;
3120 /* Create the new window */
3121 if (tree_view->priv->drag_column_window_state != DRAG_COLUMN_WINDOW_STATE_ARROW_LEFT &&
3122 tree_view->priv->drag_column_window_state != DRAG_COLUMN_WINDOW_STATE_ARROW_RIGHT)
3124 if (tree_view->priv->drag_highlight_window)
3126 gdk_window_set_user_data (tree_view->priv->drag_highlight_window,
3127 NULL);
3128 gdk_window_destroy (tree_view->priv->drag_highlight_window);
3131 attributes.window_type = GDK_WINDOW_TEMP;
3132 attributes.wclass = GDK_INPUT_OUTPUT;
3133 attributes.visual = gtk_widget_get_visual (GTK_WIDGET (tree_view));
3134 attributes.colormap = gtk_widget_get_colormap (GTK_WIDGET (tree_view));
3135 attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK;
3136 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
3137 attributes.x = x;
3138 attributes.y = y;
3139 attributes.width = width;
3140 attributes.height = height;
3141 tree_view->priv->drag_highlight_window = gdk_window_new (NULL, &attributes, attributes_mask);
3142 gdk_window_set_user_data (tree_view->priv->drag_highlight_window, GTK_WIDGET (tree_view));
3144 mask = gdk_pixmap_new (tree_view->priv->drag_highlight_window, width, height, 1);
3145 gc = gdk_gc_new (mask);
3146 col.pixel = 1;
3147 gdk_gc_set_foreground (gc, &col);
3148 gdk_draw_rectangle (mask, gc, TRUE, 0, 0, width, height);
3150 /* Draw the 2 arrows as per above */
3151 col.pixel = 0;
3152 gdk_gc_set_foreground (gc, &col);
3153 j = tree_view->priv->expander_size;
3154 for (i = 0; i < width; i ++)
3156 gint k;
3157 if (arrow_type == DRAG_COLUMN_WINDOW_STATE_ARROW_LEFT)
3158 k = width - i - 1;
3159 else
3160 k = i;
3161 gdk_draw_line (mask, gc, k, j, k, height - j);
3162 gdk_draw_line (mask, gc, k, 0, k, tree_view->priv->expander_size - j);
3163 gdk_draw_line (mask, gc, k, height, k, height - tree_view->priv->expander_size + j);
3164 j--;
3166 g_object_unref (gc);
3167 gdk_window_shape_combine_mask (tree_view->priv->drag_highlight_window,
3168 mask, 0, 0);
3169 if (mask) g_object_unref (mask);
3172 tree_view->priv->drag_column_window_state = arrow_type;
3173 gdk_window_move (tree_view->priv->drag_highlight_window, x, y);
3175 else
3177 g_warning (G_STRLOC"Invalid PsppSheetViewColumnReorder struct");
3178 gdk_window_hide (tree_view->priv->drag_highlight_window);
3179 return;
3182 gdk_window_show (tree_view->priv->drag_highlight_window);
3183 gdk_window_raise (tree_view->priv->drag_highlight_window);
3184 #endif
3187 static gboolean
3188 pspp_sheet_view_motion_resize_column (GtkWidget *widget,
3189 GdkEventMotion *event)
3191 gint x;
3192 gint new_width;
3193 PsppSheetViewColumn *column;
3194 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
3196 column = pspp_sheet_view_get_column (tree_view, tree_view->priv->drag_pos);
3198 if (event->is_hint || event->window != gtk_widget_get_window (widget))
3199 gtk_widget_get_pointer (widget, &x, NULL);
3200 else
3201 x = event->x;
3203 if (tree_view->priv->hadjustment)
3204 x += gtk_adjustment_get_value (tree_view->priv->hadjustment);
3206 new_width = pspp_sheet_view_new_column_width (tree_view,
3207 tree_view->priv->drag_pos, &x);
3208 if (x != tree_view->priv->x_drag &&
3209 (new_width != column->fixed_width))
3211 column->use_resized_width = TRUE;
3212 column->resized_width = new_width;
3213 #if 0
3214 if (column->expand)
3215 column->resized_width -= tree_view->priv->last_extra_space_per_column;
3216 #endif
3217 gtk_widget_queue_resize (widget);
3220 return FALSE;
3224 static void
3225 pspp_sheet_view_update_current_reorder (PsppSheetView *tree_view)
3227 PsppSheetViewColumnReorder *reorder = NULL;
3228 GList *list;
3229 gint mouse_x;
3231 gdk_window_get_pointer (tree_view->priv->header_window, &mouse_x, NULL, NULL);
3232 for (list = tree_view->priv->column_drag_info; list; list = list->next)
3234 reorder = (PsppSheetViewColumnReorder *) list->data;
3235 if (mouse_x >= reorder->left_align && mouse_x < reorder->right_align)
3236 break;
3237 reorder = NULL;
3240 /* if (reorder && reorder == tree_view->priv->cur_reorder)
3241 return;*/
3243 tree_view->priv->cur_reorder = reorder;
3244 pspp_sheet_view_motion_draw_column_motion_arrow (tree_view);
3247 static void
3248 pspp_sheet_view_vertical_autoscroll (PsppSheetView *tree_view)
3250 GdkRectangle visible_rect;
3251 gint y;
3252 gint offset;
3253 gfloat value;
3255 gdk_window_get_pointer (tree_view->priv->bin_window, NULL, &y, NULL);
3256 y += tree_view->priv->dy;
3258 pspp_sheet_view_get_visible_rect (tree_view, &visible_rect);
3260 /* see if we are near the edge. */
3261 offset = y - (visible_rect.y + 2 * SCROLL_EDGE_SIZE);
3262 if (offset > 0)
3264 offset = y - (visible_rect.y + visible_rect.height - 2 * SCROLL_EDGE_SIZE);
3265 if (offset < 0)
3266 return;
3269 value = CLAMP (gtk_adjustment_get_value (tree_view->priv->vadjustment) + offset, 0.0,
3270 gtk_adjustment_get_upper (tree_view->priv->vadjustment) - gtk_adjustment_get_page_size (tree_view->priv->vadjustment));
3271 gtk_adjustment_set_value (tree_view->priv->vadjustment, value);
3274 static gboolean
3275 pspp_sheet_view_horizontal_autoscroll (PsppSheetView *tree_view)
3277 GdkRectangle visible_rect;
3278 gint x;
3279 gint offset;
3280 gfloat value;
3282 gdk_window_get_pointer (tree_view->priv->bin_window, &x, NULL, NULL);
3284 pspp_sheet_view_get_visible_rect (tree_view, &visible_rect);
3286 /* See if we are near the edge. */
3287 offset = x - (visible_rect.x + SCROLL_EDGE_SIZE);
3288 if (offset > 0)
3290 offset = x - (visible_rect.x + visible_rect.width - SCROLL_EDGE_SIZE);
3291 if (offset < 0)
3292 return TRUE;
3294 offset = offset/3;
3296 value = CLAMP (gtk_adjustment_get_value (tree_view->priv->hadjustment) + offset,
3297 0.0, gtk_adjustment_get_upper (tree_view->priv->hadjustment) - gtk_adjustment_get_page_size (tree_view->priv->hadjustment));
3298 gtk_adjustment_set_value (tree_view->priv->hadjustment, value);
3300 return TRUE;
3304 static gboolean
3305 pspp_sheet_view_motion_drag_column (GtkWidget *widget,
3306 GdkEventMotion *event)
3308 PsppSheetView *tree_view = (PsppSheetView *) widget;
3309 PsppSheetViewColumn *column = tree_view->priv->drag_column;
3310 gint x, y;
3311 GtkAllocation allocation;
3313 /* Sanity Check */
3314 if ((column == NULL) ||
3315 (event->window != tree_view->priv->drag_window))
3316 return FALSE;
3318 /* Handle moving the header */
3319 gdk_window_get_position (tree_view->priv->drag_window, &x, &y);
3320 gtk_widget_get_allocation (GTK_WIDGET (tree_view), &allocation);
3321 x = CLAMP (x + (gint)event->x - column->drag_x, 0,
3322 MAX (tree_view->priv->width, allocation.width) - column->allocation.width);
3323 gdk_window_move (tree_view->priv->drag_window, x, y);
3325 /* autoscroll, if needed */
3326 pspp_sheet_view_horizontal_autoscroll (tree_view);
3327 /* Update the current reorder position and arrow; */
3328 pspp_sheet_view_update_current_reorder (tree_view);
3330 return TRUE;
3333 static void
3334 pspp_sheet_view_stop_rubber_band (PsppSheetView *tree_view)
3336 remove_scroll_timeout (tree_view);
3337 gtk_grab_remove (GTK_WIDGET (tree_view));
3339 if (tree_view->priv->rubber_band_status == RUBBER_BAND_ACTIVE)
3341 GtkTreePath *tmp_path;
3343 gtk_widget_queue_draw (GTK_WIDGET (tree_view));
3345 /* The anchor path should be set to the start path */
3346 tmp_path = _pspp_sheet_view_find_path (tree_view,
3347 tree_view->priv->rubber_band_start_node);
3349 if (tree_view->priv->anchor)
3350 gtk_tree_row_reference_free (tree_view->priv->anchor);
3352 tree_view->priv->anchor =
3353 gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view),
3354 tree_view->priv->model,
3355 tmp_path);
3357 gtk_tree_path_free (tmp_path);
3359 /* ... and the cursor to the end path */
3360 tmp_path = _pspp_sheet_view_find_path (tree_view,
3361 tree_view->priv->rubber_band_end_node);
3362 pspp_sheet_view_real_set_cursor (PSPP_SHEET_VIEW (tree_view), tmp_path, FALSE, FALSE, 0); /* XXX mode? */
3363 gtk_tree_path_free (tmp_path);
3365 _pspp_sheet_selection_emit_changed (tree_view->priv->selection);
3368 /* Clear status variables */
3369 tree_view->priv->rubber_band_status = RUBBER_BAND_OFF;
3370 tree_view->priv->rubber_band_shift = 0;
3371 tree_view->priv->rubber_band_ctrl = 0;
3373 tree_view->priv->rubber_band_start_node = -1;
3374 tree_view->priv->rubber_band_end_node = -1;
3377 static void
3378 pspp_sheet_view_update_rubber_band_selection_range (PsppSheetView *tree_view,
3379 int start_node,
3380 int end_node,
3381 gboolean select,
3382 gboolean skip_start,
3383 gboolean skip_end)
3385 if (start_node == end_node)
3386 return;
3388 /* We skip the first node and jump inside the loop */
3389 if (skip_start)
3390 goto skip_first;
3394 /* Small optimization by assuming insensitive nodes are never
3395 * selected.
3397 if (select)
3399 if (tree_view->priv->rubber_band_shift)
3400 pspp_sheet_view_node_select (tree_view, start_node);
3401 else if (tree_view->priv->rubber_band_ctrl)
3403 /* Toggle the selection state */
3404 if (pspp_sheet_view_node_is_selected (tree_view, start_node))
3405 pspp_sheet_view_node_unselect (tree_view, start_node);
3406 else
3407 pspp_sheet_view_node_select (tree_view, start_node);
3409 else
3410 pspp_sheet_view_node_select (tree_view, start_node);
3412 else
3414 /* Mirror the above */
3415 if (tree_view->priv->rubber_band_shift)
3416 pspp_sheet_view_node_unselect (tree_view, start_node);
3417 else if (tree_view->priv->rubber_band_ctrl)
3419 /* Toggle the selection state */
3420 if (pspp_sheet_view_node_is_selected (tree_view, start_node))
3421 pspp_sheet_view_node_unselect (tree_view, start_node);
3422 else
3423 pspp_sheet_view_node_select (tree_view, start_node);
3425 else
3426 pspp_sheet_view_node_unselect (tree_view, start_node);
3429 _pspp_sheet_view_queue_draw_node (tree_view, start_node, NULL);
3431 if (start_node == end_node)
3432 break;
3434 skip_first:
3436 start_node = pspp_sheet_view_node_next (tree_view, start_node);
3438 if (start_node < 0)
3439 /* Ran out of tree */
3440 break;
3442 if (skip_end && start_node == end_node)
3443 break;
3445 while (TRUE);
3448 static gint
3449 pspp_sheet_view_node_find_offset (PsppSheetView *tree_view,
3450 int node)
3452 return node * tree_view->priv->fixed_height;
3455 static gint
3456 pspp_sheet_view_find_offset (PsppSheetView *tree_view,
3457 gint height,
3458 int *new_node)
3460 int fixed_height = tree_view->priv->fixed_height;
3461 if (fixed_height <= 0
3462 || height < 0
3463 || height >= tree_view->priv->row_count * fixed_height)
3465 *new_node = -1;
3466 return 0;
3468 else
3470 *new_node = height / fixed_height;
3471 return height % fixed_height;
3475 static void
3476 pspp_sheet_view_update_rubber_band_selection (PsppSheetView *tree_view)
3478 int start_node;
3479 int end_node;
3481 pspp_sheet_view_find_offset (tree_view, MIN (tree_view->priv->press_start_y, tree_view->priv->rubber_band_y), &start_node);
3482 pspp_sheet_view_find_offset (tree_view, MAX (tree_view->priv->press_start_y, tree_view->priv->rubber_band_y), &end_node);
3484 /* Handle the start area first */
3485 if (tree_view->priv->rubber_band_start_node < 0)
3487 pspp_sheet_view_update_rubber_band_selection_range (tree_view,
3488 start_node,
3489 end_node,
3490 TRUE,
3491 FALSE,
3492 FALSE);
3494 else if (start_node < tree_view->priv->rubber_band_start_node)
3496 /* New node is above the old one; selection became bigger */
3497 pspp_sheet_view_update_rubber_band_selection_range (tree_view,
3498 start_node,
3499 tree_view->priv->rubber_band_start_node,
3500 TRUE,
3501 FALSE,
3502 TRUE);
3504 else if (start_node > tree_view->priv->rubber_band_start_node)
3506 /* New node is below the old one; selection became smaller */
3507 pspp_sheet_view_update_rubber_band_selection_range (tree_view,
3508 tree_view->priv->rubber_band_start_node,
3509 start_node,
3510 FALSE,
3511 FALSE,
3512 TRUE);
3515 tree_view->priv->rubber_band_start_node = start_node;
3517 /* Next, handle the end area */
3518 if (tree_view->priv->rubber_band_end_node < 0)
3520 /* In the event this happens, start_node was also -1; this case is
3521 * handled above.
3524 else if (end_node < 0)
3526 /* Find the last node in the tree */
3527 pspp_sheet_view_find_offset (tree_view, tree_view->priv->height - 1,
3528 &end_node);
3530 /* Selection reached end of the tree */
3531 pspp_sheet_view_update_rubber_band_selection_range (tree_view,
3532 tree_view->priv->rubber_band_end_node,
3533 end_node,
3534 TRUE,
3535 TRUE,
3536 FALSE);
3538 else if (end_node > tree_view->priv->rubber_band_end_node)
3540 /* New node is below the old one; selection became bigger */
3541 pspp_sheet_view_update_rubber_band_selection_range (tree_view,
3542 tree_view->priv->rubber_band_end_node,
3543 end_node,
3544 TRUE,
3545 TRUE,
3546 FALSE);
3548 else if (end_node < tree_view->priv->rubber_band_end_node)
3550 /* New node is above the old one; selection became smaller */
3551 pspp_sheet_view_update_rubber_band_selection_range (tree_view,
3552 end_node,
3553 tree_view->priv->rubber_band_end_node,
3554 FALSE,
3555 TRUE,
3556 FALSE);
3559 tree_view->priv->rubber_band_end_node = end_node;
3562 #define GDK_RECTANGLE_PTR(X) ((GdkRectangle *)(X))
3564 static void
3565 pspp_sheet_view_update_rubber_band (PsppSheetView *tree_view)
3567 gint x, y;
3568 cairo_rectangle_int_t old_area;
3569 cairo_rectangle_int_t new_area;
3570 cairo_rectangle_int_t common;
3571 cairo_region_t *invalid_region;
3572 PsppSheetViewColumn *column;
3574 old_area.x = MIN (tree_view->priv->press_start_x, tree_view->priv->rubber_band_x);
3575 old_area.y = MIN (tree_view->priv->press_start_y, tree_view->priv->rubber_band_y) - tree_view->priv->dy;
3576 old_area.width = ABS (tree_view->priv->rubber_band_x - tree_view->priv->press_start_x) + 1;
3577 old_area.height = ABS (tree_view->priv->rubber_band_y - tree_view->priv->press_start_y) + 1;
3579 gdk_window_get_pointer (tree_view->priv->bin_window, &x, &y, NULL);
3581 x = MAX (x, 0);
3582 y = MAX (y, 0) + tree_view->priv->dy;
3584 new_area.x = MIN (tree_view->priv->press_start_x, x);
3585 new_area.y = MIN (tree_view->priv->press_start_y, y) - tree_view->priv->dy;
3586 new_area.width = ABS (x - tree_view->priv->press_start_x) + 1;
3587 new_area.height = ABS (y - tree_view->priv->press_start_y) + 1;
3589 invalid_region = cairo_region_create_rectangle (&old_area);
3590 cairo_region_union_rectangle (invalid_region, &new_area);
3592 gdk_rectangle_intersect (GDK_RECTANGLE_PTR (&old_area),
3593 GDK_RECTANGLE_PTR (&new_area), GDK_RECTANGLE_PTR (&common));
3594 if (common.width > 2 && common.height > 2)
3596 cairo_region_t *common_region;
3598 /* make sure the border is invalidated */
3599 common.x += 1;
3600 common.y += 1;
3601 common.width -= 2;
3602 common.height -= 2;
3604 common_region = cairo_region_create_rectangle (&common);
3606 cairo_region_subtract (invalid_region, common_region);
3607 cairo_region_destroy (common_region);
3610 #if GTK_MAJOR_VERSION == 3
3611 gdk_window_invalidate_region (tree_view->priv->bin_window, invalid_region, TRUE);
3612 #else
3614 cairo_rectangle_int_t extents;
3615 GdkRegion *ereg;
3616 cairo_region_get_extents (invalid_region, &extents);
3617 ereg = gdk_region_rectangle (GDK_RECTANGLE_PTR (&extents));
3618 gdk_window_invalidate_region (tree_view->priv->bin_window, ereg, TRUE);
3619 gdk_region_destroy (ereg);
3621 #endif
3623 cairo_region_destroy (invalid_region);
3625 tree_view->priv->rubber_band_x = x;
3626 tree_view->priv->rubber_band_y = y;
3627 pspp_sheet_view_get_path_at_pos (tree_view, x, y, NULL, &column, NULL, NULL);
3629 pspp_sheet_selection_unselect_all_columns (tree_view->priv->selection);
3630 pspp_sheet_selection_select_column_range (tree_view->priv->selection,
3631 tree_view->priv->anchor_column,
3632 column);
3634 gtk_widget_queue_draw (GTK_WIDGET (tree_view));
3636 pspp_sheet_view_update_rubber_band_selection (tree_view);
3639 #if GTK3_TRANSITION
3640 static void
3641 pspp_sheet_view_paint_rubber_band (PsppSheetView *tree_view,
3642 GdkRectangle *area)
3644 cairo_t *cr;
3645 GdkRectangle rect;
3646 GdkRectangle rubber_rect;
3647 GtkStyle *style;
3649 return;
3650 rubber_rect.x = MIN (tree_view->priv->press_start_x, tree_view->priv->rubber_band_x);
3651 rubber_rect.y = MIN (tree_view->priv->press_start_y, tree_view->priv->rubber_band_y) - tree_view->priv->dy;
3652 rubber_rect.width = ABS (tree_view->priv->press_start_x - tree_view->priv->rubber_band_x) + 1;
3653 rubber_rect.height = ABS (tree_view->priv->press_start_y - tree_view->priv->rubber_band_y) + 1;
3655 if (!gdk_rectangle_intersect (&rubber_rect, area, &rect))
3656 return;
3658 cr = gdk_cairo_create (tree_view->priv->bin_window);
3659 cairo_set_line_width (cr, 1.0);
3661 style = gtk_widget_get_style (GTK_WIDGET (tree_view));
3662 cairo_set_source_rgba (cr,
3663 style->fg[GTK_STATE_NORMAL].red / 65535.,
3664 style->fg[GTK_STATE_NORMAL].green / 65535.,
3665 style->fg[GTK_STATE_NORMAL].blue / 65535.,
3666 .25);
3668 gdk_cairo_rectangle (cr, &rect);
3669 cairo_clip (cr);
3670 cairo_paint (cr);
3672 cairo_set_source_rgb (cr,
3673 style->fg[GTK_STATE_NORMAL].red / 65535.,
3674 style->fg[GTK_STATE_NORMAL].green / 65535.,
3675 style->fg[GTK_STATE_NORMAL].blue / 65535.);
3677 cairo_rectangle (cr,
3678 rubber_rect.x + 0.5, rubber_rect.y + 0.5,
3679 rubber_rect.width - 1, rubber_rect.height - 1);
3680 cairo_stroke (cr);
3682 cairo_destroy (cr);
3684 #endif
3687 static gboolean
3688 pspp_sheet_view_motion_bin_window (GtkWidget *widget,
3689 GdkEventMotion *event)
3691 PsppSheetView *tree_view;
3692 int node;
3693 gint new_y;
3695 tree_view = (PsppSheetView *) widget;
3697 if (tree_view->priv->row_count == 0)
3698 return FALSE;
3700 if (tree_view->priv->rubber_band_status == RUBBER_BAND_MAYBE_START)
3702 GdkRectangle background_area, cell_area;
3703 PsppSheetViewColumn *column;
3705 if (find_click (tree_view, event->x, event->y, &node, &column,
3706 &background_area, &cell_area)
3707 && tree_view->priv->focus_column == column
3708 && tree_view->priv->press_start_node == node)
3709 return FALSE;
3711 gtk_grab_add (GTK_WIDGET (tree_view));
3712 pspp_sheet_view_update_rubber_band (tree_view);
3714 tree_view->priv->rubber_band_status = RUBBER_BAND_ACTIVE;
3716 else if (tree_view->priv->rubber_band_status == RUBBER_BAND_ACTIVE)
3718 pspp_sheet_view_update_rubber_band (tree_view);
3720 add_scroll_timeout (tree_view);
3723 /* only check for an initiated drag when a button is pressed */
3724 if (tree_view->priv->pressed_button >= 0
3725 && !tree_view->priv->rubber_band_status)
3726 pspp_sheet_view_maybe_begin_dragging_row (tree_view, event);
3728 new_y = TREE_WINDOW_Y_TO_RBTREE_Y(tree_view, event->y);
3729 if (new_y < 0)
3730 new_y = 0;
3732 pspp_sheet_view_find_offset (tree_view, new_y, &node);
3734 tree_view->priv->event_last_x = event->x;
3735 tree_view->priv->event_last_y = event->y;
3737 prelight_or_select (tree_view, node, event->x, event->y);
3739 return TRUE;
3742 static gboolean
3743 pspp_sheet_view_motion (GtkWidget *widget,
3744 GdkEventMotion *event)
3746 PsppSheetView *tree_view;
3748 tree_view = (PsppSheetView *) widget;
3750 /* Resizing a column */
3751 if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_RESIZE))
3752 return pspp_sheet_view_motion_resize_column (widget, event);
3754 /* Drag column */
3755 if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_DRAG))
3756 return pspp_sheet_view_motion_drag_column (widget, event);
3758 /* Sanity check it */
3759 if (event->window == tree_view->priv->bin_window)
3760 return pspp_sheet_view_motion_bin_window (widget, event);
3762 return FALSE;
3765 /* Invalidate the focus rectangle near the edge of the bin_window; used when
3766 * the tree is empty.
3768 static void
3769 invalidate_empty_focus (PsppSheetView *tree_view)
3771 GdkRectangle area;
3773 if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
3774 return;
3776 area.x = 0;
3777 area.y = 0;
3778 area.width = gdk_window_get_width (tree_view->priv->bin_window);
3779 area.height = gdk_window_get_height (tree_view->priv->bin_window);
3780 gdk_window_invalidate_rect (tree_view->priv->bin_window, &area, FALSE);
3783 /* Draws a focus rectangle near the edge of the bin_window; used when the tree
3784 * is empty.
3786 static void
3787 draw_empty_focus (PsppSheetView *tree_view)
3789 GtkWidget *widget = GTK_WIDGET (tree_view);
3790 gint w, h;
3791 cairo_t *cr = gdk_cairo_create (tree_view->priv->bin_window);
3793 if (!gtk_widget_has_focus (widget))
3794 return;
3796 w = gdk_window_get_width (tree_view->priv->bin_window);
3797 h = gdk_window_get_height (tree_view->priv->bin_window);
3799 w -= 2;
3800 h -= 2;
3802 if (w > 0 && h > 0)
3803 gtk_paint_focus (gtk_widget_get_style (widget),
3805 gtk_widget_get_state (widget),
3806 widget,
3807 NULL,
3808 1, 1, w, h);
3809 cairo_destroy (cr);
3812 static void
3813 pspp_sheet_view_draw_vertical_grid_lines (PsppSheetView *tree_view,
3814 cairo_t *cr,
3815 gint n_visible_columns,
3816 gint min_y,
3817 gint max_y)
3819 GList *list = tree_view->priv->columns;
3820 gint i = 0;
3821 gint current_x = 0;
3823 if (tree_view->priv->grid_lines != PSPP_SHEET_VIEW_GRID_LINES_VERTICAL
3824 && tree_view->priv->grid_lines != PSPP_SHEET_VIEW_GRID_LINES_BOTH)
3825 return;
3827 /* Only draw the lines for visible rows and columns */
3828 for (list = tree_view->priv->columns; list; list = list->next, i++)
3830 PsppSheetViewColumn *column = list->data;
3831 gint x;
3833 if (! column->visible)
3834 continue;
3836 current_x += column->width;
3838 /* Generally the grid lines should fit within the column, but for the
3839 last visible column we put it just past the end of the column.
3840 (Otherwise horizontal grid lines sometimes stick out by one pixel.) */
3841 x = current_x;
3842 if (i != n_visible_columns - 1)
3843 x--;
3845 cairo_set_line_width (cr, 1.0);
3846 cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
3847 cairo_move_to (cr, x + 0.5, min_y);
3848 cairo_line_to (cr, x + 0.5, max_y - min_y);
3849 cairo_stroke (cr);
3853 /* Warning: Very scary function.
3854 * Modify at your own risk
3856 * KEEP IN SYNC WITH pspp_sheet_view_create_row_drag_icon()!
3857 * FIXME: It's not...
3859 static gboolean
3860 pspp_sheet_view_bin_expose (GtkWidget *widget,
3861 cairo_t *cr)
3863 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
3864 GtkTreePath *path;
3865 GList *list;
3866 int node;
3867 int cursor = -1;
3868 int drag_highlight = -1;
3869 GtkTreeIter iter;
3870 gint new_y;
3871 gint y_offset, cell_offset;
3872 gint max_height;
3873 GdkRectangle background_area;
3874 GdkRectangle cell_area;
3875 guint flags;
3876 gint bin_window_width;
3877 gint bin_window_height;
3878 GtkTreePath *cursor_path;
3879 GtkTreePath *drag_dest_path;
3880 GList *first_column, *last_column;
3881 gint vertical_separator;
3882 gint horizontal_separator;
3883 gint focus_line_width;
3884 gboolean allow_rules;
3885 gboolean has_special_cell;
3886 gboolean rtl;
3887 gint n_visible_columns;
3888 gint grid_line_width;
3889 gboolean row_ending_details;
3890 gboolean draw_vgrid_lines, draw_hgrid_lines;
3891 gint min_y, max_y;
3893 GdkRectangle Zarea;
3894 GtkAllocation allocation;
3895 gtk_widget_get_allocation (widget, &allocation);
3897 Zarea.x = 0;
3898 Zarea.y = 0;
3899 Zarea.height = allocation.height;
3901 rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
3903 gtk_widget_style_get (widget,
3904 "horizontal-separator", &horizontal_separator,
3905 "vertical-separator", &vertical_separator,
3906 "allow-rules", &allow_rules,
3907 "focus-line-width", &focus_line_width,
3908 "row-ending-details", &row_ending_details,
3909 NULL);
3911 if (tree_view->priv->row_count == 0)
3913 draw_empty_focus (tree_view);
3914 return TRUE;
3917 #if GTK3_TRANSITION
3918 /* clip event->area to the visible area */
3919 if (Zarea.height < 0.5)
3920 return TRUE;
3921 #endif
3923 validate_visible_area (tree_view);
3925 new_y = TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, Zarea.y);
3927 if (new_y < 0)
3928 new_y = 0;
3929 y_offset = -pspp_sheet_view_find_offset (tree_view, new_y, &node);
3930 bin_window_width =
3931 gdk_window_get_width (tree_view->priv->bin_window);
3933 bin_window_height =
3934 gdk_window_get_height (tree_view->priv->bin_window);
3937 if (tree_view->priv->height < bin_window_height)
3939 gtk_paint_flat_box (gtk_widget_get_style (widget),
3941 gtk_widget_get_state (widget),
3942 GTK_SHADOW_NONE,
3943 widget,
3944 "cell_even",
3945 0, tree_view->priv->height,
3946 bin_window_width,
3947 bin_window_height - tree_view->priv->height);
3950 if (node < 0)
3951 return TRUE;
3953 /* find the path for the node */
3954 path = _pspp_sheet_view_find_path ((PsppSheetView *)widget, node);
3955 gtk_tree_model_get_iter (tree_view->priv->model,
3956 &iter,
3957 path);
3958 gtk_tree_path_free (path);
3960 cursor_path = NULL;
3961 drag_dest_path = NULL;
3963 if (tree_view->priv->cursor)
3964 cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
3966 if (cursor_path)
3967 _pspp_sheet_view_find_node (tree_view, cursor_path, &cursor);
3969 if (tree_view->priv->drag_dest_row)
3970 drag_dest_path = gtk_tree_row_reference_get_path (tree_view->priv->drag_dest_row);
3972 if (drag_dest_path)
3973 _pspp_sheet_view_find_node (tree_view, drag_dest_path,
3974 &drag_highlight);
3976 draw_vgrid_lines =
3977 tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_VERTICAL
3978 || tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_BOTH;
3979 draw_hgrid_lines =
3980 tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_HORIZONTAL
3981 || tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_BOTH;
3983 if (draw_vgrid_lines || draw_hgrid_lines)
3984 gtk_widget_style_get (widget, "grid-line-width", &grid_line_width, NULL);
3986 n_visible_columns = 0;
3987 for (list = tree_view->priv->columns; list; list = list->next)
3989 if (! PSPP_SHEET_VIEW_COLUMN (list->data)->visible)
3990 continue;
3991 n_visible_columns ++;
3994 /* Find the last column */
3995 for (last_column = g_list_last (tree_view->priv->columns);
3996 last_column && !(PSPP_SHEET_VIEW_COLUMN (last_column->data)->visible);
3997 last_column = last_column->prev)
4000 /* and the first */
4001 for (first_column = g_list_first (tree_view->priv->columns);
4002 first_column && !(PSPP_SHEET_VIEW_COLUMN (first_column->data)->visible);
4003 first_column = first_column->next)
4006 /* Actually process the expose event. To do this, we want to
4007 * start at the first node of the event, and walk the tree in
4008 * order, drawing each successive node.
4011 min_y = y_offset;
4014 gboolean parity;
4015 gboolean is_first = FALSE;
4016 gboolean is_last = FALSE;
4017 gboolean done = FALSE;
4018 gboolean selected;
4020 max_height = ROW_HEIGHT (tree_view);
4022 cell_offset = 0;
4024 background_area.y = y_offset + Zarea.y;
4025 background_area.height = max_height;
4026 max_y = background_area.y + max_height;
4028 flags = 0;
4030 if (node == tree_view->priv->prelight_node)
4031 flags |= GTK_CELL_RENDERER_PRELIT;
4033 selected = pspp_sheet_view_node_is_selected (tree_view, node);
4035 parity = node % 2;
4037 if (tree_view->priv->special_cells == PSPP_SHEET_VIEW_SPECIAL_CELLS_DETECT)
4039 /* we *need* to set cell data on all cells before the call
4040 * to _has_special_cell, else _has_special_cell() does not
4041 * return a correct value.
4043 for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns));
4044 list;
4045 list = (rtl ? list->prev : list->next))
4047 PsppSheetViewColumn *column = list->data;
4048 pspp_sheet_view_column_cell_set_cell_data (column,
4049 tree_view->priv->model,
4050 &iter);
4053 has_special_cell = pspp_sheet_view_has_special_cell (tree_view);
4055 else
4056 has_special_cell = tree_view->priv->special_cells == PSPP_SHEET_VIEW_SPECIAL_CELLS_YES;
4058 for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns));
4059 list;
4060 list = (rtl ? list->prev : list->next))
4062 PsppSheetViewColumn *column = list->data;
4063 const gchar *detail = NULL;
4064 gboolean selected_column;
4065 GtkStateType state;
4067 if (!column->visible)
4068 continue;
4070 if (tree_view->priv->selection->type == PSPP_SHEET_SELECTION_RECTANGLE)
4071 selected_column = column->selected && column->selectable;
4072 else
4073 selected_column = TRUE;
4075 #if GTK3_TRANSITION
4076 if (cell_offset > Zarea.x + Zarea.width ||
4077 cell_offset + column->width < Zarea.x)
4079 cell_offset += column->width;
4080 continue;
4082 #endif
4084 if (selected && selected_column)
4085 flags |= GTK_CELL_RENDERER_SELECTED;
4086 else
4087 flags &= ~GTK_CELL_RENDERER_SELECTED;
4089 if (column->show_sort_indicator)
4090 flags |= GTK_CELL_RENDERER_SORTED;
4091 else
4092 flags &= ~GTK_CELL_RENDERER_SORTED;
4094 if (cursor == node)
4095 flags |= GTK_CELL_RENDERER_FOCUSED;
4096 else
4097 flags &= ~GTK_CELL_RENDERER_FOCUSED;
4099 background_area.x = cell_offset;
4100 background_area.width = column->width;
4102 cell_area = background_area;
4103 cell_area.y += vertical_separator / 2;
4104 cell_area.x += horizontal_separator / 2;
4105 cell_area.height -= vertical_separator;
4106 cell_area.width -= horizontal_separator;
4108 if (draw_vgrid_lines)
4110 if (list == first_column)
4112 cell_area.width -= grid_line_width / 2;
4114 else if (list == last_column)
4116 cell_area.x += grid_line_width / 2;
4117 cell_area.width -= grid_line_width / 2;
4119 else
4121 cell_area.x += grid_line_width / 2;
4122 cell_area.width -= grid_line_width;
4126 if (draw_hgrid_lines)
4128 cell_area.y += grid_line_width / 2;
4129 cell_area.height -= grid_line_width;
4132 #if GTK3_TRANSITION
4133 if (gdk_region_rect_in (event->region, &background_area) == GDK_OVERLAP_RECTANGLE_OUT)
4135 cell_offset += column->width;
4136 continue;
4138 #endif
4140 pspp_sheet_view_column_cell_set_cell_data (column,
4141 tree_view->priv->model,
4142 &iter);
4144 /* Select the detail for drawing the cell. relevant
4145 * factors are parity, sortedness, and whether to
4146 * display rules.
4148 if (allow_rules && tree_view->priv->has_rules)
4150 if ((flags & GTK_CELL_RENDERER_SORTED) &&
4151 n_visible_columns >= 3)
4153 if (parity)
4154 detail = "cell_odd_ruled_sorted";
4155 else
4156 detail = "cell_even_ruled_sorted";
4158 else
4160 if (parity)
4161 detail = "cell_odd_ruled";
4162 else
4163 detail = "cell_even_ruled";
4166 else
4168 if ((flags & GTK_CELL_RENDERER_SORTED) &&
4169 n_visible_columns >= 3)
4171 if (parity)
4172 detail = "cell_odd_sorted";
4173 else
4174 detail = "cell_even_sorted";
4176 else
4178 if (parity)
4179 detail = "cell_odd";
4180 else
4181 detail = "cell_even";
4185 g_assert (detail);
4187 if (gtk_widget_get_state (widget) == GTK_STATE_INSENSITIVE)
4188 state = GTK_STATE_INSENSITIVE;
4189 else if (flags & GTK_CELL_RENDERER_SELECTED)
4190 state = GTK_STATE_SELECTED;
4191 else
4192 state = GTK_STATE_NORMAL;
4194 /* Draw background */
4195 if (row_ending_details)
4197 char new_detail[128];
4199 is_first = (rtl ? !list->next : !list->prev);
4200 is_last = (rtl ? !list->prev : !list->next);
4202 /* (I don't like the snprintfs either, but couldn't find a
4203 * less messy way).
4205 if (is_first && is_last)
4206 g_snprintf (new_detail, 127, "%s", detail);
4207 else if (is_first)
4208 g_snprintf (new_detail, 127, "%s_start", detail);
4209 else if (is_last)
4210 g_snprintf (new_detail, 127, "%s_end", detail);
4211 else
4212 g_snprintf (new_detail, 128, "%s_middle", detail);
4214 gtk_paint_flat_box (gtk_widget_get_style (widget),
4216 state,
4217 GTK_SHADOW_NONE,
4218 widget,
4219 new_detail,
4220 background_area.x,
4221 background_area.y,
4222 background_area.width,
4223 background_area.height);
4225 else
4227 gtk_paint_flat_box (gtk_widget_get_style (widget),
4229 state,
4230 GTK_SHADOW_NONE,
4231 widget,
4232 detail,
4233 background_area.x,
4234 background_area.y,
4235 background_area.width,
4236 background_area.height);
4239 if (draw_hgrid_lines)
4241 cairo_set_line_width (cr, 1.0);
4242 cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
4244 if (background_area.y >= 0)
4246 #if GTK3_TRANSITION
4247 gdk_draw_line (event->window,
4248 tree_view->priv->grid_line_gc[widget->state],
4249 background_area.x, background_area.y,
4250 background_area.x + background_area.width,
4251 background_area.y);
4252 #else
4253 cairo_move_to (cr, background_area.x, background_area.y - 0.5);
4254 cairo_line_to (cr, background_area.x + background_area.width,
4255 background_area.y - 0.5);
4256 #endif
4259 if (y_offset + max_height <= Zarea.height - 0.5)
4261 #if GTK3_TRANSITION
4262 gdk_draw_line (event->window,
4263 tree_view->priv->grid_line_gc[widget->state],
4264 background_area.x, background_area.y + max_height,
4265 background_area.x + background_area.width,
4266 background_area.y + max_height);
4267 #else
4269 cairo_move_to (cr, background_area.x, background_area.y + max_height - 0.5);
4270 cairo_line_to (cr, background_area.x + background_area.width,
4271 background_area.y + max_height - 0.5);
4272 #endif
4274 cairo_stroke (cr);
4277 _pspp_sheet_view_column_cell_render (column,
4279 &background_area,
4280 &cell_area,
4281 flags);
4283 if (node == cursor && has_special_cell &&
4284 ((column == tree_view->priv->focus_column &&
4285 PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_DRAW_KEYFOCUS) &&
4286 gtk_widget_has_focus (widget)) ||
4287 (column == tree_view->priv->edited_column)))
4289 _pspp_sheet_view_column_cell_draw_focus (column,
4291 &background_area,
4292 &cell_area,
4293 flags);
4296 cell_offset += column->width;
4299 if (cell_offset < Zarea.x)
4301 gtk_paint_flat_box (gtk_widget_get_style (widget),
4303 GTK_STATE_NORMAL,
4304 GTK_SHADOW_NONE,
4305 widget,
4306 "base",
4307 cell_offset,
4308 background_area.y,
4309 Zarea.x - cell_offset,
4310 background_area.height);
4313 if (node == drag_highlight)
4315 /* Draw indicator for the drop
4317 gint highlight_y = -1;
4318 int node = -1;
4319 gint width;
4321 switch (tree_view->priv->drag_dest_pos)
4323 case PSPP_SHEET_VIEW_DROP_BEFORE:
4324 highlight_y = background_area.y - 1;
4325 if (highlight_y < 0)
4326 highlight_y = 0;
4327 break;
4329 case PSPP_SHEET_VIEW_DROP_AFTER:
4330 highlight_y = background_area.y + background_area.height - 1;
4331 break;
4333 case PSPP_SHEET_VIEW_DROP_INTO_OR_BEFORE:
4334 case PSPP_SHEET_VIEW_DROP_INTO_OR_AFTER:
4335 _pspp_sheet_view_find_node (tree_view, drag_dest_path, &node);
4337 if (node < 0)
4338 break;
4339 width = gdk_window_get_width (tree_view->priv->bin_window);
4341 if (row_ending_details)
4342 gtk_paint_focus (gtk_widget_get_style (widget),
4344 gtk_widget_get_state (widget),
4345 widget,
4346 (is_first
4347 ? (is_last ? "treeview-drop-indicator" : "treeview-drop-indicator-left" )
4348 : (is_last ? "treeview-drop-indicator-right" : "tree-view-drop-indicator-middle" )),
4349 0, BACKGROUND_FIRST_PIXEL (tree_view, node)
4350 - focus_line_width / 2,
4351 width, ROW_HEIGHT (tree_view)
4352 - focus_line_width + 1);
4353 else
4354 gtk_paint_focus (gtk_widget_get_style (widget),
4356 gtk_widget_get_state (widget),
4357 widget,
4358 "treeview-drop-indicator",
4359 0, BACKGROUND_FIRST_PIXEL (tree_view, node)
4360 - focus_line_width / 2,
4361 width, ROW_HEIGHT (tree_view)
4362 - focus_line_width + 1);
4363 break;
4366 #if GTK3_TRANSITION
4367 if (highlight_y >= 0)
4369 gdk_draw_line (event->window,
4370 widget->style->fg_gc[gtk_widget_get_state (widget)],
4372 highlight_y,
4373 rtl ? 0 : bin_window_width,
4374 highlight_y);
4376 #endif
4379 /* draw the big row-spanning focus rectangle, if needed */
4380 if (!has_special_cell && node == cursor &&
4381 PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_DRAW_KEYFOCUS) &&
4382 gtk_widget_has_focus (widget))
4384 gint tmp_y, tmp_height;
4385 gint width;
4386 GtkStateType focus_rect_state;
4388 focus_rect_state =
4389 flags & GTK_CELL_RENDERER_SELECTED ? GTK_STATE_SELECTED :
4390 (flags & GTK_CELL_RENDERER_PRELIT ? GTK_STATE_PRELIGHT :
4391 (flags & GTK_CELL_RENDERER_INSENSITIVE ? GTK_STATE_INSENSITIVE :
4392 GTK_STATE_NORMAL));
4394 width = gdk_window_get_width (tree_view->priv->bin_window);
4396 if (draw_hgrid_lines)
4398 tmp_y = BACKGROUND_FIRST_PIXEL (tree_view, node) + grid_line_width / 2;
4399 tmp_height = ROW_HEIGHT (tree_view) - grid_line_width;
4401 else
4403 tmp_y = BACKGROUND_FIRST_PIXEL (tree_view, node);
4404 tmp_height = ROW_HEIGHT (tree_view);
4407 if (row_ending_details)
4408 gtk_paint_focus (gtk_widget_get_style (widget),
4410 focus_rect_state,
4411 widget,
4412 (is_first
4413 ? (is_last ? "treeview" : "treeview-left" )
4414 : (is_last ? "treeview-right" : "treeview-middle" )),
4415 0, tmp_y,
4416 width, tmp_height);
4417 else
4418 gtk_paint_focus (gtk_widget_get_style (widget),
4420 focus_rect_state,
4421 widget,
4422 "treeview",
4423 0, tmp_y,
4424 width, tmp_height);
4427 y_offset += max_height;
4431 node = pspp_sheet_view_node_next (tree_view, node);
4432 if (node >= 0)
4434 gboolean has_next = gtk_tree_model_iter_next (tree_view->priv->model, &iter);
4435 done = TRUE;
4437 /* Sanity Check! */
4438 TREE_VIEW_INTERNAL_ASSERT (has_next, FALSE);
4440 else
4441 goto done;
4443 while (!done);
4445 while (y_offset < Zarea.height);
4447 done:
4448 pspp_sheet_view_draw_vertical_grid_lines (tree_view, cr, n_visible_columns,
4449 min_y, max_y);
4451 #if GTK3_TRANSITION
4452 if (tree_view->priv->rubber_band_status == RUBBER_BAND_ACTIVE)
4454 GdkRectangle *rectangles;
4455 gint n_rectangles;
4457 gdk_region_get_rectangles (event->region,
4458 &rectangles,
4459 &n_rectangles);
4461 while (n_rectangles--)
4462 pspp_sheet_view_paint_rubber_band (tree_view, &rectangles[n_rectangles]);
4464 g_free (rectangles);
4466 #endif
4468 if (cursor_path)
4469 gtk_tree_path_free (cursor_path);
4471 if (drag_dest_path)
4472 gtk_tree_path_free (drag_dest_path);
4474 return FALSE;
4478 static gboolean
4479 pspp_sheet_view_draw (GtkWidget *widget,
4480 cairo_t *cr)
4482 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
4484 if (gtk_cairo_should_draw_window (cr, tree_view->priv->bin_window))
4486 gboolean retval;
4487 GList *tmp_list;
4489 cairo_save (cr);
4490 gtk_cairo_transform_to_window(cr,widget,tree_view->priv->bin_window);
4491 retval = pspp_sheet_view_bin_expose (widget, cr);
4492 cairo_restore (cr);
4494 /* We can't just chain up to Container::expose as it will try to send the
4495 * event to the headers, so we handle propagating it to our children
4496 * (eg. widgets being edited) ourselves.
4498 tmp_list = tree_view->priv->children;
4499 while (tmp_list)
4501 PsppSheetViewChild *child = tmp_list->data;
4502 tmp_list = tmp_list->next;
4504 gtk_container_propagate_draw (GTK_CONTAINER (tree_view), child->widget, cr);
4507 return retval;
4509 else if (gtk_cairo_should_draw_window (cr, tree_view->priv->header_window))
4511 gint n_visible_columns;
4512 GList *list;
4514 for (list = tree_view->priv->columns; list != NULL; list = list->next)
4516 PsppSheetViewColumn *column = list->data;
4518 if (column == tree_view->priv->drag_column || !column->visible)
4519 continue;
4521 if (span_intersects (column->allocation.x, column->allocation.width,
4522 (int) gtk_adjustment_get_value (tree_view->priv->hadjustment),
4523 (int) gtk_widget_get_allocated_width (widget))
4524 && column->button != NULL)
4525 gtk_container_propagate_draw (GTK_CONTAINER (tree_view),
4526 column->button, cr);
4529 n_visible_columns = 0;
4530 for (list = tree_view->priv->columns; list; list = list->next)
4532 if (! PSPP_SHEET_VIEW_COLUMN (list->data)->visible)
4533 continue;
4534 n_visible_columns ++;
4536 cairo_save (cr);
4537 gtk_cairo_transform_to_window(cr,widget,tree_view->priv->header_window);
4538 pspp_sheet_view_draw_vertical_grid_lines (tree_view,
4540 n_visible_columns,
4542 TREE_VIEW_HEADER_HEIGHT (tree_view));
4543 cairo_restore (cr);
4545 return TRUE;
4547 else if (gtk_cairo_should_draw_window (cr, tree_view->priv->drag_window))
4549 gtk_container_propagate_draw (GTK_CONTAINER (tree_view),
4550 tree_view->priv->drag_column->button,
4551 cr);
4553 return TRUE;
4556 return FALSE;
4559 enum
4561 DROP_HOME,
4562 DROP_RIGHT,
4563 DROP_LEFT,
4564 DROP_END
4567 /* returns 0x1 when no column has been found -- yes it's hackish */
4568 static PsppSheetViewColumn *
4569 pspp_sheet_view_get_drop_column (PsppSheetView *tree_view,
4570 PsppSheetViewColumn *column,
4571 gint drop_position)
4573 PsppSheetViewColumn *left_column = NULL;
4574 PsppSheetViewColumn *cur_column = NULL;
4575 GList *tmp_list;
4577 if (!column->reorderable)
4578 return (PsppSheetViewColumn *)0x1;
4580 switch (drop_position)
4582 case DROP_HOME:
4583 /* find first column where we can drop */
4584 tmp_list = tree_view->priv->columns;
4585 if (column == PSPP_SHEET_VIEW_COLUMN (tmp_list->data))
4586 return (PsppSheetViewColumn *)0x1;
4588 while (tmp_list)
4590 g_assert (tmp_list);
4592 cur_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
4593 tmp_list = tmp_list->next;
4595 if (left_column && left_column->visible == FALSE)
4596 continue;
4598 if (!tree_view->priv->column_drop_func)
4599 return left_column;
4601 if (!tree_view->priv->column_drop_func (tree_view, column, left_column, cur_column, tree_view->priv->column_drop_func_data))
4603 left_column = cur_column;
4604 continue;
4607 return left_column;
4610 if (!tree_view->priv->column_drop_func)
4611 return left_column;
4613 if (tree_view->priv->column_drop_func (tree_view, column, left_column, NULL, tree_view->priv->column_drop_func_data))
4614 return left_column;
4615 else
4616 return (PsppSheetViewColumn *)0x1;
4617 break;
4619 case DROP_RIGHT:
4620 /* find first column after column where we can drop */
4621 tmp_list = tree_view->priv->columns;
4623 for (; tmp_list; tmp_list = tmp_list->next)
4624 if (PSPP_SHEET_VIEW_COLUMN (tmp_list->data) == column)
4625 break;
4627 if (!tmp_list || !tmp_list->next)
4628 return (PsppSheetViewColumn *)0x1;
4630 tmp_list = tmp_list->next;
4631 left_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
4632 tmp_list = tmp_list->next;
4634 while (tmp_list)
4636 g_assert (tmp_list);
4638 cur_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
4639 tmp_list = tmp_list->next;
4641 if (left_column && left_column->visible == FALSE)
4643 left_column = cur_column;
4644 if (tmp_list)
4645 tmp_list = tmp_list->next;
4646 continue;
4649 if (!tree_view->priv->column_drop_func)
4650 return left_column;
4652 if (!tree_view->priv->column_drop_func (tree_view, column, left_column, cur_column, tree_view->priv->column_drop_func_data))
4654 left_column = cur_column;
4655 continue;
4658 return left_column;
4661 if (!tree_view->priv->column_drop_func)
4662 return left_column;
4664 if (tree_view->priv->column_drop_func (tree_view, column, left_column, NULL, tree_view->priv->column_drop_func_data))
4665 return left_column;
4666 else
4667 return (PsppSheetViewColumn *)0x1;
4668 break;
4670 case DROP_LEFT:
4671 /* find first column before column where we can drop */
4672 tmp_list = tree_view->priv->columns;
4674 for (; tmp_list; tmp_list = tmp_list->next)
4675 if (PSPP_SHEET_VIEW_COLUMN (tmp_list->data) == column)
4676 break;
4678 if (!tmp_list || !tmp_list->prev)
4679 return (PsppSheetViewColumn *)0x1;
4681 tmp_list = tmp_list->prev;
4682 cur_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
4683 tmp_list = tmp_list->prev;
4685 while (tmp_list)
4687 g_assert (tmp_list);
4689 left_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
4691 if (left_column && !left_column->visible)
4693 /*if (!tmp_list->prev)
4694 return (PsppSheetViewColumn *)0x1;
4697 cur_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->prev->data);
4698 tmp_list = tmp_list->prev->prev;
4699 continue;*/
4701 cur_column = left_column;
4702 if (tmp_list)
4703 tmp_list = tmp_list->prev;
4704 continue;
4707 if (!tree_view->priv->column_drop_func)
4708 return left_column;
4710 if (tree_view->priv->column_drop_func (tree_view, column, left_column, cur_column, tree_view->priv->column_drop_func_data))
4711 return left_column;
4713 cur_column = left_column;
4714 tmp_list = tmp_list->prev;
4717 if (!tree_view->priv->column_drop_func)
4718 return NULL;
4720 if (tree_view->priv->column_drop_func (tree_view, column, NULL, cur_column, tree_view->priv->column_drop_func_data))
4721 return NULL;
4722 else
4723 return (PsppSheetViewColumn *)0x1;
4724 break;
4726 case DROP_END:
4727 /* same as DROP_HOME case, but doing it backwards */
4728 tmp_list = g_list_last (tree_view->priv->columns);
4729 cur_column = NULL;
4731 if (column == PSPP_SHEET_VIEW_COLUMN (tmp_list->data))
4732 return (PsppSheetViewColumn *)0x1;
4734 while (tmp_list)
4736 g_assert (tmp_list);
4738 left_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
4740 if (left_column && !left_column->visible)
4742 cur_column = left_column;
4743 tmp_list = tmp_list->prev;
4746 if (!tree_view->priv->column_drop_func)
4747 return left_column;
4749 if (tree_view->priv->column_drop_func (tree_view, column, left_column, cur_column, tree_view->priv->column_drop_func_data))
4750 return left_column;
4752 cur_column = left_column;
4753 tmp_list = tmp_list->prev;
4756 if (!tree_view->priv->column_drop_func)
4757 return NULL;
4759 if (tree_view->priv->column_drop_func (tree_view, column, NULL, cur_column, tree_view->priv->column_drop_func_data))
4760 return NULL;
4761 else
4762 return (PsppSheetViewColumn *)0x1;
4763 break;
4766 return (PsppSheetViewColumn *)0x1;
4769 static gboolean
4770 pspp_sheet_view_key_press (GtkWidget *widget,
4771 GdkEventKey *event)
4773 PsppSheetView *tree_view = (PsppSheetView *) widget;
4775 if (tree_view->priv->rubber_band_status)
4777 if (event->keyval == GDK_Escape)
4778 pspp_sheet_view_stop_rubber_band (tree_view);
4780 return TRUE;
4783 if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_DRAG))
4785 if (event->keyval == GDK_Escape)
4787 tree_view->priv->cur_reorder = NULL;
4788 pspp_sheet_view_button_release_drag_column (widget, NULL);
4790 return TRUE;
4793 if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_HEADERS_VISIBLE))
4795 GList *focus_column;
4796 gboolean rtl;
4798 rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
4800 for (focus_column = tree_view->priv->columns;
4801 focus_column;
4802 focus_column = focus_column->next)
4804 PsppSheetViewColumn *column = PSPP_SHEET_VIEW_COLUMN (focus_column->data);
4806 if (column->button && gtk_widget_has_focus (column->button))
4807 break;
4810 if (focus_column &&
4811 (event->state & GDK_SHIFT_MASK) && (event->state & GDK_MOD1_MASK) &&
4812 (event->keyval == GDK_Left || event->keyval == GDK_KP_Left
4813 || event->keyval == GDK_Right || event->keyval == GDK_KP_Right))
4815 PsppSheetViewColumn *column = PSPP_SHEET_VIEW_COLUMN (focus_column->data);
4817 if (!column->resizable)
4819 gtk_widget_error_bell (widget);
4820 return TRUE;
4823 if (event->keyval == (rtl ? GDK_Right : GDK_Left)
4824 || event->keyval == (rtl ? GDK_KP_Right : GDK_KP_Left))
4826 gint old_width = column->resized_width;
4828 column->resized_width = MAX (column->resized_width,
4829 column->width);
4830 column->resized_width -= 2;
4831 if (column->resized_width < 0)
4832 column->resized_width = 0;
4834 if (column->min_width == -1)
4835 column->resized_width = MAX (column->button_request,
4836 column->resized_width);
4837 else
4838 column->resized_width = MAX (column->min_width,
4839 column->resized_width);
4841 if (column->max_width != -1)
4842 column->resized_width = MIN (column->resized_width,
4843 column->max_width);
4845 column->use_resized_width = TRUE;
4847 if (column->resized_width != old_width)
4848 gtk_widget_queue_resize (widget);
4849 else
4850 gtk_widget_error_bell (widget);
4852 else if (event->keyval == (rtl ? GDK_Left : GDK_Right)
4853 || event->keyval == (rtl ? GDK_KP_Left : GDK_KP_Right))
4855 gint old_width = column->resized_width;
4857 column->resized_width = MAX (column->resized_width,
4858 column->width);
4859 column->resized_width += 2;
4861 if (column->max_width != -1)
4862 column->resized_width = MIN (column->resized_width,
4863 column->max_width);
4865 column->use_resized_width = TRUE;
4867 if (column->resized_width != old_width)
4868 gtk_widget_queue_resize (widget);
4869 else
4870 gtk_widget_error_bell (widget);
4873 return TRUE;
4876 if (focus_column &&
4877 (event->state & GDK_MOD1_MASK) &&
4878 (event->keyval == GDK_Left || event->keyval == GDK_KP_Left
4879 || event->keyval == GDK_Right || event->keyval == GDK_KP_Right
4880 || event->keyval == GDK_Home || event->keyval == GDK_KP_Home
4881 || event->keyval == GDK_End || event->keyval == GDK_KP_End))
4883 PsppSheetViewColumn *column = PSPP_SHEET_VIEW_COLUMN (focus_column->data);
4885 if (event->keyval == (rtl ? GDK_Right : GDK_Left)
4886 || event->keyval == (rtl ? GDK_KP_Right : GDK_KP_Left))
4888 PsppSheetViewColumn *col;
4889 col = pspp_sheet_view_get_drop_column (tree_view, column, DROP_LEFT);
4890 if (col != (PsppSheetViewColumn *)0x1)
4891 pspp_sheet_view_move_column_after (tree_view, column, col);
4892 else
4893 gtk_widget_error_bell (widget);
4895 else if (event->keyval == (rtl ? GDK_Left : GDK_Right)
4896 || event->keyval == (rtl ? GDK_KP_Left : GDK_KP_Right))
4898 PsppSheetViewColumn *col;
4899 col = pspp_sheet_view_get_drop_column (tree_view, column, DROP_RIGHT);
4900 if (col != (PsppSheetViewColumn *)0x1)
4901 pspp_sheet_view_move_column_after (tree_view, column, col);
4902 else
4903 gtk_widget_error_bell (widget);
4905 else if (event->keyval == GDK_Home || event->keyval == GDK_KP_Home)
4907 PsppSheetViewColumn *col;
4908 col = pspp_sheet_view_get_drop_column (tree_view, column, DROP_HOME);
4909 if (col != (PsppSheetViewColumn *)0x1)
4910 pspp_sheet_view_move_column_after (tree_view, column, col);
4911 else
4912 gtk_widget_error_bell (widget);
4914 else if (event->keyval == GDK_End || event->keyval == GDK_KP_End)
4916 PsppSheetViewColumn *col;
4917 col = pspp_sheet_view_get_drop_column (tree_view, column, DROP_END);
4918 if (col != (PsppSheetViewColumn *)0x1)
4919 pspp_sheet_view_move_column_after (tree_view, column, col);
4920 else
4921 gtk_widget_error_bell (widget);
4924 return TRUE;
4928 /* Chain up to the parent class. It handles the keybindings. */
4929 if (GTK_WIDGET_CLASS (pspp_sheet_view_parent_class)->key_press_event (widget, event))
4930 return TRUE;
4932 if (tree_view->priv->search_entry_avoid_unhandled_binding)
4934 tree_view->priv->search_entry_avoid_unhandled_binding = FALSE;
4935 return FALSE;
4938 /* We pass the event to the search_entry. If its text changes, then we start
4939 * the typeahead find capabilities. */
4940 if (gtk_widget_has_focus (GTK_WIDGET (tree_view))
4941 && tree_view->priv->enable_search
4942 && !tree_view->priv->search_custom_entry_set)
4944 GdkEvent *new_event;
4945 char *old_text;
4946 const char *new_text;
4947 gboolean retval;
4948 GdkScreen *screen;
4949 gboolean text_modified;
4950 gulong popup_menu_id;
4952 pspp_sheet_view_ensure_interactive_directory (tree_view);
4954 /* Make a copy of the current text */
4955 old_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (tree_view->priv->search_entry)));
4956 new_event = gdk_event_copy ((GdkEvent *) event);
4957 g_object_unref (((GdkEventKey *) new_event)->window);
4958 ((GdkEventKey *) new_event)->window = g_object_ref (gtk_widget_get_window (tree_view->priv->search_window));
4959 gtk_widget_realize (tree_view->priv->search_window);
4961 popup_menu_id = g_signal_connect (tree_view->priv->search_entry,
4962 "popup-menu", G_CALLBACK (gtk_true),
4963 NULL);
4965 /* Move the entry off screen */
4966 screen = gtk_widget_get_screen (GTK_WIDGET (tree_view));
4967 gtk_window_move (GTK_WINDOW (tree_view->priv->search_window),
4968 gdk_screen_get_width (screen) + 1,
4969 gdk_screen_get_height (screen) + 1);
4970 gtk_widget_show (tree_view->priv->search_window);
4972 /* Send the event to the window. If the preedit_changed signal is emitted
4973 * during this event, we will set priv->imcontext_changed */
4974 tree_view->priv->imcontext_changed = FALSE;
4975 retval = gtk_widget_event (tree_view->priv->search_window, new_event);
4976 gdk_event_free (new_event);
4977 gtk_widget_hide (tree_view->priv->search_window);
4979 g_signal_handler_disconnect (tree_view->priv->search_entry,
4980 popup_menu_id);
4982 /* We check to make sure that the entry tried to handle the text, and that
4983 * the text has changed.
4985 new_text = gtk_entry_get_text (GTK_ENTRY (tree_view->priv->search_entry));
4986 text_modified = strcmp (old_text, new_text) != 0;
4987 g_free (old_text);
4988 if (tree_view->priv->imcontext_changed || /* we're in a preedit */
4989 (retval && text_modified)) /* ...or the text was modified */
4991 if (pspp_sheet_view_real_start_interactive_search (tree_view, FALSE))
4993 gtk_widget_grab_focus (GTK_WIDGET (tree_view));
4994 return TRUE;
4996 else
4998 gtk_entry_set_text (GTK_ENTRY (tree_view->priv->search_entry), "");
4999 return FALSE;
5004 return FALSE;
5007 static gboolean
5008 pspp_sheet_view_key_release (GtkWidget *widget,
5009 GdkEventKey *event)
5011 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
5013 if (tree_view->priv->rubber_band_status)
5014 return TRUE;
5016 return GTK_WIDGET_CLASS (pspp_sheet_view_parent_class)->key_release_event (widget, event);
5019 /* FIXME Is this function necessary? Can I get an enter_notify event
5020 * w/o either an expose event or a mouse motion event?
5022 static gboolean
5023 pspp_sheet_view_enter_notify (GtkWidget *widget,
5024 GdkEventCrossing *event)
5026 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
5027 int node;
5028 gint new_y;
5030 /* Sanity check it */
5031 if (event->window != tree_view->priv->bin_window)
5032 return FALSE;
5034 if (tree_view->priv->row_count == 0)
5035 return FALSE;
5037 if (event->mode == GDK_CROSSING_GRAB ||
5038 event->mode == GDK_CROSSING_GTK_GRAB ||
5039 event->mode == GDK_CROSSING_GTK_UNGRAB ||
5040 event->mode == GDK_CROSSING_STATE_CHANGED)
5041 return TRUE;
5043 /* find the node internally */
5044 new_y = TREE_WINDOW_Y_TO_RBTREE_Y(tree_view, event->y);
5045 if (new_y < 0)
5046 new_y = 0;
5047 pspp_sheet_view_find_offset (tree_view, new_y, &node);
5049 tree_view->priv->event_last_x = event->x;
5050 tree_view->priv->event_last_y = event->y;
5052 prelight_or_select (tree_view, node, event->x, event->y);
5054 return TRUE;
5057 static gboolean
5058 pspp_sheet_view_leave_notify (GtkWidget *widget,
5059 GdkEventCrossing *event)
5061 PsppSheetView *tree_view;
5063 if (event->mode == GDK_CROSSING_GRAB)
5064 return TRUE;
5066 tree_view = PSPP_SHEET_VIEW (widget);
5068 if (tree_view->priv->prelight_node >= 0)
5069 _pspp_sheet_view_queue_draw_node (tree_view,
5070 tree_view->priv->prelight_node,
5071 NULL);
5073 tree_view->priv->event_last_x = -10000;
5074 tree_view->priv->event_last_y = -10000;
5076 prelight_or_select (tree_view,
5078 -1000, -1000); /* coords not possibly over an arrow */
5080 return TRUE;
5084 static gint
5085 pspp_sheet_view_focus_out (GtkWidget *widget,
5086 GdkEventFocus *event)
5088 PsppSheetView *tree_view;
5090 tree_view = PSPP_SHEET_VIEW (widget);
5092 gtk_widget_queue_draw (widget);
5094 /* destroy interactive search dialog */
5095 if (tree_view->priv->search_window)
5096 pspp_sheet_view_search_dialog_hide (tree_view->priv->search_window, tree_view);
5098 return FALSE;
5102 /* Incremental Reflow
5105 static void
5106 pspp_sheet_view_node_queue_redraw (PsppSheetView *tree_view,
5107 int node)
5109 GtkAllocation allocation;
5110 gint y = pspp_sheet_view_node_find_offset (tree_view, node)
5111 - gtk_adjustment_get_value (tree_view->priv->vadjustment)
5112 + TREE_VIEW_HEADER_HEIGHT (tree_view);
5114 gtk_widget_get_allocation (GTK_WIDGET (tree_view), &allocation);
5116 gtk_widget_queue_draw_area (GTK_WIDGET (tree_view),
5117 0, y,
5118 allocation.width,
5119 tree_view->priv->fixed_height);
5122 static gboolean
5123 node_is_visible (PsppSheetView *tree_view,
5124 int node)
5126 int y;
5127 int height;
5129 y = pspp_sheet_view_node_find_offset (tree_view, node);
5130 height = ROW_HEIGHT (tree_view);
5132 if (y >= gtk_adjustment_get_value (tree_view->priv->vadjustment) &&
5133 y + height <= (gtk_adjustment_get_value (tree_view->priv->vadjustment)
5134 + gtk_adjustment_get_page_size (tree_view->priv->vadjustment)))
5135 return TRUE;
5137 return FALSE;
5140 /* Returns the row height. */
5141 static gint
5142 validate_row (PsppSheetView *tree_view,
5143 int node,
5144 GtkTreeIter *iter,
5145 GtkTreePath *path)
5147 PsppSheetViewColumn *column;
5148 GList *list, *first_column, *last_column;
5149 gint height = 0;
5150 gint horizontal_separator;
5151 gint vertical_separator;
5152 gint focus_line_width;
5153 gboolean draw_vgrid_lines, draw_hgrid_lines;
5154 gint focus_pad;
5155 gint grid_line_width;
5156 gboolean wide_separators;
5157 gint separator_height;
5159 gtk_widget_style_get (GTK_WIDGET (tree_view),
5160 "focus-padding", &focus_pad,
5161 "focus-line-width", &focus_line_width,
5162 "horizontal-separator", &horizontal_separator,
5163 "vertical-separator", &vertical_separator,
5164 "grid-line-width", &grid_line_width,
5165 "wide-separators", &wide_separators,
5166 "separator-height", &separator_height,
5167 NULL);
5169 draw_vgrid_lines =
5170 tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_VERTICAL
5171 || tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_BOTH;
5172 draw_hgrid_lines =
5173 tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_HORIZONTAL
5174 || tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_BOTH;
5176 for (last_column = g_list_last (tree_view->priv->columns);
5177 last_column && !(PSPP_SHEET_VIEW_COLUMN (last_column->data)->visible);
5178 last_column = last_column->prev)
5181 for (first_column = g_list_first (tree_view->priv->columns);
5182 first_column && !(PSPP_SHEET_VIEW_COLUMN (first_column->data)->visible);
5183 first_column = first_column->next)
5186 for (list = tree_view->priv->columns; list; list = list->next)
5188 gint tmp_width;
5189 gint tmp_height;
5191 column = list->data;
5193 if (! column->visible)
5194 continue;
5196 pspp_sheet_view_column_cell_set_cell_data (column, tree_view->priv->model, iter);
5197 pspp_sheet_view_column_cell_get_size (column,
5198 NULL, NULL, NULL,
5199 &tmp_width, &tmp_height);
5201 tmp_height += vertical_separator;
5202 height = MAX (height, tmp_height);
5204 tmp_width = tmp_width + horizontal_separator;
5206 if (draw_vgrid_lines)
5208 if (list->data == first_column || list->data == last_column)
5209 tmp_width += grid_line_width / 2.0;
5210 else
5211 tmp_width += grid_line_width;
5214 if (tmp_width > column->requested_width)
5215 column->requested_width = tmp_width;
5218 if (draw_hgrid_lines)
5219 height += grid_line_width;
5221 tree_view->priv->post_validation_flag = TRUE;
5222 return height;
5226 static void
5227 validate_visible_area (PsppSheetView *tree_view)
5229 GtkTreePath *path = NULL;
5230 GtkTreePath *above_path = NULL;
5231 GtkTreeIter iter;
5232 int node = -1;
5233 gboolean size_changed = FALSE;
5234 gint total_height;
5235 gint area_above = 0;
5236 gint area_below = 0;
5237 GtkAllocation allocation;
5239 if (tree_view->priv->row_count == 0)
5240 return;
5242 if (tree_view->priv->scroll_to_path == NULL)
5243 return;
5245 gtk_widget_get_allocation (GTK_WIDGET (tree_view), &allocation);
5247 total_height = allocation.height - TREE_VIEW_HEADER_HEIGHT (tree_view);
5249 if (total_height == 0)
5250 return;
5252 path = gtk_tree_row_reference_get_path (tree_view->priv->scroll_to_path);
5253 if (path)
5255 /* we are going to scroll, and will update dy */
5256 _pspp_sheet_view_find_node (tree_view, path, &node);
5257 gtk_tree_model_get_iter (tree_view->priv->model, &iter, path);
5259 if (tree_view->priv->scroll_to_use_align)
5261 gint height = ROW_HEIGHT (tree_view);
5262 area_above = (total_height - height) *
5263 tree_view->priv->scroll_to_row_align;
5264 area_below = total_height - area_above - height;
5265 area_above = MAX (area_above, 0);
5266 area_below = MAX (area_below, 0);
5268 else
5270 /* two cases:
5271 * 1) row not visible
5272 * 2) row visible
5274 gint dy;
5275 gint height = ROW_HEIGHT (tree_view);
5277 dy = pspp_sheet_view_node_find_offset (tree_view, node);
5279 if (dy >= gtk_adjustment_get_value (tree_view->priv->vadjustment) &&
5280 dy + height <= (gtk_adjustment_get_value (tree_view->priv->vadjustment)
5281 + gtk_adjustment_get_page_size (tree_view->priv->vadjustment)))
5283 /* row visible: keep the row at the same position */
5284 area_above = dy - gtk_adjustment_get_value (tree_view->priv->vadjustment);
5285 area_below = (gtk_adjustment_get_value (tree_view->priv->vadjustment) +
5286 gtk_adjustment_get_page_size (tree_view->priv->vadjustment))
5287 - dy - height;
5289 else
5291 /* row not visible */
5292 if (dy >= 0
5293 && dy + height <= gtk_adjustment_get_page_size (tree_view->priv->vadjustment))
5295 /* row at the beginning -- fixed */
5296 area_above = dy;
5297 area_below = gtk_adjustment_get_page_size (tree_view->priv->vadjustment)
5298 - area_above - height;
5300 else if (dy >= (gtk_adjustment_get_upper (tree_view->priv->vadjustment) -
5301 gtk_adjustment_get_page_size (tree_view->priv->vadjustment)))
5303 /* row at the end -- fixed */
5304 area_above = dy - (gtk_adjustment_get_upper (tree_view->priv->vadjustment) -
5305 gtk_adjustment_get_page_size (tree_view->priv->vadjustment));
5306 area_below = gtk_adjustment_get_page_size (tree_view->priv->vadjustment) -
5307 area_above - height;
5309 if (area_below < 0)
5311 area_above = gtk_adjustment_get_page_size (tree_view->priv->vadjustment) - height;
5312 area_below = 0;
5315 else
5317 /* row somewhere in the middle, bring it to the top
5318 * of the view
5320 area_above = 0;
5321 area_below = total_height - height;
5326 else
5327 /* the scroll to isn't valid; ignore it.
5330 gtk_tree_row_reference_free (tree_view->priv->scroll_to_path);
5331 tree_view->priv->scroll_to_path = NULL;
5332 return;
5335 above_path = gtk_tree_path_copy (path);
5337 /* Now, we walk forwards and backwards, measuring rows. Unfortunately,
5338 * backwards is much slower then forward, as there is no iter_prev function.
5339 * We go forwards first in case we run out of tree. Then we go backwards to
5340 * fill out the top.
5342 while (node >= 0 && area_below > 0)
5344 gboolean done = FALSE;
5347 node = pspp_sheet_view_node_next (tree_view, node);
5348 if (node >= 0)
5350 gboolean has_next = gtk_tree_model_iter_next (tree_view->priv->model, &iter);
5351 done = TRUE;
5352 gtk_tree_path_next (path);
5354 /* Sanity Check! */
5355 TREE_VIEW_INTERNAL_ASSERT_VOID (has_next);
5357 else
5358 break;
5360 while (!done);
5362 if (node < 0)
5363 break;
5365 area_below -= ROW_HEIGHT (tree_view);
5367 gtk_tree_path_free (path);
5369 /* If we ran out of tree, and have extra area_below left, we need to add it
5370 * to area_above */
5371 if (area_below > 0)
5372 area_above += area_below;
5374 _pspp_sheet_view_find_node (tree_view, above_path, &node);
5376 /* We walk backwards */
5377 while (area_above > 0)
5379 node = pspp_sheet_view_node_prev (tree_view, node);
5381 /* Always find the new path in the tree. We cannot just assume
5382 * a gtk_tree_path_prev() is enough here, as there might be children
5383 * in between this node and the previous sibling node. If this
5384 * appears to be a performance hotspot in profiles, we can look into
5385 * intrigate logic for keeping path, node and iter in sync like
5386 * we do for forward walks. (Which will be hard because of the lacking
5387 * iter_prev).
5390 if (node < 0)
5391 break;
5393 gtk_tree_path_free (above_path);
5394 above_path = _pspp_sheet_view_find_path (tree_view, node);
5396 gtk_tree_model_get_iter (tree_view->priv->model, &iter, above_path);
5398 area_above -= ROW_HEIGHT (tree_view);
5401 /* set the dy here to scroll to the path,
5402 * and sync the top row accordingly
5404 pspp_sheet_view_set_top_row (tree_view, above_path, -area_above);
5405 pspp_sheet_view_top_row_to_dy (tree_view);
5407 /* update width/height and queue a resize */
5408 if (size_changed)
5410 GtkRequisition requisition;
5412 /* We temporarily guess a size, under the assumption that it will be the
5413 * same when we get our next size_allocate. If we don't do this, we'll be
5414 * in an inconsistent state if we call top_row_to_dy. */
5416 gtk_widget_size_request (GTK_WIDGET (tree_view), &requisition);
5417 gtk_adjustment_set_upper (tree_view->priv->hadjustment, MAX (gtk_adjustment_get_upper (tree_view->priv->hadjustment), (gfloat)requisition.width));
5418 gtk_adjustment_set_upper (tree_view->priv->vadjustment, MAX (gtk_adjustment_get_upper (tree_view->priv->vadjustment), (gfloat)requisition.height));
5419 gtk_adjustment_changed (tree_view->priv->hadjustment);
5420 gtk_adjustment_changed (tree_view->priv->vadjustment);
5421 gtk_widget_queue_resize (GTK_WIDGET (tree_view));
5424 gtk_tree_row_reference_free (tree_view->priv->scroll_to_path);
5425 tree_view->priv->scroll_to_path = NULL;
5427 if (above_path)
5428 gtk_tree_path_free (above_path);
5430 if (tree_view->priv->scroll_to_column)
5432 tree_view->priv->scroll_to_column = NULL;
5434 gtk_widget_queue_draw (GTK_WIDGET (tree_view));
5437 static void
5438 initialize_fixed_height_mode (PsppSheetView *tree_view)
5440 if (!tree_view->priv->row_count)
5441 return;
5443 if (tree_view->priv->fixed_height_set)
5444 return;
5446 if (tree_view->priv->fixed_height < 0)
5448 GtkTreeIter iter;
5449 GtkTreePath *path;
5451 int node = 0;
5453 path = _pspp_sheet_view_find_path (tree_view, node);
5454 gtk_tree_model_get_iter (tree_view->priv->model, &iter, path);
5456 tree_view->priv->fixed_height = validate_row (tree_view, node, &iter, path);
5458 gtk_tree_path_free (path);
5460 g_object_notify (G_OBJECT (tree_view), "fixed-height");
5464 /* Our strategy for finding nodes to validate is a little convoluted. We find
5465 * the left-most uninvalidated node. We then try walking right, validating
5466 * nodes. Once we find a valid node, we repeat the previous process of finding
5467 * the first invalid node.
5470 static gboolean
5471 validate_rows_handler (PsppSheetView *tree_view)
5473 initialize_fixed_height_mode (tree_view);
5474 if (tree_view->priv->validate_rows_timer)
5476 g_source_remove (tree_view->priv->validate_rows_timer);
5477 tree_view->priv->validate_rows_timer = 0;
5480 return FALSE;
5483 static gboolean
5484 do_presize_handler (PsppSheetView *tree_view)
5486 GtkRequisition requisition;
5488 validate_visible_area (tree_view);
5489 tree_view->priv->presize_handler_timer = 0;
5491 if (! gtk_widget_get_realized (GTK_WIDGET (tree_view)))
5492 return FALSE;
5494 gtk_widget_size_request (GTK_WIDGET (tree_view), &requisition);
5496 gtk_adjustment_set_upper (tree_view->priv->hadjustment, MAX (gtk_adjustment_get_upper (tree_view->priv->hadjustment), (gfloat)requisition.width));
5497 gtk_adjustment_set_upper (tree_view->priv->vadjustment, MAX (gtk_adjustment_get_upper (tree_view->priv->vadjustment), (gfloat)requisition.height));
5498 gtk_adjustment_changed (tree_view->priv->hadjustment);
5499 gtk_adjustment_changed (tree_view->priv->vadjustment);
5500 gtk_widget_queue_resize (GTK_WIDGET (tree_view));
5502 return FALSE;
5505 static gboolean
5506 presize_handler_callback (gpointer data)
5508 do_presize_handler (PSPP_SHEET_VIEW (data));
5510 return FALSE;
5513 static void
5514 install_presize_handler (PsppSheetView *tree_view)
5516 if (! gtk_widget_get_realized (GTK_WIDGET (tree_view)))
5517 return;
5519 if (! tree_view->priv->presize_handler_timer)
5521 tree_view->priv->presize_handler_timer =
5522 gdk_threads_add_idle_full (GTK_PRIORITY_RESIZE - 2, presize_handler_callback, tree_view, NULL);
5524 if (! tree_view->priv->validate_rows_timer)
5526 tree_view->priv->validate_rows_timer =
5527 gdk_threads_add_idle_full (PSPP_SHEET_VIEW_PRIORITY_VALIDATE, (GSourceFunc) validate_rows_handler, tree_view, NULL);
5531 static gboolean
5532 scroll_sync_handler (PsppSheetView *tree_view)
5534 if (tree_view->priv->height <= gtk_adjustment_get_page_size (tree_view->priv->vadjustment))
5535 gtk_adjustment_set_value (GTK_ADJUSTMENT (tree_view->priv->vadjustment), 0);
5536 else if (gtk_tree_row_reference_valid (tree_view->priv->top_row))
5537 pspp_sheet_view_top_row_to_dy (tree_view);
5538 else
5539 pspp_sheet_view_dy_to_top_row (tree_view);
5541 tree_view->priv->scroll_sync_timer = 0;
5543 return FALSE;
5546 static void
5547 install_scroll_sync_handler (PsppSheetView *tree_view)
5549 if (!gtk_widget_get_realized (GTK_WIDGET (tree_view)))
5550 return;
5552 if (!tree_view->priv->scroll_sync_timer)
5554 tree_view->priv->scroll_sync_timer =
5555 gdk_threads_add_idle_full (PSPP_SHEET_VIEW_PRIORITY_SCROLL_SYNC, (GSourceFunc) scroll_sync_handler, tree_view, NULL);
5559 static void
5560 pspp_sheet_view_set_top_row (PsppSheetView *tree_view,
5561 GtkTreePath *path,
5562 gint offset)
5564 gtk_tree_row_reference_free (tree_view->priv->top_row);
5566 if (!path)
5568 tree_view->priv->top_row = NULL;
5569 tree_view->priv->top_row_dy = 0;
5571 else
5573 tree_view->priv->top_row = gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view), tree_view->priv->model, path);
5574 tree_view->priv->top_row_dy = offset;
5578 /* Always call this iff dy is in the visible range. If the tree is empty, then
5579 * it's set to be NULL, and top_row_dy is 0;
5581 static void
5582 pspp_sheet_view_dy_to_top_row (PsppSheetView *tree_view)
5584 gint offset;
5585 GtkTreePath *path;
5586 int node;
5588 if (tree_view->priv->row_count == 0)
5590 pspp_sheet_view_set_top_row (tree_view, NULL, 0);
5592 else
5594 offset = pspp_sheet_view_find_offset (tree_view,
5595 tree_view->priv->dy,
5596 &node);
5598 if (node < 0)
5600 pspp_sheet_view_set_top_row (tree_view, NULL, 0);
5602 else
5604 path = _pspp_sheet_view_find_path (tree_view, node);
5605 pspp_sheet_view_set_top_row (tree_view, path, offset);
5606 gtk_tree_path_free (path);
5611 static void
5612 pspp_sheet_view_top_row_to_dy (PsppSheetView *tree_view)
5614 GtkTreePath *path;
5615 int node;
5616 int new_dy;
5618 /* Avoid recursive calls */
5619 if (tree_view->priv->in_top_row_to_dy)
5620 return;
5622 if (tree_view->priv->top_row)
5623 path = gtk_tree_row_reference_get_path (tree_view->priv->top_row);
5624 else
5625 path = NULL;
5627 if (!path)
5628 node = -1;
5629 else
5630 _pspp_sheet_view_find_node (tree_view, path, &node);
5632 if (path)
5633 gtk_tree_path_free (path);
5635 if (node < 0)
5637 /* keep dy and set new toprow */
5638 gtk_tree_row_reference_free (tree_view->priv->top_row);
5639 tree_view->priv->top_row = NULL;
5640 tree_view->priv->top_row_dy = 0;
5641 /* DO NOT install the idle handler */
5642 pspp_sheet_view_dy_to_top_row (tree_view);
5643 return;
5646 if (ROW_HEIGHT (tree_view) < tree_view->priv->top_row_dy)
5648 /* new top row -- do NOT install the idle handler */
5649 pspp_sheet_view_dy_to_top_row (tree_view);
5650 return;
5653 new_dy = pspp_sheet_view_node_find_offset (tree_view, node);
5654 new_dy += tree_view->priv->top_row_dy;
5656 if (new_dy + gtk_adjustment_get_page_size (tree_view->priv->vadjustment) > tree_view->priv->height)
5657 new_dy = tree_view->priv->height - gtk_adjustment_get_page_size (tree_view->priv->vadjustment);
5659 new_dy = MAX (0, new_dy);
5661 tree_view->priv->in_top_row_to_dy = TRUE;
5662 gtk_adjustment_set_value (tree_view->priv->vadjustment, (gdouble)new_dy);
5663 tree_view->priv->in_top_row_to_dy = FALSE;
5667 void
5668 _pspp_sheet_view_install_mark_rows_col_dirty (PsppSheetView *tree_view)
5670 install_presize_handler (tree_view);
5673 /* Drag-and-drop */
5675 static void
5676 set_source_row (GdkDragContext *context,
5677 GtkTreeModel *model,
5678 GtkTreePath *source_row)
5680 g_object_set_data_full (G_OBJECT (context),
5681 "gtk-tree-view-source-row",
5682 source_row ? gtk_tree_row_reference_new (model, source_row) : NULL,
5683 (GDestroyNotify) (source_row ? gtk_tree_row_reference_free : NULL));
5686 static GtkTreePath*
5687 get_source_row (GdkDragContext *context)
5689 GtkTreeRowReference *ref =
5690 g_object_get_data (G_OBJECT (context), "gtk-tree-view-source-row");
5692 if (ref)
5693 return gtk_tree_row_reference_get_path (ref);
5694 else
5695 return NULL;
5698 typedef struct
5700 GtkTreeRowReference *dest_row;
5701 guint path_down_mode : 1;
5702 guint empty_view_drop : 1;
5703 guint drop_append_mode : 1;
5705 DestRow;
5707 static void
5708 dest_row_free (gpointer data)
5710 DestRow *dr = (DestRow *)data;
5712 gtk_tree_row_reference_free (dr->dest_row);
5713 g_slice_free (DestRow, dr);
5716 static void
5717 set_dest_row (GdkDragContext *context,
5718 GtkTreeModel *model,
5719 GtkTreePath *dest_row,
5720 gboolean path_down_mode,
5721 gboolean empty_view_drop,
5722 gboolean drop_append_mode)
5724 DestRow *dr;
5726 if (!dest_row)
5728 g_object_set_data_full (G_OBJECT (context), "gtk-tree-view-dest-row",
5729 NULL, NULL);
5730 return;
5733 dr = g_slice_new (DestRow);
5735 dr->dest_row = gtk_tree_row_reference_new (model, dest_row);
5736 dr->path_down_mode = path_down_mode != FALSE;
5737 dr->empty_view_drop = empty_view_drop != FALSE;
5738 dr->drop_append_mode = drop_append_mode != FALSE;
5740 g_object_set_data_full (G_OBJECT (context), "gtk-tree-view-dest-row",
5741 dr, (GDestroyNotify) dest_row_free);
5744 static GtkTreePath*
5745 get_dest_row (GdkDragContext *context,
5746 gboolean *path_down_mode)
5748 DestRow *dr =
5749 g_object_get_data (G_OBJECT (context), "gtk-tree-view-dest-row");
5751 if (dr)
5753 GtkTreePath *path = NULL;
5755 if (path_down_mode)
5756 *path_down_mode = dr->path_down_mode;
5758 if (dr->dest_row)
5759 path = gtk_tree_row_reference_get_path (dr->dest_row);
5760 else if (dr->empty_view_drop)
5761 path = gtk_tree_path_new_from_indices (0, -1);
5762 else
5763 path = NULL;
5765 if (path && dr->drop_append_mode)
5766 gtk_tree_path_next (path);
5768 return path;
5770 else
5771 return NULL;
5774 /* Get/set whether drag_motion requested the drag data and
5775 * drag_data_received should thus not actually insert the data,
5776 * since the data doesn't result from a drop.
5778 static void
5779 set_status_pending (GdkDragContext *context,
5780 GdkDragAction suggested_action)
5782 g_object_set_data (G_OBJECT (context),
5783 "gtk-tree-view-status-pending",
5784 GINT_TO_POINTER (suggested_action));
5787 static GdkDragAction
5788 get_status_pending (GdkDragContext *context)
5790 return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (context),
5791 "gtk-tree-view-status-pending"));
5794 static TreeViewDragInfo*
5795 get_info (PsppSheetView *tree_view)
5797 return g_object_get_data (G_OBJECT (tree_view), "gtk-tree-view-drag-info");
5800 static void
5801 destroy_info (TreeViewDragInfo *di)
5803 g_slice_free (TreeViewDragInfo, di);
5806 static TreeViewDragInfo*
5807 ensure_info (PsppSheetView *tree_view)
5809 TreeViewDragInfo *di;
5811 di = get_info (tree_view);
5813 if (di == NULL)
5815 di = g_slice_new0 (TreeViewDragInfo);
5817 g_object_set_data_full (G_OBJECT (tree_view),
5818 "gtk-tree-view-drag-info",
5820 (GDestroyNotify) destroy_info);
5823 return di;
5826 static void
5827 remove_info (PsppSheetView *tree_view)
5829 g_object_set_data (G_OBJECT (tree_view), "gtk-tree-view-drag-info", NULL);
5832 #if 0
5833 static gint
5834 drag_scan_timeout (gpointer data)
5836 PsppSheetView *tree_view;
5837 gint x, y;
5838 GdkModifierType state;
5839 GtkTreePath *path = NULL;
5840 PsppSheetViewColumn *column = NULL;
5841 GdkRectangle visible_rect;
5843 GDK_THREADS_ENTER ();
5845 tree_view = PSPP_SHEET_VIEW (data);
5847 gdk_window_get_pointer (tree_view->priv->bin_window,
5848 &x, &y, &state);
5850 pspp_sheet_view_get_visible_rect (tree_view, &visible_rect);
5852 /* See if we are near the edge. */
5853 if ((x - visible_rect.x) < SCROLL_EDGE_SIZE ||
5854 (visible_rect.x + visible_rect.width - x) < SCROLL_EDGE_SIZE ||
5855 (y - visible_rect.y) < SCROLL_EDGE_SIZE ||
5856 (visible_rect.y + visible_rect.height - y) < SCROLL_EDGE_SIZE)
5858 pspp_sheet_view_get_path_at_pos (tree_view,
5859 tree_view->priv->bin_window,
5860 x, y,
5861 &path,
5862 &column,
5863 NULL,
5864 NULL);
5866 if (path != NULL)
5868 pspp_sheet_view_scroll_to_cell (tree_view,
5869 path,
5870 column,
5871 TRUE,
5872 0.5, 0.5);
5874 gtk_tree_path_free (path);
5878 GDK_THREADS_LEAVE ();
5880 return TRUE;
5882 #endif /* 0 */
5884 static void
5885 add_scroll_timeout (PsppSheetView *tree_view)
5887 if (tree_view->priv->scroll_timeout == 0)
5889 tree_view->priv->scroll_timeout =
5890 gdk_threads_add_timeout (150, scroll_row_timeout, tree_view);
5894 static void
5895 remove_scroll_timeout (PsppSheetView *tree_view)
5897 if (tree_view->priv->scroll_timeout != 0)
5899 g_source_remove (tree_view->priv->scroll_timeout);
5900 tree_view->priv->scroll_timeout = 0;
5904 static gboolean
5905 check_model_dnd (GtkTreeModel *model,
5906 GType required_iface,
5907 const gchar *signal)
5909 if (model == NULL || !G_TYPE_CHECK_INSTANCE_TYPE ((model), required_iface))
5911 g_warning ("You must override the default '%s' handler "
5912 "on PsppSheetView when using models that don't support "
5913 "the %s interface and enabling drag-and-drop. The simplest way to do this "
5914 "is to connect to '%s' and call "
5915 "g_signal_stop_emission_by_name() in your signal handler to prevent "
5916 "the default handler from running. Look at the source code "
5917 "for the default handler in gtktreeview.c to get an idea what "
5918 "your handler should do. (gtktreeview.c is in the GTK source "
5919 "code.) If you're using GTK from a language other than C, "
5920 "there may be a more natural way to override default handlers, e.g. via derivation.",
5921 signal, g_type_name (required_iface), signal);
5922 return FALSE;
5924 else
5925 return TRUE;
5928 static gboolean
5929 scroll_row_timeout (gpointer data)
5931 PsppSheetView *tree_view = data;
5933 pspp_sheet_view_horizontal_autoscroll (tree_view);
5934 pspp_sheet_view_vertical_autoscroll (tree_view);
5936 if (tree_view->priv->rubber_band_status == RUBBER_BAND_ACTIVE)
5937 pspp_sheet_view_update_rubber_band (tree_view);
5939 return TRUE;
5942 /* Returns TRUE if event should not be propagated to parent widgets */
5943 static gboolean
5944 set_destination_row (PsppSheetView *tree_view,
5945 GdkDragContext *context,
5946 /* coordinates relative to the widget */
5947 gint x,
5948 gint y,
5949 GdkDragAction *suggested_action,
5950 GdkAtom *target)
5952 GtkTreePath *path = NULL;
5953 PsppSheetViewDropPosition pos;
5954 PsppSheetViewDropPosition old_pos;
5955 TreeViewDragInfo *di;
5956 GtkWidget *widget;
5957 GtkTreePath *old_dest_path = NULL;
5958 gboolean can_drop = FALSE;
5960 *suggested_action = 0;
5961 *target = GDK_NONE;
5963 widget = GTK_WIDGET (tree_view);
5965 di = get_info (tree_view);
5967 if (di == NULL || y - TREE_VIEW_HEADER_HEIGHT (tree_view) < 0)
5969 /* someone unset us as a drag dest, note that if
5970 * we return FALSE drag_leave isn't called
5973 pspp_sheet_view_set_drag_dest_row (tree_view,
5974 NULL,
5975 PSPP_SHEET_VIEW_DROP_BEFORE);
5977 remove_scroll_timeout (PSPP_SHEET_VIEW (widget));
5979 return FALSE; /* no longer a drop site */
5982 *target = gtk_drag_dest_find_target (widget, context,
5983 gtk_drag_dest_get_target_list (widget));
5984 if (*target == GDK_NONE)
5986 return FALSE;
5989 if (!pspp_sheet_view_get_dest_row_at_pos (tree_view,
5990 x, y,
5991 &path,
5992 &pos))
5994 gint n_children;
5995 GtkTreeModel *model;
5997 /* the row got dropped on empty space, let's setup a special case
6000 if (path)
6001 gtk_tree_path_free (path);
6003 model = pspp_sheet_view_get_model (tree_view);
6005 n_children = gtk_tree_model_iter_n_children (model, NULL);
6006 if (n_children)
6008 pos = PSPP_SHEET_VIEW_DROP_AFTER;
6009 path = gtk_tree_path_new_from_indices (n_children - 1, -1);
6011 else
6013 pos = PSPP_SHEET_VIEW_DROP_BEFORE;
6014 path = gtk_tree_path_new_from_indices (0, -1);
6017 can_drop = TRUE;
6019 goto out;
6022 g_assert (path);
6024 /* If we left the current row's "open" zone, unset the timeout for
6025 * opening the row
6027 pspp_sheet_view_get_drag_dest_row (tree_view,
6028 &old_dest_path,
6029 &old_pos);
6031 if (old_dest_path)
6032 gtk_tree_path_free (old_dest_path);
6034 if (TRUE /* FIXME if the location droppable predicate */)
6036 can_drop = TRUE;
6039 out:
6040 if (can_drop)
6042 GtkWidget *source_widget;
6044 *suggested_action = gdk_drag_context_get_suggested_action (context);
6045 source_widget = gtk_drag_get_source_widget (context);
6047 if (source_widget == widget)
6049 /* Default to MOVE, unless the user has
6050 * pressed ctrl or shift to affect available actions
6052 if ((gdk_drag_context_get_actions (context) & GDK_ACTION_MOVE) != 0)
6053 *suggested_action = GDK_ACTION_MOVE;
6056 pspp_sheet_view_set_drag_dest_row (PSPP_SHEET_VIEW (widget),
6057 path, pos);
6059 else
6061 /* can't drop here */
6062 pspp_sheet_view_set_drag_dest_row (PSPP_SHEET_VIEW (widget),
6063 NULL,
6064 PSPP_SHEET_VIEW_DROP_BEFORE);
6067 if (path)
6068 gtk_tree_path_free (path);
6070 return TRUE;
6073 static GtkTreePath*
6074 get_logical_dest_row (PsppSheetView *tree_view,
6075 gboolean *path_down_mode,
6076 gboolean *drop_append_mode)
6078 /* adjust path to point to the row the drop goes in front of */
6079 GtkTreePath *path = NULL;
6080 PsppSheetViewDropPosition pos;
6082 g_return_val_if_fail (path_down_mode != NULL, NULL);
6083 g_return_val_if_fail (drop_append_mode != NULL, NULL);
6085 *path_down_mode = FALSE;
6086 *drop_append_mode = 0;
6088 pspp_sheet_view_get_drag_dest_row (tree_view, &path, &pos);
6090 if (path == NULL)
6091 return NULL;
6093 if (pos == PSPP_SHEET_VIEW_DROP_BEFORE)
6094 ; /* do nothing */
6095 else if (pos == PSPP_SHEET_VIEW_DROP_INTO_OR_BEFORE ||
6096 pos == PSPP_SHEET_VIEW_DROP_INTO_OR_AFTER)
6097 *path_down_mode = TRUE;
6098 else
6100 GtkTreeIter iter;
6101 GtkTreeModel *model = pspp_sheet_view_get_model (tree_view);
6103 g_assert (pos == PSPP_SHEET_VIEW_DROP_AFTER);
6105 if (!gtk_tree_model_get_iter (model, &iter, path) ||
6106 !gtk_tree_model_iter_next (model, &iter))
6107 *drop_append_mode = 1;
6108 else
6110 *drop_append_mode = 0;
6111 gtk_tree_path_next (path);
6115 return path;
6118 static gboolean
6119 pspp_sheet_view_maybe_begin_dragging_row (PsppSheetView *tree_view,
6120 GdkEventMotion *event)
6122 GtkWidget *widget = GTK_WIDGET (tree_view);
6123 GdkDragContext *context;
6124 TreeViewDragInfo *di;
6125 GtkTreePath *path = NULL;
6126 gint button;
6127 gint cell_x, cell_y;
6128 GtkTreeModel *model;
6129 gboolean retval = FALSE;
6131 di = get_info (tree_view);
6133 if (di == NULL || !di->source_set)
6134 goto out;
6136 if (tree_view->priv->pressed_button < 0)
6137 goto out;
6139 if (!gtk_drag_check_threshold (widget,
6140 tree_view->priv->press_start_x,
6141 tree_view->priv->press_start_y,
6142 event->x, event->y))
6143 goto out;
6145 model = pspp_sheet_view_get_model (tree_view);
6147 if (model == NULL)
6148 goto out;
6150 button = tree_view->priv->pressed_button;
6151 tree_view->priv->pressed_button = -1;
6153 pspp_sheet_view_get_path_at_pos (tree_view,
6154 tree_view->priv->press_start_x,
6155 tree_view->priv->press_start_y,
6156 &path,
6157 NULL,
6158 &cell_x,
6159 &cell_y);
6161 if (path == NULL)
6162 goto out;
6164 if (!GTK_IS_TREE_DRAG_SOURCE (model) ||
6165 !gtk_tree_drag_source_row_draggable (GTK_TREE_DRAG_SOURCE (model),
6166 path))
6167 goto out;
6169 if (!(GDK_BUTTON1_MASK << (button - 1) & di->start_button_mask))
6170 goto out;
6172 /* Now we can begin the drag */
6174 retval = TRUE;
6176 context = gtk_drag_begin (widget,
6177 gtk_drag_source_get_target_list (widget),
6178 di->source_actions,
6179 button,
6180 (GdkEvent*)event);
6182 set_source_row (context, model, path);
6184 out:
6185 if (path)
6186 gtk_tree_path_free (path);
6188 return retval;
6193 static void
6194 pspp_sheet_view_drag_begin (GtkWidget *widget,
6195 GdkDragContext *context)
6197 #if GTK3_TRANSITION
6198 PsppSheetView *tree_view;
6199 GtkTreePath *path = NULL;
6200 gint cell_x, cell_y;
6201 GdkPixmap *row_pix;
6202 TreeViewDragInfo *di;
6204 tree_view = PSPP_SHEET_VIEW (widget);
6206 /* if the user uses a custom DND source impl, we don't set the icon here */
6207 di = get_info (tree_view);
6209 if (di == NULL || !di->source_set)
6210 return;
6212 pspp_sheet_view_get_path_at_pos (tree_view,
6213 tree_view->priv->press_start_x,
6214 tree_view->priv->press_start_y,
6215 &path,
6216 NULL,
6217 &cell_x,
6218 &cell_y);
6220 g_return_if_fail (path != NULL);
6222 row_pix = pspp_sheet_view_create_row_drag_icon (tree_view,
6223 path);
6225 gtk_drag_set_icon_pixmap (context,
6226 gdk_drawable_get_colormap (row_pix),
6227 row_pix,
6228 NULL,
6229 /* the + 1 is for the black border in the icon */
6230 tree_view->priv->press_start_x + 1,
6231 cell_y + 1);
6233 g_object_unref (row_pix);
6234 gtk_tree_path_free (path);
6235 #endif
6239 static void
6240 pspp_sheet_view_drag_end (GtkWidget *widget,
6241 GdkDragContext *context)
6243 /* do nothing */
6246 /* Default signal implementations for the drag signals */
6247 static void
6248 pspp_sheet_view_drag_data_get (GtkWidget *widget,
6249 GdkDragContext *context,
6250 GtkSelectionData *selection_data,
6251 guint info,
6252 guint time)
6254 PsppSheetView *tree_view;
6255 GtkTreeModel *model;
6256 TreeViewDragInfo *di;
6257 GtkTreePath *source_row;
6259 tree_view = PSPP_SHEET_VIEW (widget);
6261 model = pspp_sheet_view_get_model (tree_view);
6263 if (model == NULL)
6264 return;
6266 di = get_info (PSPP_SHEET_VIEW (widget));
6268 if (di == NULL)
6269 return;
6271 source_row = get_source_row (context);
6273 if (source_row == NULL)
6274 return;
6276 /* We can implement the GTK_TREE_MODEL_ROW target generically for
6277 * any model; for DragSource models there are some other targets
6278 * we also support.
6281 if (GTK_IS_TREE_DRAG_SOURCE (model) &&
6282 gtk_tree_drag_source_drag_data_get (GTK_TREE_DRAG_SOURCE (model),
6283 source_row,
6284 selection_data))
6285 goto done;
6287 /* If drag_data_get does nothing, try providing row data. */
6288 if (gtk_selection_data_get_target (selection_data) == gdk_atom_intern_static_string ("GTK_TREE_MODEL_ROW"))
6290 gtk_tree_set_row_drag_data (selection_data,
6291 model,
6292 source_row);
6295 done:
6296 gtk_tree_path_free (source_row);
6300 static void
6301 pspp_sheet_view_drag_data_delete (GtkWidget *widget,
6302 GdkDragContext *context)
6304 TreeViewDragInfo *di;
6305 GtkTreeModel *model;
6306 PsppSheetView *tree_view;
6307 GtkTreePath *source_row;
6309 tree_view = PSPP_SHEET_VIEW (widget);
6310 model = pspp_sheet_view_get_model (tree_view);
6312 if (!check_model_dnd (model, GTK_TYPE_TREE_DRAG_SOURCE, "drag_data_delete"))
6313 return;
6315 di = get_info (tree_view);
6317 if (di == NULL)
6318 return;
6320 source_row = get_source_row (context);
6322 if (source_row == NULL)
6323 return;
6325 gtk_tree_drag_source_drag_data_delete (GTK_TREE_DRAG_SOURCE (model),
6326 source_row);
6328 gtk_tree_path_free (source_row);
6330 set_source_row (context, NULL, NULL);
6333 static void
6334 pspp_sheet_view_drag_leave (GtkWidget *widget,
6335 GdkDragContext *context,
6336 guint time)
6338 /* unset any highlight row */
6339 pspp_sheet_view_set_drag_dest_row (PSPP_SHEET_VIEW (widget),
6340 NULL,
6341 PSPP_SHEET_VIEW_DROP_BEFORE);
6343 remove_scroll_timeout (PSPP_SHEET_VIEW (widget));
6347 static gboolean
6348 pspp_sheet_view_drag_motion (GtkWidget *widget,
6349 GdkDragContext *context,
6350 /* coordinates relative to the widget */
6351 gint x,
6352 gint y,
6353 guint time)
6355 gboolean empty;
6356 GtkTreePath *path = NULL;
6357 PsppSheetViewDropPosition pos;
6358 PsppSheetView *tree_view;
6359 GdkDragAction suggested_action = 0;
6360 GdkAtom target;
6362 tree_view = PSPP_SHEET_VIEW (widget);
6364 if (!set_destination_row (tree_view, context, x, y, &suggested_action, &target))
6365 return FALSE;
6367 pspp_sheet_view_get_drag_dest_row (tree_view, &path, &pos);
6369 /* we only know this *after* set_desination_row */
6370 empty = tree_view->priv->empty_view_drop;
6372 if (path == NULL && !empty)
6374 /* Can't drop here. */
6375 gdk_drag_status (context, 0, time);
6377 else
6379 if (tree_view->priv->open_dest_timeout == 0 &&
6380 (pos == PSPP_SHEET_VIEW_DROP_INTO_OR_AFTER ||
6381 pos == PSPP_SHEET_VIEW_DROP_INTO_OR_BEFORE))
6383 /* Nothing. */
6385 else
6387 add_scroll_timeout (tree_view);
6390 if (target == gdk_atom_intern_static_string ("GTK_TREE_MODEL_ROW"))
6392 /* Request data so we can use the source row when
6393 * determining whether to accept the drop
6395 set_status_pending (context, suggested_action);
6396 gtk_drag_get_data (widget, context, target, time);
6398 else
6400 set_status_pending (context, 0);
6401 gdk_drag_status (context, suggested_action, time);
6405 if (path)
6406 gtk_tree_path_free (path);
6408 return TRUE;
6412 static gboolean
6413 pspp_sheet_view_drag_drop (GtkWidget *widget,
6414 GdkDragContext *context,
6415 /* coordinates relative to the widget */
6416 gint x,
6417 gint y,
6418 guint time)
6420 PsppSheetView *tree_view;
6421 GtkTreePath *path;
6422 GdkDragAction suggested_action = 0;
6423 GdkAtom target = GDK_NONE;
6424 TreeViewDragInfo *di;
6425 GtkTreeModel *model;
6426 gboolean path_down_mode;
6427 gboolean drop_append_mode;
6429 tree_view = PSPP_SHEET_VIEW (widget);
6431 model = pspp_sheet_view_get_model (tree_view);
6433 remove_scroll_timeout (PSPP_SHEET_VIEW (widget));
6435 di = get_info (tree_view);
6437 if (di == NULL)
6438 return FALSE;
6440 if (!check_model_dnd (model, GTK_TYPE_TREE_DRAG_DEST, "drag_drop"))
6441 return FALSE;
6443 if (!set_destination_row (tree_view, context, x, y, &suggested_action, &target))
6444 return FALSE;
6446 path = get_logical_dest_row (tree_view, &path_down_mode, &drop_append_mode);
6448 if (target != GDK_NONE && path != NULL)
6450 /* in case a motion had requested drag data, change things so we
6451 * treat drag data receives as a drop.
6453 set_status_pending (context, 0);
6454 set_dest_row (context, model, path,
6455 path_down_mode, tree_view->priv->empty_view_drop,
6456 drop_append_mode);
6459 if (path)
6460 gtk_tree_path_free (path);
6462 /* Unset this thing */
6463 pspp_sheet_view_set_drag_dest_row (PSPP_SHEET_VIEW (widget),
6464 NULL,
6465 PSPP_SHEET_VIEW_DROP_BEFORE);
6467 if (target != GDK_NONE)
6469 gtk_drag_get_data (widget, context, target, time);
6470 return TRUE;
6472 else
6473 return FALSE;
6476 static void
6477 pspp_sheet_view_drag_data_received (GtkWidget *widget,
6478 GdkDragContext *context,
6479 /* coordinates relative to the widget */
6480 gint x,
6481 gint y,
6482 GtkSelectionData *selection_data,
6483 guint info,
6484 guint time)
6486 GtkTreePath *path;
6487 TreeViewDragInfo *di;
6488 gboolean accepted = FALSE;
6489 GtkTreeModel *model;
6490 PsppSheetView *tree_view;
6491 GtkTreePath *dest_row;
6492 GdkDragAction suggested_action;
6493 gboolean path_down_mode;
6494 gboolean drop_append_mode;
6496 tree_view = PSPP_SHEET_VIEW (widget);
6498 model = pspp_sheet_view_get_model (tree_view);
6500 if (!check_model_dnd (model, GTK_TYPE_TREE_DRAG_DEST, "drag_data_received"))
6501 return;
6503 di = get_info (tree_view);
6505 if (di == NULL)
6506 return;
6508 suggested_action = get_status_pending (context);
6510 if (suggested_action)
6512 /* We are getting this data due to a request in drag_motion,
6513 * rather than due to a request in drag_drop, so we are just
6514 * supposed to call drag_status, not actually paste in the
6515 * data.
6517 path = get_logical_dest_row (tree_view, &path_down_mode,
6518 &drop_append_mode);
6520 if (path == NULL)
6521 suggested_action = 0;
6522 else if (path_down_mode)
6523 gtk_tree_path_down (path);
6525 if (suggested_action)
6527 if (!gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (model),
6528 path,
6529 selection_data))
6531 if (path_down_mode)
6533 path_down_mode = FALSE;
6534 gtk_tree_path_up (path);
6536 if (!gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (model),
6537 path,
6538 selection_data))
6539 suggested_action = 0;
6541 else
6542 suggested_action = 0;
6546 gdk_drag_status (context, suggested_action, time);
6548 if (path)
6549 gtk_tree_path_free (path);
6551 /* If you can't drop, remove user drop indicator until the next motion */
6552 if (suggested_action == 0)
6553 pspp_sheet_view_set_drag_dest_row (PSPP_SHEET_VIEW (widget),
6554 NULL,
6555 PSPP_SHEET_VIEW_DROP_BEFORE);
6557 return;
6560 dest_row = get_dest_row (context, &path_down_mode);
6562 if (dest_row == NULL)
6563 return;
6565 if (gtk_selection_data_get_length (selection_data) >= 0)
6567 if (path_down_mode)
6569 gtk_tree_path_down (dest_row);
6570 if (!gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (model),
6571 dest_row, selection_data))
6572 gtk_tree_path_up (dest_row);
6576 if (gtk_selection_data_get_length (selection_data) >= 0)
6578 if (gtk_tree_drag_dest_drag_data_received (GTK_TREE_DRAG_DEST (model),
6579 dest_row,
6580 selection_data))
6581 accepted = TRUE;
6584 gtk_drag_finish (context,
6585 accepted,
6586 (gdk_drag_context_get_actions (context) == GDK_ACTION_MOVE),
6587 time);
6589 if (gtk_tree_path_get_depth (dest_row) == 1
6590 && gtk_tree_path_get_indices (dest_row)[0] == 0)
6592 /* special special case drag to "0", scroll to first item */
6593 if (!tree_view->priv->scroll_to_path)
6594 pspp_sheet_view_scroll_to_cell (tree_view, dest_row, NULL, FALSE, 0.0, 0.0);
6597 gtk_tree_path_free (dest_row);
6599 /* drop dest_row */
6600 set_dest_row (context, NULL, NULL, FALSE, FALSE, FALSE);
6605 /* GtkContainer Methods
6609 static void
6610 pspp_sheet_view_remove (GtkContainer *container,
6611 GtkWidget *widget)
6613 PsppSheetView *tree_view = PSPP_SHEET_VIEW (container);
6614 PsppSheetViewChild *child = NULL;
6615 GList *tmp_list;
6617 tmp_list = tree_view->priv->children;
6618 while (tmp_list)
6620 child = tmp_list->data;
6621 if (child->widget == widget)
6623 gtk_widget_unparent (widget);
6625 tree_view->priv->children = g_list_remove_link (tree_view->priv->children, tmp_list);
6626 g_list_free_1 (tmp_list);
6627 g_slice_free (PsppSheetViewChild, child);
6628 return;
6631 tmp_list = tmp_list->next;
6634 tmp_list = tree_view->priv->columns;
6636 while (tmp_list)
6638 PsppSheetViewColumn *column;
6639 column = tmp_list->data;
6640 if (column->button == widget)
6642 gtk_widget_unparent (widget);
6643 return;
6645 tmp_list = tmp_list->next;
6649 static void
6650 pspp_sheet_view_forall (GtkContainer *container,
6651 gboolean include_internals,
6652 GtkCallback callback,
6653 gpointer callback_data)
6655 PsppSheetView *tree_view = PSPP_SHEET_VIEW (container);
6656 PsppSheetViewChild *child = NULL;
6657 PsppSheetViewColumn *column;
6658 GList *tmp_list;
6660 tmp_list = tree_view->priv->children;
6661 while (tmp_list)
6663 child = tmp_list->data;
6664 tmp_list = tmp_list->next;
6666 (* callback) (child->widget, callback_data);
6668 if (include_internals == FALSE)
6669 return;
6671 for (tmp_list = tree_view->priv->columns; tmp_list; tmp_list = tmp_list->next)
6673 column = tmp_list->data;
6675 if (column->button)
6676 (* callback) (column->button, callback_data);
6680 /* Returns TRUE if the treeview contains no "special" (editable or activatable)
6681 * cells. If so we draw one big row-spanning focus rectangle.
6683 static gboolean
6684 pspp_sheet_view_has_special_cell (PsppSheetView *tree_view)
6686 GList *list;
6688 if (tree_view->priv->special_cells != PSPP_SHEET_VIEW_SPECIAL_CELLS_DETECT)
6689 return tree_view->priv->special_cells = PSPP_SHEET_VIEW_SPECIAL_CELLS_YES;
6691 for (list = tree_view->priv->columns; list; list = list->next)
6693 if (!((PsppSheetViewColumn *)list->data)->visible)
6694 continue;
6695 if (_pspp_sheet_view_column_count_special_cells (list->data))
6696 return TRUE;
6699 return FALSE;
6702 static void
6703 pspp_sheet_view_focus_column (PsppSheetView *tree_view,
6704 PsppSheetViewColumn *focus_column,
6705 gboolean clamp_column_visible)
6707 g_return_if_fail (focus_column != NULL);
6709 tree_view->priv->focus_column = focus_column;
6711 if (gtk_container_get_focus_child (GTK_CONTAINER (tree_view)) != focus_column->button)
6712 gtk_widget_grab_focus (focus_column->button);
6714 if (clamp_column_visible)
6715 pspp_sheet_view_clamp_column_visible (tree_view, focus_column, FALSE);
6718 /* Returns TRUE if the focus is within the headers, after the focus operation is
6719 * done
6721 static gboolean
6722 pspp_sheet_view_header_focus (PsppSheetView *tree_view,
6723 GtkDirectionType dir,
6724 gboolean clamp_column_visible)
6726 GtkWidget *focus_child;
6727 PsppSheetViewColumn *focus_column;
6728 GList *last_column, *first_column;
6729 GList *tmp_list;
6730 gboolean rtl;
6732 if (! PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_HEADERS_VISIBLE))
6733 return FALSE;
6735 focus_child = gtk_container_get_focus_child (GTK_CONTAINER (tree_view));
6737 first_column = tree_view->priv->columns;
6738 while (first_column)
6740 PsppSheetViewColumn *c = PSPP_SHEET_VIEW_COLUMN (first_column->data);
6742 if (pspp_sheet_view_column_can_focus (c) && c->visible)
6743 break;
6744 first_column = first_column->next;
6747 /* No headers are visible, or are focusable. We can't focus in or out.
6749 if (first_column == NULL)
6750 return FALSE;
6752 last_column = g_list_last (tree_view->priv->columns);
6753 while (last_column)
6755 PsppSheetViewColumn *c = PSPP_SHEET_VIEW_COLUMN (last_column->data);
6757 if (pspp_sheet_view_column_can_focus (c) && c->visible)
6758 break;
6759 last_column = last_column->prev;
6763 rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
6765 switch (dir)
6767 case GTK_DIR_TAB_BACKWARD:
6768 case GTK_DIR_TAB_FORWARD:
6769 case GTK_DIR_UP:
6770 case GTK_DIR_DOWN:
6771 if (focus_child == NULL)
6773 if (tree_view->priv->focus_column != NULL &&
6774 pspp_sheet_view_column_can_focus (tree_view->priv->focus_column))
6775 focus_column = tree_view->priv->focus_column;
6776 else
6777 focus_column = first_column->data;
6778 pspp_sheet_view_focus_column (tree_view, focus_column,
6779 clamp_column_visible);
6780 return TRUE;
6782 return FALSE;
6784 case GTK_DIR_LEFT:
6785 case GTK_DIR_RIGHT:
6786 if (focus_child == NULL)
6788 if (tree_view->priv->focus_column != NULL)
6789 focus_column = tree_view->priv->focus_column;
6790 else if (dir == GTK_DIR_LEFT)
6791 focus_column = last_column->data;
6792 else
6793 focus_column = first_column->data;
6794 pspp_sheet_view_focus_column (tree_view, focus_column,
6795 clamp_column_visible);
6796 return TRUE;
6799 if (gtk_widget_child_focus (focus_child, dir))
6801 /* The focus moves inside the button. */
6802 /* This is probably a great example of bad UI */
6803 if (clamp_column_visible)
6804 pspp_sheet_view_clamp_column_visible (tree_view,
6805 tree_view->priv->focus_column,
6806 FALSE);
6807 return TRUE;
6810 /* We need to move the focus among the row of buttons. */
6811 for (tmp_list = tree_view->priv->columns; tmp_list; tmp_list = tmp_list->next)
6812 if (PSPP_SHEET_VIEW_COLUMN (tmp_list->data)->button == focus_child)
6813 break;
6815 if ((tmp_list == first_column && dir == (rtl ? GTK_DIR_RIGHT : GTK_DIR_LEFT))
6816 || (tmp_list == last_column && dir == (rtl ? GTK_DIR_LEFT : GTK_DIR_RIGHT)))
6818 gtk_widget_error_bell (GTK_WIDGET (tree_view));
6819 return TRUE;
6822 while (tmp_list)
6824 PsppSheetViewColumn *column;
6826 if (dir == (rtl ? GTK_DIR_LEFT : GTK_DIR_RIGHT))
6827 tmp_list = tmp_list->next;
6828 else
6829 tmp_list = tmp_list->prev;
6831 if (tmp_list == NULL)
6833 g_warning ("Internal button not found");
6834 break;
6836 column = tmp_list->data;
6837 if (column->visible &&
6838 pspp_sheet_view_column_can_focus (column))
6840 if (column->button)
6842 pspp_sheet_view_focus_column (tree_view, column,
6843 clamp_column_visible);
6844 return TRUE;
6848 return FALSE;
6850 default:
6851 g_assert_not_reached ();
6852 break;
6855 return FALSE;
6858 /* This function returns in 'path' the first focusable path, if the given path
6859 * is already focusable, it's the returned one.
6862 static gboolean
6863 search_first_focusable_path (PsppSheetView *tree_view,
6864 GtkTreePath **path,
6865 gboolean search_forward,
6866 int *new_node)
6868 /* XXX this function is trivial given that the sheetview doesn't support
6869 separator rows */
6870 int node = -1;
6872 if (!path || !*path)
6873 return FALSE;
6875 _pspp_sheet_view_find_node (tree_view, *path, &node);
6877 if (node < 0)
6878 return FALSE;
6880 if (new_node)
6881 *new_node = node;
6883 return (*path != NULL);
6886 static gint
6887 pspp_sheet_view_focus (GtkWidget *widget,
6888 GtkDirectionType direction)
6890 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
6891 GtkContainer *container = GTK_CONTAINER (widget);
6892 GtkWidget *focus_child;
6894 if (!gtk_widget_is_sensitive (widget) || !gtk_widget_get_can_focus (widget))
6895 return FALSE;
6897 focus_child = gtk_container_get_focus_child (container);
6899 pspp_sheet_view_stop_editing (PSPP_SHEET_VIEW (widget), FALSE);
6900 /* Case 1. Headers currently have focus. */
6901 if (focus_child)
6903 switch (direction)
6905 case GTK_DIR_LEFT:
6906 case GTK_DIR_RIGHT:
6907 pspp_sheet_view_header_focus (tree_view, direction, TRUE);
6908 return TRUE;
6909 case GTK_DIR_TAB_BACKWARD:
6910 case GTK_DIR_UP:
6911 return FALSE;
6912 case GTK_DIR_TAB_FORWARD:
6913 case GTK_DIR_DOWN:
6914 gtk_widget_grab_focus (widget);
6915 return TRUE;
6916 default:
6917 g_assert_not_reached ();
6918 return FALSE;
6922 /* Case 2. We don't have focus at all. */
6923 if (!gtk_widget_has_focus (widget))
6925 if (!pspp_sheet_view_header_focus (tree_view, direction, FALSE))
6926 gtk_widget_grab_focus (widget);
6927 return TRUE;
6930 /* Case 3. We have focus already. */
6931 if (direction == GTK_DIR_TAB_BACKWARD)
6932 return (pspp_sheet_view_header_focus (tree_view, direction, FALSE));
6933 else if (direction == GTK_DIR_TAB_FORWARD)
6934 return FALSE;
6936 /* Other directions caught by the keybindings */
6937 gtk_widget_grab_focus (widget);
6938 return TRUE;
6941 static void
6942 pspp_sheet_view_grab_focus (GtkWidget *widget)
6944 GTK_WIDGET_CLASS (pspp_sheet_view_parent_class)->grab_focus (widget);
6946 pspp_sheet_view_focus_to_cursor (PSPP_SHEET_VIEW (widget));
6949 static void
6950 pspp_sheet_view_style_set (GtkWidget *widget,
6951 GtkStyle *previous_style)
6953 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
6954 GList *list;
6955 PsppSheetViewColumn *column;
6957 if (gtk_widget_get_realized (widget))
6959 gdk_window_set_background (tree_view->priv->bin_window, &gtk_widget_get_style (widget)->base[gtk_widget_get_state (widget)]);
6960 gtk_style_set_background (gtk_widget_get_style (widget), tree_view->priv->header_window, GTK_STATE_NORMAL);
6961 pspp_sheet_view_set_grid_lines (tree_view, tree_view->priv->grid_lines);
6964 gtk_widget_style_get (widget,
6965 "expander-size", &tree_view->priv->expander_size,
6966 NULL);
6967 tree_view->priv->expander_size += EXPANDER_EXTRA_PADDING;
6969 for (list = tree_view->priv->columns; list; list = list->next)
6971 column = list->data;
6972 _pspp_sheet_view_column_cell_set_dirty (column);
6975 tree_view->priv->fixed_height = -1;
6977 /* Invalidate cached button style. */
6978 if (tree_view->priv->button_style)
6980 g_object_unref (tree_view->priv->button_style);
6981 tree_view->priv->button_style = NULL;
6984 gtk_widget_queue_resize (widget);
6988 static void
6989 pspp_sheet_view_set_focus_child (GtkContainer *container,
6990 GtkWidget *child)
6992 PsppSheetView *tree_view = PSPP_SHEET_VIEW (container);
6993 GList *list;
6995 for (list = tree_view->priv->columns; list; list = list->next)
6997 if (PSPP_SHEET_VIEW_COLUMN (list->data)->button == child)
6999 tree_view->priv->focus_column = PSPP_SHEET_VIEW_COLUMN (list->data);
7000 break;
7004 GTK_CONTAINER_CLASS (pspp_sheet_view_parent_class)->set_focus_child (container, child);
7007 static void
7008 pspp_sheet_view_set_adjustments (PsppSheetView *tree_view,
7009 GtkAdjustment *hadj,
7010 GtkAdjustment *vadj)
7012 gboolean need_adjust = FALSE;
7014 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
7016 if (hadj)
7017 g_return_if_fail (GTK_IS_ADJUSTMENT (hadj));
7018 else
7019 hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
7020 if (vadj)
7021 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj));
7022 else
7023 vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
7025 if (tree_view->priv->hadjustment && (tree_view->priv->hadjustment != hadj))
7027 g_signal_handlers_disconnect_by_func (tree_view->priv->hadjustment,
7028 pspp_sheet_view_adjustment_changed,
7029 tree_view);
7030 g_object_unref (tree_view->priv->hadjustment);
7033 if (tree_view->priv->vadjustment && (tree_view->priv->vadjustment != vadj))
7035 g_signal_handlers_disconnect_by_func (tree_view->priv->vadjustment,
7036 pspp_sheet_view_adjustment_changed,
7037 tree_view);
7038 g_object_unref (tree_view->priv->vadjustment);
7041 if (tree_view->priv->hadjustment != hadj)
7043 tree_view->priv->hadjustment = hadj;
7044 g_object_ref_sink (tree_view->priv->hadjustment);
7046 g_signal_connect (tree_view->priv->hadjustment, "value-changed",
7047 G_CALLBACK (pspp_sheet_view_adjustment_changed),
7048 tree_view);
7049 need_adjust = TRUE;
7052 if (tree_view->priv->vadjustment != vadj)
7054 tree_view->priv->vadjustment = vadj;
7055 g_object_ref_sink (tree_view->priv->vadjustment);
7057 g_signal_connect (tree_view->priv->vadjustment, "value-changed",
7058 G_CALLBACK (pspp_sheet_view_adjustment_changed),
7059 tree_view);
7060 need_adjust = TRUE;
7063 if (need_adjust)
7064 pspp_sheet_view_adjustment_changed (NULL, tree_view);
7068 static gboolean
7069 pspp_sheet_view_real_move_cursor (PsppSheetView *tree_view,
7070 GtkMovementStep step,
7071 gint count)
7073 PsppSheetSelectMode mode;
7074 GdkModifierType state;
7076 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
7077 g_return_val_if_fail (step == GTK_MOVEMENT_LOGICAL_POSITIONS ||
7078 step == GTK_MOVEMENT_VISUAL_POSITIONS ||
7079 step == GTK_MOVEMENT_DISPLAY_LINES ||
7080 step == GTK_MOVEMENT_PAGES ||
7081 step == GTK_MOVEMENT_BUFFER_ENDS ||
7082 step == GTK_MOVEMENT_DISPLAY_LINE_ENDS, FALSE);
7084 if (tree_view->priv->row_count == 0)
7085 return FALSE;
7086 if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
7087 return FALSE;
7089 pspp_sheet_view_stop_editing (tree_view, FALSE);
7090 PSPP_SHEET_VIEW_SET_FLAG (tree_view, PSPP_SHEET_VIEW_DRAW_KEYFOCUS);
7091 gtk_widget_grab_focus (GTK_WIDGET (tree_view));
7093 mode = 0;
7094 if (gtk_get_current_event_state (&state))
7096 if ((state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
7097 mode |= PSPP_SHEET_SELECT_MODE_TOGGLE;
7098 if ((state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK)
7099 mode |= PSPP_SHEET_SELECT_MODE_EXTEND;
7101 /* else we assume not pressed */
7103 switch (step)
7105 case GTK_MOVEMENT_LOGICAL_POSITIONS:
7106 pspp_sheet_view_move_cursor_tab (tree_view, count);
7107 break;
7108 case GTK_MOVEMENT_VISUAL_POSITIONS:
7109 pspp_sheet_view_move_cursor_left_right (tree_view, count, mode);
7110 break;
7111 case GTK_MOVEMENT_DISPLAY_LINES:
7112 pspp_sheet_view_move_cursor_up_down (tree_view, count, mode);
7113 break;
7114 case GTK_MOVEMENT_PAGES:
7115 pspp_sheet_view_move_cursor_page_up_down (tree_view, count, mode);
7116 break;
7117 case GTK_MOVEMENT_BUFFER_ENDS:
7118 pspp_sheet_view_move_cursor_start_end (tree_view, count, mode);
7119 break;
7120 case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
7121 pspp_sheet_view_move_cursor_line_start_end (tree_view, count, mode);
7122 break;
7123 default:
7124 g_assert_not_reached ();
7127 return TRUE;
7130 static void
7131 pspp_sheet_view_put (PsppSheetView *tree_view,
7132 GtkWidget *child_widget,
7133 /* in bin_window coordinates */
7134 gint x,
7135 gint y,
7136 gint width,
7137 gint height)
7139 PsppSheetViewChild *child;
7141 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
7142 g_return_if_fail (GTK_IS_WIDGET (child_widget));
7144 child = g_slice_new (PsppSheetViewChild);
7146 child->widget = child_widget;
7147 child->x = x;
7148 child->y = y;
7149 child->width = width;
7150 child->height = height;
7152 tree_view->priv->children = g_list_append (tree_view->priv->children, child);
7154 if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
7155 gtk_widget_set_parent_window (child->widget, tree_view->priv->bin_window);
7157 gtk_widget_set_parent (child_widget, GTK_WIDGET (tree_view));
7160 void
7161 _pspp_sheet_view_child_move_resize (PsppSheetView *tree_view,
7162 GtkWidget *widget,
7163 /* in tree coordinates */
7164 gint x,
7165 gint y,
7166 gint width,
7167 gint height)
7169 PsppSheetViewChild *child = NULL;
7170 GList *list;
7171 GdkRectangle allocation;
7173 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
7174 g_return_if_fail (GTK_IS_WIDGET (widget));
7176 for (list = tree_view->priv->children; list; list = list->next)
7178 if (((PsppSheetViewChild *)list->data)->widget == widget)
7180 child = list->data;
7181 break;
7184 if (child == NULL)
7185 return;
7187 allocation.x = child->x = x;
7188 allocation.y = child->y = y;
7189 allocation.width = child->width = width;
7190 allocation.height = child->height = height;
7192 if (gtk_widget_get_realized (widget))
7193 gtk_widget_size_allocate (widget, &allocation);
7197 /* TreeModel Callbacks
7200 static void
7201 pspp_sheet_view_row_changed (GtkTreeModel *model,
7202 GtkTreePath *path,
7203 GtkTreeIter *iter,
7204 gpointer data)
7206 PsppSheetView *tree_view = (PsppSheetView *)data;
7207 int node;
7208 gboolean free_path = FALSE;
7209 GtkTreePath *cursor_path;
7211 g_return_if_fail (path != NULL || iter != NULL);
7213 if (tree_view->priv->cursor != NULL)
7214 cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
7215 else
7216 cursor_path = NULL;
7218 if (tree_view->priv->edited_column &&
7219 (cursor_path == NULL || gtk_tree_path_compare (cursor_path, path) == 0))
7220 pspp_sheet_view_stop_editing (tree_view, TRUE);
7222 if (cursor_path != NULL)
7223 gtk_tree_path_free (cursor_path);
7225 if (path == NULL)
7227 path = gtk_tree_model_get_path (model, iter);
7228 free_path = TRUE;
7230 else if (iter == NULL)
7231 gtk_tree_model_get_iter (model, iter, path);
7233 _pspp_sheet_view_find_node (tree_view,
7234 path,
7235 &node);
7237 if (node >= 0)
7239 if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
7240 pspp_sheet_view_node_queue_redraw (tree_view, node);
7243 if (free_path)
7244 gtk_tree_path_free (path);
7247 static void
7248 pspp_sheet_view_row_inserted (GtkTreeModel *model,
7249 GtkTreePath *path,
7250 GtkTreeIter *iter,
7251 gpointer data)
7253 PsppSheetView *tree_view = (PsppSheetView *) data;
7254 gint *indices;
7255 int tmpnode = -1;
7256 gint height = tree_view->priv->fixed_height;
7257 gboolean free_path = FALSE;
7258 gboolean node_visible = TRUE;
7260 g_return_if_fail (path != NULL || iter != NULL);
7262 if (path == NULL)
7264 path = gtk_tree_model_get_path (model, iter);
7265 free_path = TRUE;
7267 else if (iter == NULL)
7268 gtk_tree_model_get_iter (model, iter, path);
7270 tree_view->priv->row_count = gtk_tree_model_iter_n_children (model, NULL);
7272 /* Update all row-references */
7273 gtk_tree_row_reference_inserted (G_OBJECT (data), path);
7274 indices = gtk_tree_path_get_indices (path);
7275 tmpnode = indices[0];
7277 range_tower_insert0 (tree_view->priv->selected, tmpnode, 1);
7279 if (height > 0)
7281 if (node_visible && node_is_visible (tree_view, tmpnode))
7282 gtk_widget_queue_resize (GTK_WIDGET (tree_view));
7283 else
7284 gtk_widget_queue_resize_no_redraw (GTK_WIDGET (tree_view));
7286 else
7287 install_presize_handler (tree_view);
7288 if (free_path)
7289 gtk_tree_path_free (path);
7292 static void
7293 pspp_sheet_view_row_deleted (GtkTreeModel *model,
7294 GtkTreePath *path,
7295 gpointer data)
7297 PsppSheetView *tree_view = (PsppSheetView *)data;
7298 int node;
7300 g_return_if_fail (path != NULL);
7302 gtk_tree_row_reference_deleted (G_OBJECT (data), path);
7304 _pspp_sheet_view_find_node (tree_view, path, &node);
7306 if (node < 0)
7307 return;
7309 range_tower_delete (tree_view->priv->selected, node, 1);
7311 /* Ensure we don't have a dangling pointer to a dead node */
7312 ensure_unprelighted (tree_view);
7314 /* Cancel editting if we've started */
7315 pspp_sheet_view_stop_editing (tree_view, TRUE);
7317 if (tree_view->priv->destroy_count_func)
7319 gint child_count = 0;
7320 tree_view->priv->destroy_count_func (tree_view, path, child_count, tree_view->priv->destroy_count_data);
7323 tree_view->priv->row_count = gtk_tree_model_iter_n_children (model, NULL);
7325 if (! gtk_tree_row_reference_valid (tree_view->priv->top_row))
7327 gtk_tree_row_reference_free (tree_view->priv->top_row);
7328 tree_view->priv->top_row = NULL;
7331 install_scroll_sync_handler (tree_view);
7333 gtk_widget_queue_resize (GTK_WIDGET (tree_view));
7335 #if 0
7336 if (helper_data.changed)
7337 g_signal_emit_by_name (tree_view->priv->selection, "changed");
7338 #endif
7341 static void
7342 pspp_sheet_view_rows_reordered (GtkTreeModel *model,
7343 GtkTreePath *parent,
7344 GtkTreeIter *iter,
7345 gint *new_order,
7346 gpointer data)
7348 PsppSheetView *tree_view = PSPP_SHEET_VIEW (data);
7349 gint len;
7351 /* XXX need to adjust selection */
7352 len = gtk_tree_model_iter_n_children (model, iter);
7354 if (len < 2)
7355 return;
7357 gtk_tree_row_reference_reordered (G_OBJECT (data),
7358 parent,
7359 iter,
7360 new_order);
7362 if (gtk_tree_path_get_depth (parent) != 0)
7363 return;
7365 if (tree_view->priv->edited_column)
7366 pspp_sheet_view_stop_editing (tree_view, TRUE);
7368 /* we need to be unprelighted */
7369 ensure_unprelighted (tree_view);
7371 gtk_widget_queue_draw (GTK_WIDGET (tree_view));
7373 pspp_sheet_view_dy_to_top_row (tree_view);
7377 /* Internal tree functions
7381 static void
7382 pspp_sheet_view_get_background_xrange (PsppSheetView *tree_view,
7383 PsppSheetViewColumn *column,
7384 gint *x1,
7385 gint *x2)
7387 PsppSheetViewColumn *tmp_column = NULL;
7388 gint total_width;
7389 GList *list;
7390 gboolean rtl;
7392 if (x1)
7393 *x1 = 0;
7395 if (x2)
7396 *x2 = 0;
7398 rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
7400 total_width = 0;
7401 for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns));
7402 list;
7403 list = (rtl ? list->prev : list->next))
7405 tmp_column = list->data;
7407 if (tmp_column == column)
7408 break;
7410 if (tmp_column->visible)
7411 total_width += tmp_column->width;
7414 if (tmp_column != column)
7416 g_warning (G_STRLOC": passed-in column isn't in the tree");
7417 return;
7420 if (x1)
7421 *x1 = total_width;
7423 if (x2)
7425 if (column->visible)
7426 *x2 = total_width + column->width;
7427 else
7428 *x2 = total_width; /* width of 0 */
7432 /* Make sure the node is visible vertically */
7433 static void
7434 pspp_sheet_view_clamp_node_visible (PsppSheetView *tree_view,
7435 int node)
7437 gint node_dy, height;
7438 GtkTreePath *path = NULL;
7440 if (!gtk_widget_get_realized (GTK_WIDGET (tree_view)))
7441 return;
7443 /* just return if the node is visible, avoiding a costly expose */
7444 node_dy = pspp_sheet_view_node_find_offset (tree_view, node);
7445 height = ROW_HEIGHT (tree_view);
7446 if (node_dy >= gtk_adjustment_get_value (tree_view->priv->vadjustment)
7447 && node_dy + height <= (gtk_adjustment_get_value (tree_view->priv->vadjustment)
7448 + gtk_adjustment_get_page_size (tree_view->priv->vadjustment)))
7449 return;
7451 path = _pspp_sheet_view_find_path (tree_view, node);
7452 if (path)
7454 /* We process updates because we want to clear old selected items when we scroll.
7455 * if this is removed, we get a "selection streak" at the bottom. */
7456 gdk_window_process_updates (tree_view->priv->bin_window, TRUE);
7457 pspp_sheet_view_scroll_to_cell (tree_view, path, NULL, FALSE, 0.0, 0.0);
7458 gtk_tree_path_free (path);
7462 static void
7463 pspp_sheet_view_clamp_column_visible (PsppSheetView *tree_view,
7464 PsppSheetViewColumn *column,
7465 gboolean focus_to_cell)
7467 gint x, width;
7469 if (column == NULL)
7470 return;
7472 x = column->allocation.x;
7473 width = column->allocation.width;
7475 if (width > gtk_adjustment_get_page_size (tree_view->priv->hadjustment))
7477 /* The column is larger than the horizontal page size. If the
7478 * column has cells which can be focussed individually, then we make
7479 * sure the cell which gets focus is fully visible (if even the
7480 * focus cell is bigger than the page size, we make sure the
7481 * left-hand side of the cell is visible).
7483 * If the column does not have those so-called special cells, we
7484 * make sure the left-hand side of the column is visible.
7487 if (focus_to_cell && pspp_sheet_view_has_special_cell (tree_view))
7489 GtkTreePath *cursor_path;
7490 GdkRectangle background_area, cell_area, focus_area;
7492 cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
7494 pspp_sheet_view_get_cell_area (tree_view,
7495 cursor_path, column, &cell_area);
7496 pspp_sheet_view_get_background_area (tree_view,
7497 cursor_path, column,
7498 &background_area);
7500 gtk_tree_path_free (cursor_path);
7502 _pspp_sheet_view_column_get_focus_area (column,
7503 &background_area,
7504 &cell_area,
7505 &focus_area);
7507 x = focus_area.x;
7508 width = focus_area.width;
7510 if (width < gtk_adjustment_get_page_size (tree_view->priv->hadjustment))
7512 if ((gtk_adjustment_get_value (tree_view->priv->hadjustment) + gtk_adjustment_get_page_size (tree_view->priv->hadjustment)) < (x + width))
7513 gtk_adjustment_set_value (tree_view->priv->hadjustment,
7514 x + width - gtk_adjustment_get_page_size (tree_view->priv->hadjustment));
7515 else if (gtk_adjustment_get_value (tree_view->priv->hadjustment) > x)
7516 gtk_adjustment_set_value (tree_view->priv->hadjustment, x);
7520 gtk_adjustment_set_value (tree_view->priv->hadjustment,
7521 CLAMP (x,
7522 gtk_adjustment_get_lower (tree_view->priv->hadjustment),
7523 gtk_adjustment_get_upper (tree_view->priv->hadjustment)
7524 - gtk_adjustment_get_page_size (tree_view->priv->hadjustment)));
7526 else
7528 if ((gtk_adjustment_get_value (tree_view->priv->hadjustment) + gtk_adjustment_get_page_size (tree_view->priv->hadjustment)) < (x + width))
7529 gtk_adjustment_set_value (tree_view->priv->hadjustment,
7530 x + width - gtk_adjustment_get_page_size (tree_view->priv->hadjustment));
7531 else if (gtk_adjustment_get_value (tree_view->priv->hadjustment) > x)
7532 gtk_adjustment_set_value (tree_view->priv->hadjustment, x);
7536 GtkTreePath *
7537 _pspp_sheet_view_find_path (PsppSheetView *tree_view,
7538 int node)
7540 GtkTreePath *path;
7542 path = gtk_tree_path_new ();
7543 if (node >= 0)
7544 gtk_tree_path_append_index (path, node);
7545 return path;
7548 void
7549 _pspp_sheet_view_find_node (PsppSheetView *tree_view,
7550 GtkTreePath *path,
7551 int *node)
7553 gint *indices = gtk_tree_path_get_indices (path);
7554 gint depth = gtk_tree_path_get_depth (path);
7556 *node = -1;
7557 if (depth == 0 || indices[0] < 0 || indices[0] >= tree_view->priv->row_count)
7558 return;
7559 *node = indices[0];
7562 static void
7563 pspp_sheet_view_add_move_binding (GtkBindingSet *binding_set,
7564 guint keyval,
7565 guint modmask,
7566 gboolean add_shifted_binding,
7567 GtkMovementStep step,
7568 gint count)
7571 gtk_binding_entry_add_signal (binding_set, keyval, modmask,
7572 "move-cursor", 2,
7573 G_TYPE_ENUM, step,
7574 G_TYPE_INT, count);
7576 if (add_shifted_binding)
7577 gtk_binding_entry_add_signal (binding_set, keyval, GDK_SHIFT_MASK,
7578 "move-cursor", 2,
7579 G_TYPE_ENUM, step,
7580 G_TYPE_INT, count);
7582 if ((modmask & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
7583 return;
7585 gtk_binding_entry_add_signal (binding_set, keyval, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
7586 "move-cursor", 2,
7587 G_TYPE_ENUM, step,
7588 G_TYPE_INT, count);
7590 gtk_binding_entry_add_signal (binding_set, keyval, GDK_CONTROL_MASK,
7591 "move-cursor", 2,
7592 G_TYPE_ENUM, step,
7593 G_TYPE_INT, count);
7596 static void
7597 pspp_sheet_view_set_column_drag_info (PsppSheetView *tree_view,
7598 PsppSheetViewColumn *column)
7600 PsppSheetViewColumn *left_column;
7601 PsppSheetViewColumn *cur_column = NULL;
7602 PsppSheetViewColumnReorder *reorder;
7603 gboolean rtl;
7604 GList *tmp_list;
7605 gint left;
7607 /* We want to precalculate the motion list such that we know what column slots
7608 * are available.
7610 left_column = NULL;
7611 rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
7613 /* First, identify all possible drop spots */
7614 if (rtl)
7615 tmp_list = g_list_last (tree_view->priv->columns);
7616 else
7617 tmp_list = g_list_first (tree_view->priv->columns);
7619 while (tmp_list)
7621 cur_column = PSPP_SHEET_VIEW_COLUMN (tmp_list->data);
7622 tmp_list = rtl?g_list_previous (tmp_list):g_list_next (tmp_list);
7624 if (cur_column->visible == FALSE)
7625 continue;
7627 /* If it's not the column moving and func tells us to skip over the column, we continue. */
7628 if (left_column != column && cur_column != column &&
7629 tree_view->priv->column_drop_func &&
7630 ! tree_view->priv->column_drop_func (tree_view, column, left_column, cur_column, tree_view->priv->column_drop_func_data))
7632 left_column = cur_column;
7633 continue;
7635 reorder = g_slice_new0 (PsppSheetViewColumnReorder);
7636 reorder->left_column = left_column;
7637 left_column = reorder->right_column = cur_column;
7639 tree_view->priv->column_drag_info = g_list_append (tree_view->priv->column_drag_info, reorder);
7642 /* Add the last one */
7643 if (tree_view->priv->column_drop_func == NULL ||
7644 ((left_column != column) &&
7645 tree_view->priv->column_drop_func (tree_view, column, left_column, NULL, tree_view->priv->column_drop_func_data)))
7647 reorder = g_slice_new0 (PsppSheetViewColumnReorder);
7648 reorder->left_column = left_column;
7649 reorder->right_column = NULL;
7650 tree_view->priv->column_drag_info = g_list_append (tree_view->priv->column_drag_info, reorder);
7653 /* We quickly check to see if it even makes sense to reorder columns. */
7654 /* If there is nothing that can be moved, then we return */
7656 if (tree_view->priv->column_drag_info == NULL)
7657 return;
7659 /* We know there are always 2 slots possbile, as you can always return column. */
7660 /* If that's all there is, return */
7661 if (tree_view->priv->column_drag_info->next == NULL ||
7662 (tree_view->priv->column_drag_info->next->next == NULL &&
7663 ((PsppSheetViewColumnReorder *)tree_view->priv->column_drag_info->data)->right_column == column &&
7664 ((PsppSheetViewColumnReorder *)tree_view->priv->column_drag_info->next->data)->left_column == column))
7666 for (tmp_list = tree_view->priv->column_drag_info; tmp_list; tmp_list = tmp_list->next)
7667 g_slice_free (PsppSheetViewColumnReorder, tmp_list->data);
7668 g_list_free (tree_view->priv->column_drag_info);
7669 tree_view->priv->column_drag_info = NULL;
7670 return;
7672 /* We fill in the ranges for the columns, now that we've isolated them */
7673 left = - TREE_VIEW_COLUMN_DRAG_DEAD_MULTIPLIER (tree_view);
7675 for (tmp_list = tree_view->priv->column_drag_info; tmp_list; tmp_list = tmp_list->next)
7677 reorder = (PsppSheetViewColumnReorder *) tmp_list->data;
7679 reorder->left_align = left;
7680 if (tmp_list->next != NULL)
7682 g_assert (tmp_list->next->data);
7683 left = reorder->right_align = (reorder->right_column->allocation.x +
7684 reorder->right_column->allocation.width +
7685 ((PsppSheetViewColumnReorder *)tmp_list->next->data)->left_column->allocation.x)/2;
7687 else
7689 gint width = gdk_window_get_width (tree_view->priv->header_window);
7690 reorder->right_align = width + TREE_VIEW_COLUMN_DRAG_DEAD_MULTIPLIER (tree_view);
7695 void
7696 _pspp_sheet_view_column_start_drag (PsppSheetView *tree_view,
7697 PsppSheetViewColumn *column)
7699 GdkEvent *send_event;
7700 GtkAllocation allocation;
7701 gint x, y;
7702 GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (tree_view));
7703 GdkDisplay *display = gdk_screen_get_display (screen);
7705 g_return_if_fail (tree_view->priv->column_drag_info == NULL);
7706 g_return_if_fail (tree_view->priv->cur_reorder == NULL);
7707 g_return_if_fail (column->button);
7709 pspp_sheet_view_set_column_drag_info (tree_view, column);
7711 if (tree_view->priv->column_drag_info == NULL)
7712 return;
7714 if (tree_view->priv->drag_window == NULL)
7716 GdkWindowAttr attributes;
7717 guint attributes_mask;
7719 attributes.window_type = GDK_WINDOW_CHILD;
7720 attributes.wclass = GDK_INPUT_OUTPUT;
7721 attributes.x = column->allocation.x;
7722 attributes.y = 0;
7723 attributes.width = column->allocation.width;
7724 attributes.height = column->allocation.height;
7725 attributes.visual = gtk_widget_get_visual (GTK_WIDGET (tree_view));
7726 attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK;
7727 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL ;
7729 tree_view->priv->drag_window = gdk_window_new (tree_view->priv->bin_window,
7730 &attributes,
7731 attributes_mask);
7732 gdk_window_set_user_data (tree_view->priv->drag_window, GTK_WIDGET (tree_view));
7735 gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME);
7736 gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME);
7738 gtk_grab_remove (column->button);
7740 send_event = gdk_event_new (GDK_LEAVE_NOTIFY);
7741 send_event->crossing.send_event = TRUE;
7742 send_event->crossing.window = g_object_ref (gtk_button_get_event_window (GTK_BUTTON (column->button)));
7743 send_event->crossing.subwindow = NULL;
7744 send_event->crossing.detail = GDK_NOTIFY_ANCESTOR;
7745 send_event->crossing.time = GDK_CURRENT_TIME;
7747 gtk_propagate_event (column->button, send_event);
7748 gdk_event_free (send_event);
7750 send_event = gdk_event_new (GDK_BUTTON_RELEASE);
7751 send_event->button.window = g_object_ref (gdk_screen_get_root_window (screen));
7752 send_event->button.send_event = TRUE;
7753 send_event->button.time = GDK_CURRENT_TIME;
7754 send_event->button.x = -1;
7755 send_event->button.y = -1;
7756 send_event->button.axes = NULL;
7757 send_event->button.state = 0;
7758 send_event->button.button = 1;
7759 send_event->button.device =
7760 gdk_device_manager_get_client_pointer (gdk_display_get_device_manager (display));
7762 send_event->button.x_root = 0;
7763 send_event->button.y_root = 0;
7765 gtk_propagate_event (column->button, send_event);
7766 gdk_event_free (send_event);
7768 /* Kids, don't try this at home */
7769 g_object_ref (column->button);
7770 gtk_container_remove (GTK_CONTAINER (tree_view), column->button);
7771 gtk_widget_set_parent_window (column->button, tree_view->priv->drag_window);
7772 gtk_widget_set_parent (column->button, GTK_WIDGET (tree_view));
7773 g_object_unref (column->button);
7775 tree_view->priv->drag_column_x = column->allocation.x;
7776 allocation = column->allocation;
7777 allocation.x = 0;
7778 gtk_widget_size_allocate (column->button, &allocation);
7779 gtk_widget_set_parent_window (column->button, tree_view->priv->drag_window);
7781 tree_view->priv->drag_column = column;
7782 gdk_window_show (tree_view->priv->drag_window);
7784 gdk_window_get_origin (tree_view->priv->header_window, &x, &y);
7786 gtk_widget_grab_focus (GTK_WIDGET (tree_view));
7787 while (gtk_events_pending ())
7788 gtk_main_iteration ();
7790 PSPP_SHEET_VIEW_SET_FLAG (tree_view, PSPP_SHEET_VIEW_IN_COLUMN_DRAG);
7791 gdk_pointer_grab (tree_view->priv->drag_window,
7792 FALSE,
7793 GDK_POINTER_MOTION_MASK|GDK_BUTTON_RELEASE_MASK,
7794 NULL, NULL, GDK_CURRENT_TIME);
7795 gdk_keyboard_grab (tree_view->priv->drag_window,
7796 FALSE,
7797 GDK_CURRENT_TIME);
7800 void
7801 _pspp_sheet_view_queue_draw_node (PsppSheetView *tree_view,
7802 int node,
7803 const GdkRectangle *clip_rect)
7805 GdkRectangle rect;
7806 GtkAllocation allocation;
7808 if (!gtk_widget_get_realized (GTK_WIDGET (tree_view)))
7809 return;
7811 gtk_widget_get_allocation (GTK_WIDGET (tree_view), &allocation);
7812 rect.x = 0;
7813 rect.width = MAX (tree_view->priv->width, allocation.width);
7815 rect.y = BACKGROUND_FIRST_PIXEL (tree_view, node);
7816 rect.height = ROW_HEIGHT (tree_view);
7818 if (clip_rect)
7820 GdkRectangle new_rect;
7822 gdk_rectangle_intersect (clip_rect, &rect, &new_rect);
7824 gdk_window_invalidate_rect (tree_view->priv->bin_window, &new_rect, TRUE);
7826 else
7828 gdk_window_invalidate_rect (tree_view->priv->bin_window, &rect, TRUE);
7832 static void
7833 pspp_sheet_view_queue_draw_path (PsppSheetView *tree_view,
7834 GtkTreePath *path,
7835 const GdkRectangle *clip_rect)
7837 int node = -1;
7839 _pspp_sheet_view_find_node (tree_view, path, &node);
7841 if (node)
7842 _pspp_sheet_view_queue_draw_node (tree_view, node, clip_rect);
7845 static void
7846 pspp_sheet_view_focus_to_cursor (PsppSheetView *tree_view)
7849 GtkTreePath *cursor_path;
7851 if ((tree_view->priv->row_count == 0) ||
7852 (! gtk_widget_get_realized (GTK_WIDGET (tree_view))))
7853 return;
7855 cursor_path = NULL;
7856 if (tree_view->priv->cursor)
7857 cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
7859 if (cursor_path == NULL)
7861 /* There's no cursor. Move the cursor to the first selected row, if any
7862 * are selected, otherwise to the first row in the sheetview.
7864 GList *selected_rows;
7865 GtkTreeModel *model;
7866 PsppSheetSelection *selection;
7868 selection = pspp_sheet_view_get_selection (tree_view);
7869 selected_rows = pspp_sheet_selection_get_selected_rows (selection, &model);
7871 if (selected_rows)
7873 /* XXX we could avoid doing O(n) work to get this result */
7874 cursor_path = gtk_tree_path_copy((const GtkTreePath *)(selected_rows->data));
7875 g_list_foreach (selected_rows, (GFunc)gtk_tree_path_free, NULL);
7876 g_list_free (selected_rows);
7878 else
7880 cursor_path = gtk_tree_path_new_first ();
7881 search_first_focusable_path (tree_view, &cursor_path,
7882 TRUE, NULL);
7885 gtk_tree_row_reference_free (tree_view->priv->cursor);
7886 tree_view->priv->cursor = NULL;
7888 if (cursor_path)
7890 if (tree_view->priv->selection->type == PSPP_SHEET_SELECTION_MULTIPLE ||
7891 tree_view->priv->selection->type == PSPP_SHEET_SELECTION_RECTANGLE)
7892 pspp_sheet_view_real_set_cursor (tree_view, cursor_path, FALSE, FALSE, 0);
7893 else
7894 pspp_sheet_view_real_set_cursor (tree_view, cursor_path, TRUE, FALSE, 0);
7898 if (cursor_path)
7900 /* Now find a column for the cursor. */
7901 PSPP_SHEET_VIEW_SET_FLAG (tree_view, PSPP_SHEET_VIEW_DRAW_KEYFOCUS);
7903 pspp_sheet_view_queue_draw_path (tree_view, cursor_path, NULL);
7904 gtk_tree_path_free (cursor_path);
7906 if (tree_view->priv->focus_column == NULL)
7908 GList *list;
7909 for (list = tree_view->priv->columns; list; list = list->next)
7911 if (PSPP_SHEET_VIEW_COLUMN (list->data)->visible)
7913 tree_view->priv->focus_column = PSPP_SHEET_VIEW_COLUMN (list->data);
7914 pspp_sheet_selection_unselect_all_columns (tree_view->priv->selection);
7915 pspp_sheet_selection_select_column (tree_view->priv->selection, tree_view->priv->focus_column);
7916 break;
7924 static gboolean
7925 pspp_sheet_view_move_cursor_up_down (PsppSheetView *tree_view,
7926 gint count,
7927 PsppSheetSelectMode mode)
7929 gint selection_count;
7930 int cursor_node = -1;
7931 int new_cursor_node = -1;
7932 GtkTreePath *cursor_path = NULL;
7933 gboolean grab_focus = TRUE;
7935 if (! gtk_widget_has_focus (GTK_WIDGET (tree_view)))
7936 return FALSE;
7938 cursor_path = NULL;
7939 if (!gtk_tree_row_reference_valid (tree_view->priv->cursor))
7940 /* FIXME: we lost the cursor; should we get the first? */
7941 return FALSE;
7943 cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
7944 _pspp_sheet_view_find_node (tree_view, cursor_path, &cursor_node);
7946 if (cursor_node < 0)
7947 /* FIXME: we lost the cursor; should we get the first? */
7948 return FALSE;
7950 selection_count = pspp_sheet_selection_count_selected_rows (tree_view->priv->selection);
7952 if (selection_count == 0
7953 && tree_view->priv->selection->type != PSPP_SHEET_SELECTION_NONE
7954 && !(mode & PSPP_SHEET_SELECT_MODE_TOGGLE))
7956 /* Don't move the cursor, but just select the current node */
7957 new_cursor_node = cursor_node;
7959 else
7961 if (count == -1)
7962 new_cursor_node = pspp_sheet_view_node_prev (tree_view, cursor_node);
7963 else
7964 new_cursor_node = pspp_sheet_view_node_next (tree_view, cursor_node);
7967 gtk_tree_path_free (cursor_path);
7969 if (new_cursor_node)
7971 cursor_path = _pspp_sheet_view_find_path (tree_view, new_cursor_node);
7973 search_first_focusable_path (tree_view, &cursor_path,
7974 (count != -1),
7975 &new_cursor_node);
7977 if (cursor_path)
7978 gtk_tree_path_free (cursor_path);
7982 * If the list has only one item and multi-selection is set then select
7983 * the row (if not yet selected).
7985 if ((tree_view->priv->selection->type == PSPP_SHEET_SELECTION_MULTIPLE ||
7986 tree_view->priv->selection->type == PSPP_SHEET_SELECTION_RECTANGLE) &&
7987 new_cursor_node < 0)
7989 if (count == -1)
7990 new_cursor_node = pspp_sheet_view_node_next (tree_view, cursor_node);
7991 else
7992 new_cursor_node = pspp_sheet_view_node_prev (tree_view, cursor_node);
7994 if (new_cursor_node < 0
7995 && !pspp_sheet_view_node_is_selected (tree_view, cursor_node))
7997 new_cursor_node = cursor_node;
7999 else
8001 new_cursor_node = -1;
8005 if (new_cursor_node >= 0)
8007 cursor_path = _pspp_sheet_view_find_path (tree_view, new_cursor_node);
8008 pspp_sheet_view_real_set_cursor (tree_view, cursor_path, TRUE, TRUE, mode);
8009 gtk_tree_path_free (cursor_path);
8011 else
8013 pspp_sheet_view_clamp_node_visible (tree_view, cursor_node);
8015 if (!(mode & PSPP_SHEET_SELECT_MODE_EXTEND))
8017 if (! gtk_widget_keynav_failed (GTK_WIDGET (tree_view),
8018 count < 0 ?
8019 GTK_DIR_UP : GTK_DIR_DOWN))
8021 GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (tree_view));
8023 if (toplevel)
8024 gtk_widget_child_focus (toplevel,
8025 count < 0 ?
8026 GTK_DIR_TAB_BACKWARD :
8027 GTK_DIR_TAB_FORWARD);
8029 grab_focus = FALSE;
8032 else
8034 gtk_widget_error_bell (GTK_WIDGET (tree_view));
8038 if (grab_focus)
8039 gtk_widget_grab_focus (GTK_WIDGET (tree_view));
8041 return new_cursor_node >= 0;
8044 static void
8045 pspp_sheet_view_move_cursor_page_up_down (PsppSheetView *tree_view,
8046 gint count,
8047 PsppSheetSelectMode mode)
8049 int cursor_node = -1;
8050 GtkTreePath *old_cursor_path = NULL;
8051 GtkTreePath *cursor_path = NULL;
8052 int start_cursor_node = -1;
8053 gint y;
8054 gint window_y;
8055 gint vertical_separator;
8057 if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
8058 return;
8060 if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
8061 old_cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
8062 else
8063 /* This is sorta weird. Focus in should give us a cursor */
8064 return;
8066 gtk_widget_style_get (GTK_WIDGET (tree_view), "vertical-separator", &vertical_separator, NULL);
8067 _pspp_sheet_view_find_node (tree_view, old_cursor_path, &cursor_node);
8069 if (cursor_node < 0)
8071 /* FIXME: we lost the cursor. Should we try to get one? */
8072 gtk_tree_path_free (old_cursor_path);
8073 return;
8076 y = pspp_sheet_view_node_find_offset (tree_view, cursor_node);
8077 window_y = RBTREE_Y_TO_TREE_WINDOW_Y (tree_view, y);
8078 y += tree_view->priv->cursor_offset;
8079 y += count * (int)gtk_adjustment_get_page_increment (tree_view->priv->vadjustment);
8080 y = CLAMP (y, (gint)gtk_adjustment_get_lower (tree_view->priv->vadjustment), (gint)gtk_adjustment_get_upper (tree_view->priv->vadjustment) - vertical_separator);
8082 if (y >= tree_view->priv->height)
8083 y = tree_view->priv->height - 1;
8085 tree_view->priv->cursor_offset =
8086 pspp_sheet_view_find_offset (tree_view, y, &cursor_node);
8088 if (tree_view->priv->cursor_offset > BACKGROUND_HEIGHT (tree_view))
8090 cursor_node = pspp_sheet_view_node_next (tree_view, cursor_node);
8091 tree_view->priv->cursor_offset -= BACKGROUND_HEIGHT (tree_view);
8094 y -= tree_view->priv->cursor_offset;
8095 cursor_path = _pspp_sheet_view_find_path (tree_view, cursor_node);
8097 start_cursor_node = cursor_node;
8099 if (! search_first_focusable_path (tree_view, &cursor_path,
8100 (count != -1),
8101 &cursor_node))
8103 /* It looks like we reached the end of the view without finding
8104 * a focusable row. We will step backwards to find the last
8105 * focusable row.
8107 cursor_node = start_cursor_node;
8108 cursor_path = _pspp_sheet_view_find_path (tree_view, cursor_node);
8110 search_first_focusable_path (tree_view, &cursor_path,
8111 (count == -1),
8112 &cursor_node);
8115 if (!cursor_path)
8116 goto cleanup;
8118 /* update y */
8119 y = pspp_sheet_view_node_find_offset (tree_view, cursor_node);
8121 pspp_sheet_view_real_set_cursor (tree_view, cursor_path, TRUE, FALSE, mode);
8123 y -= window_y;
8124 pspp_sheet_view_scroll_to_point (tree_view, -1, y);
8125 pspp_sheet_view_clamp_node_visible (tree_view, cursor_node);
8126 _pspp_sheet_view_queue_draw_node (tree_view, cursor_node, NULL);
8128 if (!gtk_tree_path_compare (old_cursor_path, cursor_path))
8129 gtk_widget_error_bell (GTK_WIDGET (tree_view));
8131 gtk_widget_grab_focus (GTK_WIDGET (tree_view));
8133 cleanup:
8134 gtk_tree_path_free (old_cursor_path);
8135 gtk_tree_path_free (cursor_path);
8138 static void
8139 pspp_sheet_view_move_cursor_left_right (PsppSheetView *tree_view,
8140 gint count,
8141 PsppSheetSelectMode mode)
8143 int cursor_node = -1;
8144 GtkTreePath *cursor_path = NULL;
8145 PsppSheetViewColumn *column;
8146 GtkTreeIter iter;
8147 GList *list;
8148 gboolean found_column = FALSE;
8149 gboolean rtl;
8151 rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
8153 if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
8154 return;
8156 if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
8157 cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
8158 else
8159 return;
8161 _pspp_sheet_view_find_node (tree_view, cursor_path, &cursor_node);
8162 if (cursor_node < 0)
8163 return;
8164 if (gtk_tree_model_get_iter (tree_view->priv->model, &iter, cursor_path) == FALSE)
8166 gtk_tree_path_free (cursor_path);
8167 return;
8169 gtk_tree_path_free (cursor_path);
8171 list = rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns);
8172 if (tree_view->priv->focus_column)
8174 for (; list; list = (rtl ? list->prev : list->next))
8176 if (list->data == tree_view->priv->focus_column)
8177 break;
8181 while (list)
8183 gboolean left, right;
8185 column = list->data;
8186 if (column->visible == FALSE || column->row_head)
8187 goto loop_end;
8189 pspp_sheet_view_column_cell_set_cell_data (column,
8190 tree_view->priv->model,
8191 &iter);
8193 if (rtl)
8195 right = list->prev ? TRUE : FALSE;
8196 left = list->next ? TRUE : FALSE;
8198 else
8200 left = list->prev ? TRUE : FALSE;
8201 right = list->next ? TRUE : FALSE;
8204 if (_pspp_sheet_view_column_cell_focus (column, count, left, right))
8206 tree_view->priv->focus_column = column;
8207 found_column = TRUE;
8208 break;
8210 loop_end:
8211 if (count == 1)
8212 list = rtl ? list->prev : list->next;
8213 else
8214 list = rtl ? list->next : list->prev;
8217 if (found_column)
8219 _pspp_sheet_view_queue_draw_node (tree_view, cursor_node, NULL);
8220 g_signal_emit (tree_view, tree_view_signals[CURSOR_CHANGED], 0);
8221 gtk_widget_grab_focus (GTK_WIDGET (tree_view));
8223 else
8225 gtk_widget_error_bell (GTK_WIDGET (tree_view));
8228 pspp_sheet_view_clamp_column_visible (tree_view,
8229 tree_view->priv->focus_column, TRUE);
8232 static void
8233 pspp_sheet_view_move_cursor_line_start_end (PsppSheetView *tree_view,
8234 gint count,
8235 PsppSheetSelectMode mode)
8237 int cursor_node = -1;
8238 GtkTreePath *cursor_path = NULL;
8239 PsppSheetViewColumn *column;
8240 PsppSheetViewColumn *found_column;
8241 GtkTreeIter iter;
8242 GList *list;
8243 gboolean rtl;
8245 rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
8247 if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
8248 return;
8250 if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
8251 cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
8252 else
8253 return;
8255 _pspp_sheet_view_find_node (tree_view, cursor_path, &cursor_node);
8256 if (cursor_node < 0)
8257 return;
8258 if (gtk_tree_model_get_iter (tree_view->priv->model, &iter, cursor_path) == FALSE)
8260 gtk_tree_path_free (cursor_path);
8261 return;
8263 gtk_tree_path_free (cursor_path);
8265 list = rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns);
8266 if (tree_view->priv->focus_column)
8268 for (; list; list = (rtl ? list->prev : list->next))
8270 if (list->data == tree_view->priv->focus_column)
8271 break;
8275 found_column = NULL;
8276 while (list)
8278 gboolean left, right;
8280 column = list->data;
8281 if (column->visible == FALSE || column->row_head)
8282 goto loop_end;
8284 pspp_sheet_view_column_cell_set_cell_data (column,
8285 tree_view->priv->model,
8286 &iter);
8288 if (rtl)
8290 right = list->prev ? TRUE : FALSE;
8291 left = list->next ? TRUE : FALSE;
8293 else
8295 left = list->prev ? TRUE : FALSE;
8296 right = list->next ? TRUE : FALSE;
8299 if (column->tabbable
8300 && _pspp_sheet_view_column_cell_focus (column, count, left, right))
8301 found_column = column;
8303 loop_end:
8304 if (count == 1)
8305 list = rtl ? list->prev : list->next;
8306 else
8307 list = rtl ? list->next : list->prev;
8310 if (found_column)
8312 tree_view->priv->focus_column = found_column;
8313 _pspp_sheet_view_queue_draw_node (tree_view, cursor_node, NULL);
8314 g_signal_emit (tree_view, tree_view_signals[CURSOR_CHANGED], 0);
8315 gtk_widget_grab_focus (GTK_WIDGET (tree_view));
8318 pspp_sheet_view_clamp_column_visible (tree_view,
8319 tree_view->priv->focus_column, TRUE);
8322 static gboolean
8323 try_move_cursor_tab (PsppSheetView *tree_view,
8324 gboolean start_at_focus_column,
8325 gint count)
8327 PsppSheetViewColumn *column;
8328 GtkTreeIter iter;
8329 int cursor_node = -1;
8330 GtkTreePath *cursor_path = NULL;
8331 gboolean rtl;
8332 GList *list;
8334 if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
8335 cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
8336 else
8337 return TRUE;
8339 _pspp_sheet_view_find_node (tree_view, cursor_path, &cursor_node);
8340 if (cursor_node < 0)
8341 return TRUE;
8342 if (gtk_tree_model_get_iter (tree_view->priv->model, &iter, cursor_path) == FALSE)
8344 gtk_tree_path_free (cursor_path);
8345 return TRUE;
8347 gtk_tree_path_free (cursor_path);
8349 rtl = gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL;
8350 if (start_at_focus_column)
8352 list = (rtl
8353 ? g_list_last (tree_view->priv->columns)
8354 : g_list_first (tree_view->priv->columns));
8355 if (tree_view->priv->focus_column)
8357 for (; list; list = (rtl ? list->prev : list->next))
8359 if (list->data == tree_view->priv->focus_column)
8360 break;
8364 else
8366 list = (rtl ^ (count == 1)
8367 ? g_list_first (tree_view->priv->columns)
8368 : g_list_last (tree_view->priv->columns));
8371 while (list)
8373 gboolean left, right;
8375 column = list->data;
8376 if (column->visible == FALSE || !column->tabbable)
8377 goto loop_end;
8379 pspp_sheet_view_column_cell_set_cell_data (column,
8380 tree_view->priv->model,
8381 &iter);
8383 if (rtl)
8385 right = list->prev ? TRUE : FALSE;
8386 left = list->next ? TRUE : FALSE;
8388 else
8390 left = list->prev ? TRUE : FALSE;
8391 right = list->next ? TRUE : FALSE;
8394 if (column->tabbable
8395 && _pspp_sheet_view_column_cell_focus (column, count, left, right))
8397 tree_view->priv->focus_column = column;
8398 _pspp_sheet_view_queue_draw_node (tree_view, cursor_node, NULL);
8399 g_signal_emit (tree_view, tree_view_signals[CURSOR_CHANGED], 0);
8400 gtk_widget_grab_focus (GTK_WIDGET (tree_view));
8401 return TRUE;
8403 loop_end:
8404 if (count == 1)
8405 list = rtl ? list->prev : list->next;
8406 else
8407 list = rtl ? list->next : list->prev;
8410 return FALSE;
8413 static void
8414 pspp_sheet_view_move_cursor_tab (PsppSheetView *tree_view,
8415 gint count)
8417 if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
8418 return;
8420 if (!try_move_cursor_tab (tree_view, TRUE, count))
8422 if (pspp_sheet_view_move_cursor_up_down (tree_view, count, 0)
8423 && !try_move_cursor_tab (tree_view, FALSE, count))
8424 gtk_widget_error_bell (GTK_WIDGET (tree_view));
8427 pspp_sheet_view_clamp_column_visible (tree_view,
8428 tree_view->priv->focus_column, TRUE);
8431 static void
8432 pspp_sheet_view_move_cursor_start_end (PsppSheetView *tree_view,
8433 gint count,
8434 PsppSheetSelectMode mode)
8436 int cursor_node;
8437 GtkTreePath *path;
8438 GtkTreePath *old_path;
8440 if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
8441 return;
8443 g_return_if_fail (tree_view->priv->row_count > 0);
8445 pspp_sheet_view_get_cursor (tree_view, &old_path, NULL);
8447 if (count == -1)
8449 /* Now go forward to find the first focusable row. */
8450 path = _pspp_sheet_view_find_path (tree_view, 0);
8451 search_first_focusable_path (tree_view, &path,
8452 TRUE, &cursor_node);
8454 else
8456 /* Now go backwards to find last focusable row. */
8457 path = _pspp_sheet_view_find_path (tree_view, tree_view->priv->row_count - 1);
8458 search_first_focusable_path (tree_view, &path,
8459 FALSE, &cursor_node);
8462 if (!path)
8463 goto cleanup;
8465 if (gtk_tree_path_compare (old_path, path))
8467 pspp_sheet_view_real_set_cursor (tree_view, path, TRUE, TRUE, mode);
8468 gtk_widget_grab_focus (GTK_WIDGET (tree_view));
8470 else
8472 gtk_widget_error_bell (GTK_WIDGET (tree_view));
8475 cleanup:
8476 gtk_tree_path_free (old_path);
8477 gtk_tree_path_free (path);
8480 static gboolean
8481 pspp_sheet_view_real_select_all (PsppSheetView *tree_view)
8483 if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
8484 return FALSE;
8486 if (tree_view->priv->selection->type != PSPP_SHEET_SELECTION_MULTIPLE &&
8487 tree_view->priv->selection->type != PSPP_SHEET_SELECTION_RECTANGLE)
8488 return FALSE;
8490 pspp_sheet_selection_select_all (tree_view->priv->selection);
8492 return TRUE;
8495 static gboolean
8496 pspp_sheet_view_real_unselect_all (PsppSheetView *tree_view)
8498 if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
8499 return FALSE;
8501 if (tree_view->priv->selection->type != PSPP_SHEET_SELECTION_MULTIPLE &&
8502 tree_view->priv->selection->type != PSPP_SHEET_SELECTION_RECTANGLE)
8503 return FALSE;
8505 pspp_sheet_selection_unselect_all (tree_view->priv->selection);
8507 return TRUE;
8510 static gboolean
8511 pspp_sheet_view_real_select_cursor_row (PsppSheetView *tree_view,
8512 gboolean start_editing,
8513 PsppSheetSelectMode mode)
8515 int new_node = -1;
8516 int cursor_node = -1;
8517 GtkTreePath *cursor_path = NULL;
8519 if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
8520 return FALSE;
8522 if (tree_view->priv->cursor)
8523 cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
8525 if (cursor_path == NULL)
8526 return FALSE;
8528 _pspp_sheet_view_find_node (tree_view, cursor_path,
8529 &cursor_node);
8531 if (cursor_node < 0)
8533 gtk_tree_path_free (cursor_path);
8534 return FALSE;
8537 if (!(mode & PSPP_SHEET_SELECT_MODE_EXTEND) && start_editing &&
8538 tree_view->priv->focus_column)
8540 if (pspp_sheet_view_start_editing (tree_view, cursor_path))
8542 gtk_tree_path_free (cursor_path);
8543 return TRUE;
8547 _pspp_sheet_selection_internal_select_node (tree_view->priv->selection,
8548 cursor_node,
8549 cursor_path,
8550 mode,
8551 FALSE);
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.
8557 _pspp_sheet_view_find_node (tree_view, cursor_path, &new_node);
8559 if (cursor_node != new_node)
8560 return FALSE;
8562 pspp_sheet_view_clamp_node_visible (tree_view, cursor_node);
8564 gtk_widget_grab_focus (GTK_WIDGET (tree_view));
8565 _pspp_sheet_view_queue_draw_node (tree_view, cursor_node, NULL);
8567 if (!(mode & PSPP_SHEET_SELECT_MODE_EXTEND))
8568 pspp_sheet_view_row_activated (tree_view, cursor_path,
8569 tree_view->priv->focus_column);
8571 gtk_tree_path_free (cursor_path);
8573 return TRUE;
8576 static gboolean
8577 pspp_sheet_view_real_toggle_cursor_row (PsppSheetView *tree_view)
8579 int new_node = -1;
8580 int cursor_node = -1;
8581 GtkTreePath *cursor_path = NULL;
8583 if (!gtk_widget_has_focus (GTK_WIDGET (tree_view)))
8584 return FALSE;
8586 cursor_path = NULL;
8587 if (tree_view->priv->cursor)
8588 cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
8590 if (cursor_path == NULL)
8591 return FALSE;
8593 _pspp_sheet_view_find_node (tree_view, cursor_path, &cursor_node);
8594 if (cursor_node < 0)
8596 gtk_tree_path_free (cursor_path);
8597 return FALSE;
8600 _pspp_sheet_selection_internal_select_node (tree_view->priv->selection,
8601 cursor_node,
8602 cursor_path,
8603 PSPP_SHEET_SELECT_MODE_TOGGLE,
8604 FALSE);
8606 /* We bail out if the original (tree, node) don't exist anymore after
8607 * handling the selection-changed callback. We do return TRUE because
8608 * the key press has been handled at this point.
8610 _pspp_sheet_view_find_node (tree_view, cursor_path, &new_node);
8612 if (cursor_node != new_node)
8613 return FALSE;
8615 pspp_sheet_view_clamp_node_visible (tree_view, cursor_node);
8617 gtk_widget_grab_focus (GTK_WIDGET (tree_view));
8618 pspp_sheet_view_queue_draw_path (tree_view, cursor_path, NULL);
8619 gtk_tree_path_free (cursor_path);
8621 return TRUE;
8624 static gboolean
8625 pspp_sheet_view_search_entry_flush_timeout (PsppSheetView *tree_view)
8627 pspp_sheet_view_search_dialog_hide (tree_view->priv->search_window, tree_view);
8628 tree_view->priv->typeselect_flush_timeout = 0;
8630 return FALSE;
8633 /* Cut and paste from gtkwindow.c */
8634 static void
8635 send_focus_change (GtkWidget *widget,
8636 gboolean in)
8638 GdkEvent *fevent = gdk_event_new (GDK_FOCUS_CHANGE);
8640 fevent->focus_change.type = GDK_FOCUS_CHANGE;
8641 fevent->focus_change.window = g_object_ref (gtk_widget_get_window (widget));
8642 fevent->focus_change.in = in;
8644 gtk_widget_send_focus_change (widget, fevent);
8645 gdk_event_free (fevent);
8648 static void
8649 pspp_sheet_view_ensure_interactive_directory (PsppSheetView *tree_view)
8651 GtkWidget *frame, *vbox, *toplevel;
8652 GdkScreen *screen;
8654 if (tree_view->priv->search_custom_entry_set)
8655 return;
8657 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (tree_view));
8658 screen = gtk_widget_get_screen (GTK_WIDGET (tree_view));
8660 if (tree_view->priv->search_window != NULL)
8662 if (gtk_window_get_group (GTK_WINDOW (toplevel)))
8663 gtk_window_group_add_window (gtk_window_get_group (GTK_WINDOW (toplevel)),
8664 GTK_WINDOW (tree_view->priv->search_window));
8665 else if (gtk_window_get_group (GTK_WINDOW (tree_view->priv->search_window)))
8666 gtk_window_group_remove_window (gtk_window_get_group (GTK_WINDOW (tree_view->priv->search_window)),
8667 GTK_WINDOW (tree_view->priv->search_window));
8668 gtk_window_set_screen (GTK_WINDOW (tree_view->priv->search_window), screen);
8669 return;
8672 tree_view->priv->search_window = gtk_window_new (GTK_WINDOW_POPUP);
8673 gtk_window_set_screen (GTK_WINDOW (tree_view->priv->search_window), screen);
8675 if (gtk_window_get_group (GTK_WINDOW (toplevel)))
8676 gtk_window_group_add_window (gtk_window_get_group (GTK_WINDOW (toplevel)),
8677 GTK_WINDOW (tree_view->priv->search_window));
8679 gtk_window_set_type_hint (GTK_WINDOW (tree_view->priv->search_window),
8680 GDK_WINDOW_TYPE_HINT_UTILITY);
8681 gtk_window_set_modal (GTK_WINDOW (tree_view->priv->search_window), TRUE);
8682 g_signal_connect (tree_view->priv->search_window, "delete-event",
8683 G_CALLBACK (pspp_sheet_view_search_delete_event),
8684 tree_view);
8685 g_signal_connect (tree_view->priv->search_window, "key-press-event",
8686 G_CALLBACK (pspp_sheet_view_search_key_press_event),
8687 tree_view);
8688 g_signal_connect (tree_view->priv->search_window, "button-press-event",
8689 G_CALLBACK (pspp_sheet_view_search_button_press_event),
8690 tree_view);
8691 g_signal_connect (tree_view->priv->search_window, "scroll-event",
8692 G_CALLBACK (pspp_sheet_view_search_scroll_event),
8693 tree_view);
8695 frame = gtk_frame_new (NULL);
8696 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
8697 gtk_widget_show (frame);
8698 gtk_container_add (GTK_CONTAINER (tree_view->priv->search_window), frame);
8700 vbox = gtk_vbox_new (FALSE, 0);
8701 gtk_widget_show (vbox);
8702 gtk_container_add (GTK_CONTAINER (frame), vbox);
8703 gtk_container_set_border_width (GTK_CONTAINER (vbox), 3);
8705 /* add entry */
8706 tree_view->priv->search_entry = gtk_entry_new ();
8707 gtk_widget_show (tree_view->priv->search_entry);
8708 g_signal_connect (tree_view->priv->search_entry, "populate-popup",
8709 G_CALLBACK (pspp_sheet_view_search_disable_popdown),
8710 tree_view);
8711 g_signal_connect (tree_view->priv->search_entry,
8712 "activate", G_CALLBACK (pspp_sheet_view_search_activate),
8713 tree_view);
8715 #if GTK3_TRANSITION
8716 g_signal_connect (GTK_ENTRY (tree_view->priv->search_entry)->im_context,
8717 "preedit-changed",
8718 G_CALLBACK (pspp_sheet_view_search_preedit_changed),
8719 tree_view);
8720 #endif
8722 gtk_container_add (GTK_CONTAINER (vbox),
8723 tree_view->priv->search_entry);
8725 gtk_widget_realize (tree_view->priv->search_entry);
8728 /* Pops up the interactive search entry. If keybinding is TRUE then the user
8729 * started this by typing the start_interactive_search keybinding. Otherwise, it came from
8731 static gboolean
8732 pspp_sheet_view_real_start_interactive_search (PsppSheetView *tree_view,
8733 gboolean keybinding)
8735 /* We only start interactive search if we have focus or the columns
8736 * have focus. If one of our children have focus, we don't want to
8737 * start the search.
8739 GList *list;
8740 gboolean found_focus = FALSE;
8741 GtkWidgetClass *entry_parent_class;
8743 if (!tree_view->priv->enable_search && !keybinding)
8744 return FALSE;
8746 if (tree_view->priv->search_custom_entry_set)
8747 return FALSE;
8749 if (tree_view->priv->search_window != NULL &&
8750 gtk_widget_get_visible (tree_view->priv->search_window))
8751 return TRUE;
8753 for (list = tree_view->priv->columns; list; list = list->next)
8755 PsppSheetViewColumn *column;
8757 column = list->data;
8758 if (! column->visible)
8759 continue;
8761 if (column->button && gtk_widget_has_focus (column->button))
8763 found_focus = TRUE;
8764 break;
8768 if (gtk_widget_has_focus (GTK_WIDGET (tree_view)))
8769 found_focus = TRUE;
8771 if (!found_focus)
8772 return FALSE;
8774 if (tree_view->priv->search_column < 0)
8775 return FALSE;
8777 pspp_sheet_view_ensure_interactive_directory (tree_view);
8779 if (keybinding)
8780 gtk_entry_set_text (GTK_ENTRY (tree_view->priv->search_entry), "");
8782 /* done, show it */
8783 tree_view->priv->search_position_func (tree_view, tree_view->priv->search_window, tree_view->priv->search_position_user_data);
8784 gtk_widget_show (tree_view->priv->search_window);
8785 if (tree_view->priv->search_entry_changed_id == 0)
8787 tree_view->priv->search_entry_changed_id =
8788 g_signal_connect (tree_view->priv->search_entry, "changed",
8789 G_CALLBACK (pspp_sheet_view_search_init),
8790 tree_view);
8793 tree_view->priv->typeselect_flush_timeout =
8794 gdk_threads_add_timeout (PSPP_SHEET_VIEW_SEARCH_DIALOG_TIMEOUT,
8795 (GSourceFunc) pspp_sheet_view_search_entry_flush_timeout,
8796 tree_view);
8798 /* Grab focus will select all the text. We don't want that to happen, so we
8799 * call the parent instance and bypass the selection change. This is probably
8800 * really non-kosher. */
8801 entry_parent_class = g_type_class_peek_parent (GTK_ENTRY_GET_CLASS (tree_view->priv->search_entry));
8802 (entry_parent_class->grab_focus) (tree_view->priv->search_entry);
8804 /* send focus-in event */
8805 send_focus_change (tree_view->priv->search_entry, TRUE);
8807 /* search first matching iter */
8808 pspp_sheet_view_search_init (tree_view->priv->search_entry, tree_view);
8810 return TRUE;
8813 static gboolean
8814 pspp_sheet_view_start_interactive_search (PsppSheetView *tree_view)
8816 return pspp_sheet_view_real_start_interactive_search (tree_view, TRUE);
8819 /* this function returns the new width of the column being resized given
8820 * the column and x position of the cursor; the x cursor position is passed
8821 * in as a pointer and automagicly corrected if it's beyond min/max limits
8823 static gint
8824 pspp_sheet_view_new_column_width (PsppSheetView *tree_view,
8825 gint i,
8826 gint *x)
8828 PsppSheetViewColumn *column;
8829 gint width;
8830 gboolean rtl;
8832 /* first translate the x position from gtk_widget_get_window (widget)
8833 * to clist->clist_window
8835 rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
8836 column = g_list_nth (tree_view->priv->columns, i)->data;
8837 width = rtl ? (column->allocation.x + column->allocation.width - *x) : (*x - column->allocation.x);
8839 /* Clamp down the value */
8840 if (column->min_width == -1)
8841 width = MAX (column->button_request, width);
8842 else
8843 width = MAX (column->min_width, width);
8844 if (column->max_width != -1)
8845 width = MIN (width, column->max_width);
8847 *x = rtl ? (column->allocation.x + column->allocation.width - width) : (column->allocation.x + width);
8849 return width;
8853 /* FIXME this adjust_allocation is a big cut-and-paste from
8854 * GtkCList, needs to be some "official" way to do this
8855 * factored out.
8857 typedef struct
8859 GdkWindow *window;
8860 int dx;
8861 int dy;
8862 } ScrollData;
8864 /* The window to which gtk_widget_get_window (widget) is relative */
8865 #define ALLOCATION_WINDOW(widget) \
8866 (!gtk_widget_get_has_window (widget) ? \
8867 gtk_widget_get_window (widget) : \
8868 gdk_window_get_parent (gtk_widget_get_window (widget)))
8870 static void
8871 adjust_allocation_recurse (GtkWidget *widget,
8872 gpointer data)
8874 ScrollData *scroll_data = data;
8875 GtkAllocation allocation;
8876 gtk_widget_get_allocation (widget, &allocation);
8877 /* Need to really size allocate instead of just poking
8878 * into widget->allocation if the widget is not realized.
8879 * FIXME someone figure out why this was.
8881 if (!gtk_widget_get_realized (widget))
8883 if (gtk_widget_get_visible (widget))
8885 GdkRectangle tmp_rectangle = allocation;
8886 tmp_rectangle.x += scroll_data->dx;
8887 tmp_rectangle.y += scroll_data->dy;
8889 gtk_widget_size_allocate (widget, &tmp_rectangle);
8892 else
8894 if (ALLOCATION_WINDOW (widget) == scroll_data->window)
8896 allocation.x += scroll_data->dx;
8897 allocation.y += scroll_data->dy;
8899 if (GTK_IS_CONTAINER (widget))
8900 gtk_container_forall (GTK_CONTAINER (widget),
8901 adjust_allocation_recurse,
8902 data);
8907 static void
8908 adjust_allocation (GtkWidget *widget,
8909 int dx,
8910 int dy)
8912 ScrollData scroll_data;
8914 if (gtk_widget_get_realized (widget))
8915 scroll_data.window = ALLOCATION_WINDOW (widget);
8916 else
8917 scroll_data.window = NULL;
8919 scroll_data.dx = dx;
8920 scroll_data.dy = dy;
8922 adjust_allocation_recurse (widget, &scroll_data);
8925 void
8926 pspp_sheet_view_column_update_button (PsppSheetViewColumn *tree_column);
8928 /* Callbacks */
8929 static void
8930 pspp_sheet_view_adjustment_changed (GtkAdjustment *adjustment,
8931 PsppSheetView *tree_view)
8933 if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
8935 GList *list;
8936 gint dy;
8938 gdk_window_move (tree_view->priv->bin_window,
8939 - gtk_adjustment_get_value (tree_view->priv->hadjustment),
8940 TREE_VIEW_HEADER_HEIGHT (tree_view));
8941 gdk_window_move (tree_view->priv->header_window,
8942 - gtk_adjustment_get_value (tree_view->priv->hadjustment),
8944 dy = tree_view->priv->dy - (int) gtk_adjustment_get_value (tree_view->priv->vadjustment);
8945 if (dy)
8947 update_prelight (tree_view,
8948 tree_view->priv->event_last_x,
8949 tree_view->priv->event_last_y - dy);
8951 if (tree_view->priv->edited_column &&
8952 GTK_IS_WIDGET (tree_view->priv->edited_column->editable_widget))
8954 GList *list;
8955 GtkWidget *widget;
8956 PsppSheetViewChild *child = NULL;
8958 widget = GTK_WIDGET (tree_view->priv->edited_column->editable_widget);
8959 adjust_allocation (widget, 0, dy);
8961 for (list = tree_view->priv->children; list; list = list->next)
8963 child = (PsppSheetViewChild *)list->data;
8964 if (child->widget == widget)
8966 child->y += dy;
8967 break;
8972 gdk_window_scroll (tree_view->priv->bin_window, 0, dy);
8974 if (tree_view->priv->dy != (int) gtk_adjustment_get_value (tree_view->priv->vadjustment))
8976 /* update our dy and top_row */
8977 tree_view->priv->dy = (int) gtk_adjustment_get_value (tree_view->priv->vadjustment);
8979 if (!tree_view->priv->in_top_row_to_dy)
8980 pspp_sheet_view_dy_to_top_row (tree_view);
8987 /* Public methods
8991 * pspp_sheet_view_new:
8993 * Creates a new #PsppSheetView widget.
8995 * Return value: A newly created #PsppSheetView widget.
8997 GtkWidget *
8998 pspp_sheet_view_new (void)
9000 return g_object_new (PSPP_TYPE_SHEET_VIEW, NULL);
9004 * pspp_sheet_view_new_with_model:
9005 * @model: the model.
9007 * Creates a new #PsppSheetView widget with the model initialized to @model.
9009 * Return value: A newly created #PsppSheetView widget.
9011 GtkWidget *
9012 pspp_sheet_view_new_with_model (GtkTreeModel *model)
9014 return g_object_new (PSPP_TYPE_SHEET_VIEW, "model", model, NULL);
9017 /* Public Accessors
9021 * pspp_sheet_view_get_model:
9022 * @tree_view: a #PsppSheetView
9024 * Returns the model the #PsppSheetView is based on. Returns %NULL if the
9025 * model is unset.
9027 * Return value: A #GtkTreeModel, or %NULL if none is currently being used.
9029 GtkTreeModel *
9030 pspp_sheet_view_get_model (PsppSheetView *tree_view)
9032 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
9034 return tree_view->priv->model;
9038 * pspp_sheet_view_set_model:
9039 * @tree_view: A #GtkTreeNode.
9040 * @model: (allow-none): The model.
9042 * Sets the model for a #PsppSheetView. If the @tree_view already has a model
9043 * set, it will remove it before setting the new model. If @model is %NULL,
9044 * then it will unset the old model.
9046 void
9047 pspp_sheet_view_set_model (PsppSheetView *tree_view,
9048 GtkTreeModel *model)
9050 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9051 g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model));
9053 if (model == tree_view->priv->model)
9054 return;
9056 if (tree_view->priv->scroll_to_path)
9058 gtk_tree_row_reference_free (tree_view->priv->scroll_to_path);
9059 tree_view->priv->scroll_to_path = NULL;
9062 if (tree_view->priv->model)
9064 GList *tmplist = tree_view->priv->columns;
9066 if (tree_view->priv->selected)
9067 range_tower_set0 (tree_view->priv->selected, 0, ULONG_MAX);
9068 pspp_sheet_view_stop_editing (tree_view, TRUE);
9070 g_signal_handlers_disconnect_by_func (tree_view->priv->model,
9071 pspp_sheet_view_row_changed,
9072 tree_view);
9073 g_signal_handlers_disconnect_by_func (tree_view->priv->model,
9074 pspp_sheet_view_row_inserted,
9075 tree_view);
9076 g_signal_handlers_disconnect_by_func (tree_view->priv->model,
9077 pspp_sheet_view_row_deleted,
9078 tree_view);
9079 g_signal_handlers_disconnect_by_func (tree_view->priv->model,
9080 pspp_sheet_view_rows_reordered,
9081 tree_view);
9083 for (; tmplist; tmplist = tmplist->next)
9084 _pspp_sheet_view_column_unset_model (tmplist->data,
9085 tree_view->priv->model);
9087 tree_view->priv->prelight_node = -1;
9089 gtk_tree_row_reference_free (tree_view->priv->drag_dest_row);
9090 tree_view->priv->drag_dest_row = NULL;
9091 gtk_tree_row_reference_free (tree_view->priv->cursor);
9092 tree_view->priv->cursor = NULL;
9093 gtk_tree_row_reference_free (tree_view->priv->anchor);
9094 tree_view->priv->anchor = NULL;
9095 gtk_tree_row_reference_free (tree_view->priv->top_row);
9096 tree_view->priv->top_row = NULL;
9097 gtk_tree_row_reference_free (tree_view->priv->scroll_to_path);
9098 tree_view->priv->scroll_to_path = NULL;
9100 tree_view->priv->scroll_to_column = NULL;
9102 g_object_unref (tree_view->priv->model);
9104 tree_view->priv->search_column = -1;
9105 tree_view->priv->fixed_height = -1;
9106 tree_view->priv->dy = tree_view->priv->top_row_dy = 0;
9107 tree_view->priv->last_button_x = -1;
9108 tree_view->priv->last_button_y = -1;
9111 tree_view->priv->model = model;
9113 if (tree_view->priv->model)
9115 gint i;
9117 if (tree_view->priv->search_column == -1)
9119 for (i = 0; i < gtk_tree_model_get_n_columns (model); i++)
9121 GType type = gtk_tree_model_get_column_type (model, i);
9123 if (g_value_type_transformable (type, G_TYPE_STRING))
9125 tree_view->priv->search_column = i;
9126 break;
9131 g_object_ref (tree_view->priv->model);
9132 g_signal_connect (tree_view->priv->model,
9133 "row-changed",
9134 G_CALLBACK (pspp_sheet_view_row_changed),
9135 tree_view);
9136 g_signal_connect (tree_view->priv->model,
9137 "row-inserted",
9138 G_CALLBACK (pspp_sheet_view_row_inserted),
9139 tree_view);
9140 g_signal_connect (tree_view->priv->model,
9141 "row-deleted",
9142 G_CALLBACK (pspp_sheet_view_row_deleted),
9143 tree_view);
9144 g_signal_connect (tree_view->priv->model,
9145 "rows-reordered",
9146 G_CALLBACK (pspp_sheet_view_rows_reordered),
9147 tree_view);
9149 tree_view->priv->row_count = gtk_tree_model_iter_n_children (tree_view->priv->model, NULL);
9151 /* FIXME: do I need to do this? pspp_sheet_view_create_buttons (tree_view); */
9152 install_presize_handler (tree_view);
9155 g_object_notify (G_OBJECT (tree_view), "model");
9157 if (tree_view->priv->selection)
9158 _pspp_sheet_selection_emit_changed (tree_view->priv->selection);
9160 if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
9161 gtk_widget_queue_resize (GTK_WIDGET (tree_view));
9165 * pspp_sheet_view_get_selection:
9166 * @tree_view: A #PsppSheetView.
9168 * Gets the #PsppSheetSelection associated with @tree_view.
9170 * Return value: A #PsppSheetSelection object.
9172 PsppSheetSelection *
9173 pspp_sheet_view_get_selection (PsppSheetView *tree_view)
9175 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
9177 return tree_view->priv->selection;
9181 * pspp_sheet_view_get_hadjustment:
9182 * @tree_view: A #PsppSheetView
9184 * Gets the #GtkAdjustment currently being used for the horizontal aspect.
9186 * Return value: A #GtkAdjustment object, or %NULL if none is currently being
9187 * used.
9189 GtkAdjustment *
9190 pspp_sheet_view_get_hadjustment (PsppSheetView *tree_view)
9192 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
9194 return pspp_sheet_view_do_get_hadjustment (tree_view);
9197 static GtkAdjustment *
9198 pspp_sheet_view_do_get_hadjustment (PsppSheetView *tree_view)
9200 return tree_view->priv->hadjustment;
9204 * pspp_sheet_view_set_hadjustment:
9205 * @tree_view: A #PsppSheetView
9206 * @adjustment: (allow-none): The #GtkAdjustment to set, or %NULL
9208 * Sets the #GtkAdjustment for the current horizontal aspect.
9210 void
9211 pspp_sheet_view_set_hadjustment (PsppSheetView *tree_view,
9212 GtkAdjustment *adjustment)
9214 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9216 pspp_sheet_view_set_adjustments (tree_view,
9217 adjustment,
9218 tree_view->priv->vadjustment);
9220 g_object_notify (G_OBJECT (tree_view), "hadjustment");
9223 static void
9224 pspp_sheet_view_do_set_hadjustment (PsppSheetView *tree_view,
9225 GtkAdjustment *adjustment)
9227 PsppSheetViewPrivate *priv = tree_view->priv;
9229 if (adjustment && priv->hadjustment == adjustment)
9230 return;
9232 if (priv->hadjustment != NULL)
9234 g_signal_handlers_disconnect_by_func (priv->hadjustment,
9235 pspp_sheet_view_adjustment_changed,
9236 tree_view);
9237 g_object_unref (priv->hadjustment);
9240 if (adjustment == NULL)
9241 adjustment = gtk_adjustment_new (0.0, 0.0, 0.0,
9242 0.0, 0.0, 0.0);
9244 g_signal_connect (adjustment, "value-changed",
9245 G_CALLBACK (pspp_sheet_view_adjustment_changed), tree_view);
9246 priv->hadjustment = g_object_ref_sink (adjustment);
9247 /* FIXME: Adjustment should probably be populated here with fresh values, but
9248 * internal details are too complicated for me to decipher right now.
9250 pspp_sheet_view_adjustment_changed (NULL, tree_view);
9252 g_object_notify (G_OBJECT (tree_view), "hadjustment");
9256 * pspp_sheet_view_get_vadjustment:
9257 * @tree_view: A #PsppSheetView
9259 * Gets the #GtkAdjustment currently being used for the vertical aspect.
9261 * Return value: (transfer none): A #GtkAdjustment object, or %NULL
9262 * if none is currently being used.
9264 * Deprecated: 3.0: Use gtk_scrollable_get_vadjustment()
9266 GtkAdjustment *
9267 pspp_sheet_view_get_vadjustment (PsppSheetView *tree_view)
9269 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
9271 return pspp_sheet_view_do_get_vadjustment (tree_view);
9274 static GtkAdjustment *
9275 pspp_sheet_view_do_get_vadjustment (PsppSheetView *tree_view)
9277 return tree_view->priv->vadjustment;
9281 * pspp_sheet_view_set_vadjustment:
9282 * @tree_view: A #PsppSheetView
9283 * @adjustment: (allow-none): The #GtkAdjustment to set, or %NULL
9285 * Sets the #GtkAdjustment for the current vertical aspect.
9287 * Deprecated: 3.0: Use gtk_scrollable_set_vadjustment()
9289 void
9290 pspp_sheet_view_set_vadjustment (PsppSheetView *tree_view,
9291 GtkAdjustment *adjustment)
9293 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9294 g_return_if_fail (adjustment == NULL || GTK_IS_ADJUSTMENT (adjustment));
9296 pspp_sheet_view_do_set_vadjustment (tree_view, adjustment);
9299 static void
9300 pspp_sheet_view_do_set_vadjustment (PsppSheetView *tree_view,
9301 GtkAdjustment *adjustment)
9303 PsppSheetViewPrivate *priv = tree_view->priv;
9305 if (adjustment && priv->vadjustment == adjustment)
9306 return;
9308 if (priv->vadjustment != NULL)
9310 g_signal_handlers_disconnect_by_func (priv->vadjustment,
9311 pspp_sheet_view_adjustment_changed,
9312 tree_view);
9313 g_object_unref (priv->vadjustment);
9316 if (adjustment == NULL)
9317 adjustment = gtk_adjustment_new (0.0, 0.0, 0.0,
9318 0.0, 0.0, 0.0);
9320 g_signal_connect (adjustment, "value-changed",
9321 G_CALLBACK (pspp_sheet_view_adjustment_changed), tree_view);
9322 priv->vadjustment = g_object_ref_sink (adjustment);
9323 /* FIXME: Adjustment should probably be populated here with fresh values, but
9324 * internal details are too complicated for me to decipher right now.
9326 pspp_sheet_view_adjustment_changed (NULL, tree_view);
9327 g_object_notify (G_OBJECT (tree_view), "vadjustment");
9330 /* Column and header operations */
9333 * pspp_sheet_view_get_headers_visible:
9334 * @tree_view: A #PsppSheetView.
9336 * Returns %TRUE if the headers on the @tree_view are visible.
9338 * Return value: Whether the headers are visible or not.
9340 gboolean
9341 pspp_sheet_view_get_headers_visible (PsppSheetView *tree_view)
9343 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
9345 return PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_HEADERS_VISIBLE);
9349 * pspp_sheet_view_set_headers_visible:
9350 * @tree_view: A #PsppSheetView.
9351 * @headers_visible: %TRUE if the headers are visible
9353 * Sets the visibility state of the headers.
9355 void
9356 pspp_sheet_view_set_headers_visible (PsppSheetView *tree_view,
9357 gboolean headers_visible)
9359 gint x, y;
9360 GList *list;
9361 PsppSheetViewColumn *column;
9362 GtkAllocation allocation;
9364 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9366 gtk_widget_get_allocation (GTK_WIDGET (tree_view), &allocation);
9368 headers_visible = !! headers_visible;
9370 if (PSPP_SHEET_VIEW_FLAG_SET (tree_view, PSPP_SHEET_VIEW_HEADERS_VISIBLE) == headers_visible)
9371 return;
9373 if (headers_visible)
9374 PSPP_SHEET_VIEW_SET_FLAG (tree_view, PSPP_SHEET_VIEW_HEADERS_VISIBLE);
9375 else
9376 PSPP_SHEET_VIEW_UNSET_FLAG (tree_view, PSPP_SHEET_VIEW_HEADERS_VISIBLE);
9378 if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
9380 gdk_window_get_position (tree_view->priv->bin_window, &x, &y);
9381 if (headers_visible)
9383 gdk_window_move_resize (tree_view->priv->bin_window, x, y + TREE_VIEW_HEADER_HEIGHT (tree_view),
9384 tree_view->priv->width, allocation.height - + TREE_VIEW_HEADER_HEIGHT (tree_view));
9386 if (gtk_widget_get_mapped (GTK_WIDGET (tree_view)))
9387 pspp_sheet_view_map_buttons (tree_view);
9389 else
9391 gdk_window_move_resize (tree_view->priv->bin_window, x, y, tree_view->priv->width, tree_view->priv->height);
9393 for (list = tree_view->priv->columns; list; list = list->next)
9395 column = list->data;
9396 if (column->button)
9397 gtk_widget_unmap (column->button);
9399 gdk_window_hide (tree_view->priv->header_window);
9403 gtk_adjustment_set_page_size (tree_view->priv->vadjustment, allocation.height - TREE_VIEW_HEADER_HEIGHT (tree_view));
9404 gtk_adjustment_set_page_increment (tree_view->priv->vadjustment, (allocation.height - TREE_VIEW_HEADER_HEIGHT (tree_view)) / 2);
9405 gtk_adjustment_set_lower (tree_view->priv->vadjustment, 0);
9406 gtk_adjustment_set_upper (tree_view->priv->vadjustment, tree_view->priv->height);
9407 gtk_adjustment_changed (tree_view->priv->vadjustment);
9409 gtk_widget_queue_resize (GTK_WIDGET (tree_view));
9411 g_object_notify (G_OBJECT (tree_view), "headers-visible");
9415 * pspp_sheet_view_columns_autosize:
9416 * @tree_view: A #PsppSheetView.
9418 * Resizes all columns to their optimal width. Only works after the
9419 * treeview has been realized.
9421 void
9422 pspp_sheet_view_columns_autosize (PsppSheetView *tree_view)
9424 gboolean dirty = FALSE;
9425 GList *list;
9426 PsppSheetViewColumn *column;
9428 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9430 for (list = tree_view->priv->columns; list; list = list->next)
9432 column = list->data;
9433 _pspp_sheet_view_column_cell_set_dirty (column);
9434 dirty = TRUE;
9437 if (dirty)
9438 gtk_widget_queue_resize (GTK_WIDGET (tree_view));
9442 * pspp_sheet_view_set_headers_clickable:
9443 * @tree_view: A #PsppSheetView.
9444 * @setting: %TRUE if the columns are clickable.
9446 * Allow the column title buttons to be clicked.
9448 void
9449 pspp_sheet_view_set_headers_clickable (PsppSheetView *tree_view,
9450 gboolean setting)
9452 GList *list;
9454 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9456 for (list = tree_view->priv->columns; list; list = list->next)
9457 pspp_sheet_view_column_set_clickable (PSPP_SHEET_VIEW_COLUMN (list->data), setting);
9459 g_object_notify (G_OBJECT (tree_view), "headers-clickable");
9464 * pspp_sheet_view_get_headers_clickable:
9465 * @tree_view: A #PsppSheetView.
9467 * Returns whether all header columns are clickable.
9469 * Return value: %TRUE if all header columns are clickable, otherwise %FALSE
9471 * Since: 2.10
9473 gboolean
9474 pspp_sheet_view_get_headers_clickable (PsppSheetView *tree_view)
9476 GList *list;
9478 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
9480 for (list = tree_view->priv->columns; list; list = list->next)
9481 if (!PSPP_SHEET_VIEW_COLUMN (list->data)->clickable)
9482 return FALSE;
9484 return TRUE;
9488 * pspp_sheet_view_set_rules_hint
9489 * @tree_view: a #PsppSheetView
9490 * @setting: %TRUE if the tree requires reading across rows
9492 * This function tells GTK+ that the user interface for your
9493 * application requires users to read across tree rows and associate
9494 * cells with one another. By default, GTK+ will then render the tree
9495 * with alternating row colors. Do <emphasis>not</emphasis> use it
9496 * just because you prefer the appearance of the ruled tree; that's a
9497 * question for the theme. Some themes will draw tree rows in
9498 * alternating colors even when rules are turned off, and users who
9499 * prefer that appearance all the time can choose those themes. You
9500 * should call this function only as a <emphasis>semantic</emphasis>
9501 * hint to the theme engine that your tree makes alternating colors
9502 * useful from a functional standpoint (since it has lots of columns,
9503 * generally).
9506 void
9507 pspp_sheet_view_set_rules_hint (PsppSheetView *tree_view,
9508 gboolean setting)
9510 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9512 setting = setting != FALSE;
9514 if (tree_view->priv->has_rules != setting)
9516 tree_view->priv->has_rules = setting;
9517 gtk_widget_queue_draw (GTK_WIDGET (tree_view));
9520 g_object_notify (G_OBJECT (tree_view), "rules-hint");
9524 * pspp_sheet_view_get_rules_hint
9525 * @tree_view: a #PsppSheetView
9527 * Gets the setting set by pspp_sheet_view_set_rules_hint().
9529 * Return value: %TRUE if rules are useful for the user of this tree
9531 gboolean
9532 pspp_sheet_view_get_rules_hint (PsppSheetView *tree_view)
9534 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
9536 return tree_view->priv->has_rules;
9539 /* Public Column functions
9543 * pspp_sheet_view_append_column:
9544 * @tree_view: A #PsppSheetView.
9545 * @column: The #PsppSheetViewColumn to add.
9547 * Appends @column to the list of columns.
9549 * Return value: The number of columns in @tree_view after appending.
9551 gint
9552 pspp_sheet_view_append_column (PsppSheetView *tree_view,
9553 PsppSheetViewColumn *column)
9555 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), -1);
9556 g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (column), -1);
9557 g_return_val_if_fail (column->tree_view == NULL, -1);
9559 return pspp_sheet_view_insert_column (tree_view, column, -1);
9564 * pspp_sheet_view_remove_column:
9565 * @tree_view: A #PsppSheetView.
9566 * @column: The #PsppSheetViewColumn to remove.
9568 * Removes @column from @tree_view.
9570 * Return value: The number of columns in @tree_view after removing.
9572 gint
9573 pspp_sheet_view_remove_column (PsppSheetView *tree_view,
9574 PsppSheetViewColumn *column)
9576 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), -1);
9577 g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (column), -1);
9578 g_return_val_if_fail (column->tree_view == GTK_WIDGET (tree_view), -1);
9580 if (tree_view->priv->focus_column == column)
9581 tree_view->priv->focus_column = NULL;
9583 if (tree_view->priv->edited_column == column)
9585 pspp_sheet_view_stop_editing (tree_view, TRUE);
9587 /* no need to, but just to be sure ... */
9588 tree_view->priv->edited_column = NULL;
9591 _pspp_sheet_view_column_unset_tree_view (column);
9593 tree_view->priv->columns = g_list_remove (tree_view->priv->columns, column);
9594 tree_view->priv->n_columns--;
9596 if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
9598 GList *list;
9600 _pspp_sheet_view_column_unrealize_button (column);
9601 for (list = tree_view->priv->columns; list; list = list->next)
9603 PsppSheetViewColumn *tmp_column;
9605 tmp_column = PSPP_SHEET_VIEW_COLUMN (list->data);
9606 if (tmp_column->visible)
9607 _pspp_sheet_view_column_cell_set_dirty (tmp_column);
9610 if (tree_view->priv->n_columns == 0 &&
9611 pspp_sheet_view_get_headers_visible (tree_view) &&
9612 tree_view->priv->header_window)
9613 gdk_window_hide (tree_view->priv->header_window);
9615 gtk_widget_queue_resize (GTK_WIDGET (tree_view));
9618 g_object_unref (column);
9619 g_signal_emit (tree_view, tree_view_signals[COLUMNS_CHANGED], 0);
9621 return tree_view->priv->n_columns;
9625 * pspp_sheet_view_insert_column:
9626 * @tree_view: A #PsppSheetView.
9627 * @column: The #PsppSheetViewColumn to be inserted.
9628 * @position: The position to insert @column in.
9630 * This inserts the @column into the @tree_view at @position. If @position is
9631 * -1, then the column is inserted at the end.
9633 * Return value: The number of columns in @tree_view after insertion.
9635 gint
9636 pspp_sheet_view_insert_column (PsppSheetView *tree_view,
9637 PsppSheetViewColumn *column,
9638 gint position)
9640 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), -1);
9641 g_return_val_if_fail (PSPP_IS_SHEET_VIEW_COLUMN (column), -1);
9642 g_return_val_if_fail (column->tree_view == NULL, -1);
9644 g_object_ref_sink (column);
9646 if (tree_view->priv->n_columns == 0 &&
9647 gtk_widget_get_realized (GTK_WIDGET (tree_view)) &&
9648 pspp_sheet_view_get_headers_visible (tree_view))
9650 gdk_window_show (tree_view->priv->header_window);
9653 tree_view->priv->columns = g_list_insert (tree_view->priv->columns,
9654 column, position);
9655 tree_view->priv->n_columns++;
9657 _pspp_sheet_view_column_set_tree_view (column, tree_view);
9659 if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
9661 GList *list;
9663 _pspp_sheet_view_column_realize_button (column);
9665 for (list = tree_view->priv->columns; list; list = list->next)
9667 column = PSPP_SHEET_VIEW_COLUMN (list->data);
9668 if (column->visible)
9669 _pspp_sheet_view_column_cell_set_dirty (column);
9671 gtk_widget_queue_resize (GTK_WIDGET (tree_view));
9674 g_signal_emit (tree_view, tree_view_signals[COLUMNS_CHANGED], 0);
9676 return tree_view->priv->n_columns;
9680 * pspp_sheet_view_insert_column_with_attributes:
9681 * @tree_view: A #PsppSheetView
9682 * @position: The position to insert the new column in.
9683 * @title: The title to set the header to.
9684 * @cell: The #GtkCellRenderer.
9685 * @Varargs: A %NULL-terminated list of attributes.
9687 * Creates a new #PsppSheetViewColumn and inserts it into the @tree_view at
9688 * @position. If @position is -1, then the newly created column is inserted at
9689 * the end. The column is initialized with the attributes given.
9691 * Return value: The number of columns in @tree_view after insertion.
9693 gint
9694 pspp_sheet_view_insert_column_with_attributes (PsppSheetView *tree_view,
9695 gint position,
9696 const gchar *title,
9697 GtkCellRenderer *cell,
9698 ...)
9700 PsppSheetViewColumn *column;
9701 gchar *attribute;
9702 va_list args;
9703 gint column_id;
9705 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), -1);
9707 column = pspp_sheet_view_column_new ();
9708 pspp_sheet_view_column_set_title (column, title);
9709 pspp_sheet_view_column_pack_start (column, cell, TRUE);
9711 va_start (args, cell);
9713 attribute = va_arg (args, gchar *);
9715 while (attribute != NULL)
9717 column_id = va_arg (args, gint);
9718 pspp_sheet_view_column_add_attribute (column, cell, attribute, column_id);
9719 attribute = va_arg (args, gchar *);
9722 va_end (args);
9724 pspp_sheet_view_insert_column (tree_view, column, position);
9726 return tree_view->priv->n_columns;
9730 * pspp_sheet_view_insert_column_with_data_func:
9731 * @tree_view: a #PsppSheetView
9732 * @position: Position to insert, -1 for append
9733 * @title: column title
9734 * @cell: cell renderer for column
9735 * @func: function to set attributes of cell renderer
9736 * @data: data for @func
9737 * @dnotify: destroy notifier for @data
9739 * Convenience function that inserts a new column into the #PsppSheetView
9740 * with the given cell renderer and a #GtkCellDataFunc to set cell renderer
9741 * attributes (normally using data from the model). See also
9742 * pspp_sheet_view_column_set_cell_data_func(), pspp_sheet_view_column_pack_start().
9744 * Return value: number of columns in the tree view post-insert
9746 gint
9747 pspp_sheet_view_insert_column_with_data_func (PsppSheetView *tree_view,
9748 gint position,
9749 const gchar *title,
9750 GtkCellRenderer *cell,
9751 PsppSheetCellDataFunc func,
9752 gpointer data,
9753 GDestroyNotify dnotify)
9755 PsppSheetViewColumn *column;
9757 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), -1);
9759 column = pspp_sheet_view_column_new ();
9760 pspp_sheet_view_column_set_title (column, title);
9761 pspp_sheet_view_column_pack_start (column, cell, TRUE);
9762 pspp_sheet_view_column_set_cell_data_func (column, cell, func, data, dnotify);
9764 pspp_sheet_view_insert_column (tree_view, column, position);
9766 return tree_view->priv->n_columns;
9770 * pspp_sheet_view_get_column:
9771 * @tree_view: A #PsppSheetView.
9772 * @n: The position of the column, counting from 0.
9774 * Gets the #PsppSheetViewColumn at the given position in the #tree_view.
9776 * Return value: The #PsppSheetViewColumn, or %NULL if the position is outside the
9777 * range of columns.
9779 PsppSheetViewColumn *
9780 pspp_sheet_view_get_column (PsppSheetView *tree_view,
9781 gint n)
9783 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
9785 if (n < 0 || n >= tree_view->priv->n_columns)
9786 return NULL;
9788 if (tree_view->priv->columns == NULL)
9789 return NULL;
9791 return PSPP_SHEET_VIEW_COLUMN (g_list_nth (tree_view->priv->columns, n)->data);
9795 * pspp_sheet_view_get_columns:
9796 * @tree_view: A #PsppSheetView
9798 * Returns a #GList of all the #PsppSheetViewColumn s currently in @tree_view.
9799 * The returned list must be freed with g_list_free ().
9801 * Return value: (element-type PsppSheetViewColumn) (transfer container): A list of #PsppSheetViewColumn s
9803 GList *
9804 pspp_sheet_view_get_columns (PsppSheetView *tree_view)
9806 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
9808 return g_list_copy (tree_view->priv->columns);
9812 * pspp_sheet_view_move_column_after:
9813 * @tree_view: A #PsppSheetView
9814 * @column: The #PsppSheetViewColumn to be moved.
9815 * @base_column: (allow-none): The #PsppSheetViewColumn to be moved relative to, or %NULL.
9817 * Moves @column to be after to @base_column. If @base_column is %NULL, then
9818 * @column is placed in the first position.
9820 void
9821 pspp_sheet_view_move_column_after (PsppSheetView *tree_view,
9822 PsppSheetViewColumn *column,
9823 PsppSheetViewColumn *base_column)
9825 GList *column_list_el, *base_el = NULL;
9827 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9829 column_list_el = g_list_find (tree_view->priv->columns, column);
9830 g_return_if_fail (column_list_el != NULL);
9832 if (base_column)
9834 base_el = g_list_find (tree_view->priv->columns, base_column);
9835 g_return_if_fail (base_el != NULL);
9838 if (column_list_el->prev == base_el)
9839 return;
9841 tree_view->priv->columns = g_list_remove_link (tree_view->priv->columns, column_list_el);
9842 if (base_el == NULL)
9844 column_list_el->prev = NULL;
9845 column_list_el->next = tree_view->priv->columns;
9846 if (column_list_el->next)
9847 column_list_el->next->prev = column_list_el;
9848 tree_view->priv->columns = column_list_el;
9850 else
9852 column_list_el->prev = base_el;
9853 column_list_el->next = base_el->next;
9854 if (column_list_el->next)
9855 column_list_el->next->prev = column_list_el;
9856 base_el->next = column_list_el;
9859 if (gtk_widget_get_realized (GTK_WIDGET (tree_view)))
9861 gtk_widget_queue_resize (GTK_WIDGET (tree_view));
9862 pspp_sheet_view_size_allocate_columns (GTK_WIDGET (tree_view), NULL);
9865 g_signal_emit (tree_view, tree_view_signals[COLUMNS_CHANGED], 0);
9869 * pspp_sheet_view_set_column_drag_function:
9870 * @tree_view: A #PsppSheetView.
9871 * @func: (allow-none): A function to determine which columns are reorderable, or %NULL.
9872 * @user_data: (allow-none): User data to be passed to @func, or %NULL
9873 * @destroy: (allow-none): Destroy notifier for @user_data, or %NULL
9875 * Sets a user function for determining where a column may be dropped when
9876 * dragged. This function is called on every column pair in turn at the
9877 * beginning of a column drag to determine where a drop can take place. The
9878 * arguments passed to @func are: the @tree_view, the #PsppSheetViewColumn being
9879 * dragged, the two #PsppSheetViewColumn s determining the drop spot, and
9880 * @user_data. If either of the #PsppSheetViewColumn arguments for the drop spot
9881 * are %NULL, then they indicate an edge. If @func is set to be %NULL, then
9882 * @tree_view reverts to the default behavior of allowing all columns to be
9883 * dropped everywhere.
9885 void
9886 pspp_sheet_view_set_column_drag_function (PsppSheetView *tree_view,
9887 PsppSheetViewColumnDropFunc func,
9888 gpointer user_data,
9889 GDestroyNotify destroy)
9891 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9893 if (tree_view->priv->column_drop_func_data_destroy)
9894 tree_view->priv->column_drop_func_data_destroy (tree_view->priv->column_drop_func_data);
9896 tree_view->priv->column_drop_func = func;
9897 tree_view->priv->column_drop_func_data = user_data;
9898 tree_view->priv->column_drop_func_data_destroy = destroy;
9902 * pspp_sheet_view_scroll_to_point:
9903 * @tree_view: a #PsppSheetView
9904 * @tree_x: X coordinate of new top-left pixel of visible area, or -1
9905 * @tree_y: Y coordinate of new top-left pixel of visible area, or -1
9907 * Scrolls the tree view such that the top-left corner of the visible
9908 * area is @tree_x, @tree_y, where @tree_x and @tree_y are specified
9909 * in tree coordinates. The @tree_view must be realized before
9910 * this function is called. If it isn't, you probably want to be
9911 * using pspp_sheet_view_scroll_to_cell().
9913 * If either @tree_x or @tree_y are -1, then that direction isn't scrolled.
9915 void
9916 pspp_sheet_view_scroll_to_point (PsppSheetView *tree_view,
9917 gint tree_x,
9918 gint tree_y)
9920 GtkAdjustment *hadj;
9921 GtkAdjustment *vadj;
9923 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9924 g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (tree_view)));
9926 hadj = tree_view->priv->hadjustment;
9927 vadj = tree_view->priv->vadjustment;
9929 if (tree_x != -1)
9930 gtk_adjustment_set_value (hadj, CLAMP (tree_x, gtk_adjustment_get_lower (hadj), gtk_adjustment_get_upper (hadj) - gtk_adjustment_get_page_size (hadj)));
9931 if (tree_y != -1)
9932 gtk_adjustment_set_value (vadj, CLAMP (tree_y, gtk_adjustment_get_lower (vadj), gtk_adjustment_get_upper (vadj) - gtk_adjustment_get_page_size (vadj)));
9936 * pspp_sheet_view_scroll_to_cell:
9937 * @tree_view: A #PsppSheetView.
9938 * @path: (allow-none): The path of the row to move to, or %NULL.
9939 * @column: (allow-none): The #PsppSheetViewColumn to move horizontally to, or %NULL.
9940 * @use_align: whether to use alignment arguments, or %FALSE.
9941 * @row_align: The vertical alignment of the row specified by @path.
9942 * @col_align: The horizontal alignment of the column specified by @column.
9944 * Moves the alignments of @tree_view to the position specified by @column and
9945 * @path. If @column is %NULL, then no horizontal scrolling occurs. Likewise,
9946 * if @path is %NULL no vertical scrolling occurs. At a minimum, one of @column
9947 * or @path need to be non-%NULL. @row_align determines where the row is
9948 * placed, and @col_align determines where @column is placed. Both are expected
9949 * to be between 0.0 and 1.0. 0.0 means left/top alignment, 1.0 means
9950 * right/bottom alignment, 0.5 means center.
9952 * If @use_align is %FALSE, then the alignment arguments are ignored, and the
9953 * tree does the minimum amount of work to scroll the cell onto the screen.
9954 * This means that the cell will be scrolled to the edge closest to its current
9955 * position. If the cell is currently visible on the screen, nothing is done.
9957 * This function only works if the model is set, and @path is a valid row on the
9958 * model. If the model changes before the @tree_view is realized, the centered
9959 * path will be modified to reflect this change.
9961 void
9962 pspp_sheet_view_scroll_to_cell (PsppSheetView *tree_view,
9963 GtkTreePath *path,
9964 PsppSheetViewColumn *column,
9965 gboolean use_align,
9966 gfloat row_align,
9967 gfloat col_align)
9969 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
9970 g_return_if_fail (tree_view->priv->model != NULL);
9971 g_return_if_fail (row_align >= 0.0 && row_align <= 1.0);
9972 g_return_if_fail (col_align >= 0.0 && col_align <= 1.0);
9973 g_return_if_fail (path != NULL || column != NULL);
9975 #if 0
9976 g_print ("pspp_sheet_view_scroll_to_cell:\npath: %s\ncolumn: %s\nuse_align: %d\nrow_align: %f\ncol_align: %f\n",
9977 gtk_tree_path_to_string (path), column?"non-null":"null", use_align, row_align, col_align);
9978 #endif
9979 row_align = CLAMP (row_align, 0.0, 1.0);
9980 col_align = CLAMP (col_align, 0.0, 1.0);
9983 /* Note: Despite the benefits that come from having one code path for the
9984 * scrolling code, we short-circuit validate_visible_area's immplementation as
9985 * it is much slower than just going to the point.
9987 if (!gtk_widget_get_visible (GTK_WIDGET (tree_view)) ||
9988 !gtk_widget_get_realized (GTK_WIDGET (tree_view))
9989 /* XXX || GTK_WIDGET_ALLOC_NEEDED (tree_view) */)
9991 if (tree_view->priv->scroll_to_path)
9992 gtk_tree_row_reference_free (tree_view->priv->scroll_to_path);
9994 tree_view->priv->scroll_to_path = NULL;
9995 tree_view->priv->scroll_to_column = NULL;
9997 if (path)
9998 tree_view->priv->scroll_to_path = gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view), tree_view->priv->model, path);
9999 if (column)
10000 tree_view->priv->scroll_to_column = column;
10001 tree_view->priv->scroll_to_use_align = use_align;
10002 tree_view->priv->scroll_to_row_align = row_align;
10003 tree_view->priv->scroll_to_col_align = col_align;
10005 install_presize_handler (tree_view);
10007 else
10009 GdkRectangle cell_rect;
10010 GdkRectangle vis_rect;
10011 gint dest_x, dest_y;
10013 pspp_sheet_view_get_background_area (tree_view, path, column, &cell_rect);
10014 pspp_sheet_view_get_visible_rect (tree_view, &vis_rect);
10016 cell_rect.y = TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, cell_rect.y);
10018 dest_x = vis_rect.x;
10019 dest_y = vis_rect.y;
10021 if (column)
10023 if (use_align)
10025 dest_x = cell_rect.x - ((vis_rect.width - cell_rect.width) * col_align);
10027 else
10029 if (cell_rect.x < vis_rect.x)
10030 dest_x = cell_rect.x;
10031 if (cell_rect.x + cell_rect.width > vis_rect.x + vis_rect.width)
10032 dest_x = cell_rect.x + cell_rect.width - vis_rect.width;
10036 if (path)
10038 if (use_align)
10040 dest_y = cell_rect.y - ((vis_rect.height - cell_rect.height) * row_align);
10041 dest_y = MAX (dest_y, 0);
10043 else
10045 if (cell_rect.y < vis_rect.y)
10046 dest_y = cell_rect.y;
10047 if (cell_rect.y + cell_rect.height > vis_rect.y + vis_rect.height)
10048 dest_y = cell_rect.y + cell_rect.height - vis_rect.height;
10052 pspp_sheet_view_scroll_to_point (tree_view, dest_x, dest_y);
10057 * pspp_sheet_view_row_activated:
10058 * @tree_view: A #PsppSheetView
10059 * @path: The #GtkTreePath to be activated.
10060 * @column: The #PsppSheetViewColumn to be activated.
10062 * Activates the cell determined by @path and @column.
10064 void
10065 pspp_sheet_view_row_activated (PsppSheetView *tree_view,
10066 GtkTreePath *path,
10067 PsppSheetViewColumn *column)
10069 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10071 g_signal_emit (tree_view, tree_view_signals[ROW_ACTIVATED], 0, path, column);
10076 * pspp_sheet_view_get_reorderable:
10077 * @tree_view: a #PsppSheetView
10079 * Retrieves whether the user can reorder the tree via drag-and-drop. See
10080 * pspp_sheet_view_set_reorderable().
10082 * Return value: %TRUE if the tree can be reordered.
10084 gboolean
10085 pspp_sheet_view_get_reorderable (PsppSheetView *tree_view)
10087 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
10089 return tree_view->priv->reorderable;
10093 * pspp_sheet_view_set_reorderable:
10094 * @tree_view: A #PsppSheetView.
10095 * @reorderable: %TRUE, if the tree can be reordered.
10097 * This function is a convenience function to allow you to reorder
10098 * models that support the #GtkDragSourceIface and the
10099 * #GtkDragDestIface. Both #GtkTreeStore and #GtkListStore support
10100 * these. If @reorderable is %TRUE, then the user can reorder the
10101 * model by dragging and dropping rows. The developer can listen to
10102 * these changes by connecting to the model's row_inserted and
10103 * row_deleted signals. The reordering is implemented by setting up
10104 * the tree view as a drag source and destination. Therefore, drag and
10105 * drop can not be used in a reorderable view for any other purpose.
10107 * This function does not give you any degree of control over the order -- any
10108 * reordering is allowed. If more control is needed, you should probably
10109 * handle drag and drop manually.
10111 void
10112 pspp_sheet_view_set_reorderable (PsppSheetView *tree_view,
10113 gboolean reorderable)
10115 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10117 reorderable = reorderable != FALSE;
10119 if (tree_view->priv->reorderable == reorderable)
10120 return;
10122 if (reorderable)
10124 const GtkTargetEntry row_targets[] = {
10125 { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, 0 }
10128 pspp_sheet_view_enable_model_drag_source (tree_view,
10129 GDK_BUTTON1_MASK,
10130 row_targets,
10131 G_N_ELEMENTS (row_targets),
10132 GDK_ACTION_MOVE);
10133 pspp_sheet_view_enable_model_drag_dest (tree_view,
10134 row_targets,
10135 G_N_ELEMENTS (row_targets),
10136 GDK_ACTION_MOVE);
10138 else
10140 pspp_sheet_view_unset_rows_drag_source (tree_view);
10141 pspp_sheet_view_unset_rows_drag_dest (tree_view);
10144 tree_view->priv->reorderable = reorderable;
10146 g_object_notify (G_OBJECT (tree_view), "reorderable");
10149 /* If CLEAR_AND_SELECT is true, then the row will be selected and, unless Shift
10150 is pressed, other rows will be unselected.
10152 If CLAMP_NODE is true, then the sheetview will scroll to make the row
10153 visible. */
10154 static void
10155 pspp_sheet_view_real_set_cursor (PsppSheetView *tree_view,
10156 GtkTreePath *path,
10157 gboolean clear_and_select,
10158 gboolean clamp_node,
10159 PsppSheetSelectMode mode)
10161 int node = -1;
10163 if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
10165 GtkTreePath *cursor_path;
10166 cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
10167 pspp_sheet_view_queue_draw_path (tree_view, cursor_path, NULL);
10168 gtk_tree_path_free (cursor_path);
10171 gtk_tree_row_reference_free (tree_view->priv->cursor);
10172 tree_view->priv->cursor = NULL;
10174 _pspp_sheet_view_find_node (tree_view, path, &node);
10175 tree_view->priv->cursor =
10176 gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view),
10177 tree_view->priv->model,
10178 path);
10180 if (tree_view->priv->row_count > 0)
10182 int new_node = -1;
10184 if (clear_and_select && !(mode & PSPP_SHEET_SELECT_MODE_TOGGLE))
10185 _pspp_sheet_selection_internal_select_node (tree_view->priv->selection,
10186 node, path, mode,
10187 FALSE);
10189 /* We have to re-find tree and node here again, somebody might have
10190 * cleared the node or the whole tree in the PsppSheetSelection::changed
10191 * callback. If the nodes differ we bail out here.
10193 _pspp_sheet_view_find_node (tree_view, path, &new_node);
10195 if (node != new_node)
10196 return;
10198 if (clamp_node)
10200 pspp_sheet_view_clamp_node_visible (tree_view, node);
10201 _pspp_sheet_view_queue_draw_node (tree_view, node, NULL);
10205 g_signal_emit (tree_view, tree_view_signals[CURSOR_CHANGED], 0);
10209 * pspp_sheet_view_get_cursor:
10210 * @tree_view: A #PsppSheetView
10211 * @path: (allow-none): A pointer to be filled with the current cursor path, or %NULL
10212 * @focus_column: (allow-none): A pointer to be filled with the current focus column, or %NULL
10214 * Fills in @path and @focus_column with the current path and focus column. If
10215 * the cursor isn't currently set, then *@path will be %NULL. If no column
10216 * currently has focus, then *@focus_column will be %NULL.
10218 * The returned #GtkTreePath must be freed with gtk_tree_path_free() when
10219 * you are done with it.
10221 void
10222 pspp_sheet_view_get_cursor (PsppSheetView *tree_view,
10223 GtkTreePath **path,
10224 PsppSheetViewColumn **focus_column)
10226 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10228 if (path)
10230 if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
10231 *path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
10232 else
10233 *path = NULL;
10236 if (focus_column)
10238 *focus_column = tree_view->priv->focus_column;
10243 * pspp_sheet_view_set_cursor:
10244 * @tree_view: A #PsppSheetView
10245 * @path: A #GtkTreePath
10246 * @focus_column: (allow-none): A #PsppSheetViewColumn, or %NULL
10247 * @start_editing: %TRUE if the specified cell should start being edited.
10249 * Sets the current keyboard focus to be at @path, and selects it. This is
10250 * useful when you want to focus the user's attention on a particular row. If
10251 * @focus_column is not %NULL, then focus is given to the column specified by
10252 * it. Additionally, if @focus_column is specified, and @start_editing is
10253 * %TRUE, then editing should be started in the specified cell.
10254 * This function is often followed by @gtk_widget_grab_focus (@tree_view)
10255 * in order to give keyboard focus to the widget. Please note that editing
10256 * can only happen when the widget is realized.
10258 * If @path is invalid for @model, the current cursor (if any) will be unset
10259 * and the function will return without failing.
10261 void
10262 pspp_sheet_view_set_cursor (PsppSheetView *tree_view,
10263 GtkTreePath *path,
10264 PsppSheetViewColumn *focus_column,
10265 gboolean start_editing)
10267 pspp_sheet_view_set_cursor_on_cell (tree_view, path, focus_column,
10268 NULL, start_editing);
10272 * pspp_sheet_view_set_cursor_on_cell:
10273 * @tree_view: A #PsppSheetView
10274 * @path: A #GtkTreePath
10275 * @focus_column: (allow-none): A #PsppSheetViewColumn, or %NULL
10276 * @focus_cell: (allow-none): A #GtkCellRenderer, or %NULL
10277 * @start_editing: %TRUE if the specified cell should start being edited.
10279 * Sets the current keyboard focus to be at @path, and selects it. This is
10280 * useful when you want to focus the user's attention on a particular row. If
10281 * @focus_column is not %NULL, then focus is given to the column specified by
10282 * it. If @focus_column and @focus_cell are not %NULL, and @focus_column
10283 * contains 2 or more editable or activatable cells, then focus is given to
10284 * the cell specified by @focus_cell. Additionally, if @focus_column is
10285 * specified, and @start_editing is %TRUE, then editing should be started in
10286 * the specified cell. This function is often followed by
10287 * @gtk_widget_grab_focus (@tree_view) in order to give keyboard focus to the
10288 * widget. Please note that editing can only happen when the widget is
10289 * realized.
10291 * If @path is invalid for @model, the current cursor (if any) will be unset
10292 * and the function will return without failing.
10294 * Since: 2.2
10296 void
10297 pspp_sheet_view_set_cursor_on_cell (PsppSheetView *tree_view,
10298 GtkTreePath *path,
10299 PsppSheetViewColumn *focus_column,
10300 GtkCellRenderer *focus_cell,
10301 gboolean start_editing)
10303 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10304 g_return_if_fail (path != NULL);
10305 g_return_if_fail (focus_column == NULL || PSPP_IS_SHEET_VIEW_COLUMN (focus_column));
10307 if (!tree_view->priv->model)
10308 return;
10310 if (focus_cell)
10312 g_return_if_fail (focus_column);
10313 g_return_if_fail (GTK_IS_CELL_RENDERER (focus_cell));
10316 /* cancel the current editing, if it exists */
10317 if (tree_view->priv->edited_column &&
10318 tree_view->priv->edited_column->editable_widget)
10319 pspp_sheet_view_stop_editing (tree_view, TRUE);
10321 pspp_sheet_view_real_set_cursor (tree_view, path, TRUE, TRUE, 0);
10323 if (focus_column && focus_column->visible)
10325 GList *list;
10326 gboolean column_in_tree = FALSE;
10328 for (list = tree_view->priv->columns; list; list = list->next)
10329 if (list->data == focus_column)
10331 column_in_tree = TRUE;
10332 break;
10334 g_return_if_fail (column_in_tree);
10335 tree_view->priv->focus_column = focus_column;
10336 if (focus_cell)
10337 pspp_sheet_view_column_focus_cell (focus_column, focus_cell);
10338 if (start_editing)
10339 pspp_sheet_view_start_editing (tree_view, path);
10341 pspp_sheet_selection_unselect_all_columns (tree_view->priv->selection);
10342 pspp_sheet_selection_select_column (tree_view->priv->selection, focus_column);
10348 * pspp_sheet_view_get_bin_window:
10349 * @tree_view: A #PsppSheetView
10351 * Returns the window that @tree_view renders to. This is used primarily to
10352 * compare to <literal>event->window</literal> to confirm that the event on
10353 * @tree_view is on the right window.
10355 * Return value: A #GdkWindow, or %NULL when @tree_view hasn't been realized yet
10357 GdkWindow *
10358 pspp_sheet_view_get_bin_window (PsppSheetView *tree_view)
10360 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
10362 return tree_view->priv->bin_window;
10366 * pspp_sheet_view_get_path_at_pos:
10367 * @tree_view: A #PsppSheetView.
10368 * @x: The x position to be identified (relative to bin_window).
10369 * @y: The y position to be identified (relative to bin_window).
10370 * @path: (out) (allow-none): A pointer to a #GtkTreePath pointer to be filled in, or %NULL
10371 * @column: (out) (allow-none): A pointer to a #PsppSheetViewColumn pointer to be filled in, or %NULL
10372 * @cell_x: (out) (allow-none): A pointer where the X coordinate relative to the cell can be placed, or %NULL
10373 * @cell_y: (out) (allow-none): A pointer where the Y coordinate relative to the cell can be placed, or %NULL
10375 * Finds the path at the point (@x, @y), relative to bin_window coordinates
10376 * (please see pspp_sheet_view_get_bin_window()).
10377 * That is, @x and @y are relative to an events coordinates. @x and @y must
10378 * come from an event on the @tree_view only where <literal>event->window ==
10379 * pspp_sheet_view_get_bin_window (<!-- -->)</literal>. It is primarily for
10380 * things like popup menus. If @path is non-%NULL, then it will be filled
10381 * with the #GtkTreePath at that point. This path should be freed with
10382 * gtk_tree_path_free(). If @column is non-%NULL, then it will be filled
10383 * with the column at that point. @cell_x and @cell_y return the coordinates
10384 * relative to the cell background (i.e. the @background_area passed to
10385 * gtk_cell_renderer_render()). This function is only meaningful if
10386 * @tree_view is realized. Therefore this function will always return %FALSE
10387 * if @tree_view is not realized or does not have a model.
10389 * For converting widget coordinates (eg. the ones you get from
10390 * GtkWidget::query-tooltip), please see
10391 * pspp_sheet_view_convert_widget_to_bin_window_coords().
10393 * Return value: %TRUE if a row exists at that coordinate.
10395 gboolean
10396 pspp_sheet_view_get_path_at_pos (PsppSheetView *tree_view,
10397 gint x,
10398 gint y,
10399 GtkTreePath **path,
10400 PsppSheetViewColumn **column,
10401 gint *cell_x,
10402 gint *cell_y)
10404 int node;
10405 gint y_offset;
10407 g_return_val_if_fail (tree_view != NULL, FALSE);
10409 if (path)
10410 *path = NULL;
10411 if (column)
10412 *column = NULL;
10414 if (tree_view->priv->bin_window == NULL)
10415 return FALSE;
10417 if (tree_view->priv->row_count == 0)
10418 return FALSE;
10420 if (x > gtk_adjustment_get_upper (tree_view->priv->hadjustment))
10421 return FALSE;
10423 if (x < 0 || y < 0)
10424 return FALSE;
10426 if (column || cell_x)
10428 PsppSheetViewColumn *tmp_column;
10429 PsppSheetViewColumn *last_column = NULL;
10430 GList *list;
10431 gint remaining_x = x;
10432 gboolean found = FALSE;
10433 gboolean rtl;
10435 rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL);
10436 for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns));
10437 list;
10438 list = (rtl ? list->prev : list->next))
10440 tmp_column = list->data;
10442 if (tmp_column->visible == FALSE)
10443 continue;
10445 last_column = tmp_column;
10446 if (remaining_x <= tmp_column->width)
10448 found = TRUE;
10450 if (column)
10451 *column = tmp_column;
10453 if (cell_x)
10454 *cell_x = remaining_x;
10456 break;
10458 remaining_x -= tmp_column->width;
10461 /* If found is FALSE and there is a last_column, then it the remainder
10462 * space is in that area
10464 if (!found)
10466 if (last_column)
10468 if (column)
10469 *column = last_column;
10471 if (cell_x)
10472 *cell_x = last_column->width + remaining_x;
10474 else
10476 return FALSE;
10481 y_offset = pspp_sheet_view_find_offset (tree_view,
10482 TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, y),
10483 &node);
10485 if (node < 0)
10486 return FALSE;
10488 if (cell_y)
10489 *cell_y = y_offset;
10491 if (path)
10492 *path = _pspp_sheet_view_find_path (tree_view, node);
10494 return TRUE;
10497 /* Computes 'cell_area' from 'background_area', which must be the background
10498 area for a cell. Set 'subtract_focus_rect' to TRUE to compute the cell area
10499 as passed to a GtkCellRenderer's "render" function, or to FALSE to compute
10500 the cell area as passed to _pspp_sheet_view_column_cell_render().
10502 'column' is required to properly adjust 'cell_area->x' and
10503 'cell_area->width'. It may be set to NULL if these values are not of
10504 interest. In this case 'cell_area->x' and 'cell_area->width' will be
10505 returned as 0. */
10506 static void
10507 pspp_sheet_view_adjust_cell_area (PsppSheetView *tree_view,
10508 PsppSheetViewColumn *column,
10509 const GdkRectangle *background_area,
10510 gboolean subtract_focus_rect,
10511 GdkRectangle *cell_area)
10513 gint vertical_separator;
10514 gint horizontal_separator;
10516 *cell_area = *background_area;
10518 gtk_widget_style_get (GTK_WIDGET (tree_view),
10519 "vertical-separator", &vertical_separator,
10520 "horizontal-separator", &horizontal_separator,
10521 NULL);
10522 cell_area->x += horizontal_separator / 2;
10523 cell_area->y += vertical_separator / 2;
10524 cell_area->width -= horizontal_separator;
10525 cell_area->height -= vertical_separator;
10527 if (subtract_focus_rect)
10529 int focus_line_width;
10531 gtk_widget_style_get (GTK_WIDGET (tree_view),
10532 "focus-line-width", &focus_line_width,
10533 NULL);
10534 cell_area->x += focus_line_width;
10535 cell_area->y += focus_line_width;
10536 cell_area->width -= 2 * focus_line_width;
10537 cell_area->height -= 2 * focus_line_width;
10540 if (tree_view->priv->grid_lines != PSPP_SHEET_VIEW_GRID_LINES_NONE)
10542 gint grid_line_width;
10543 gtk_widget_style_get (GTK_WIDGET (tree_view),
10544 "grid-line-width", &grid_line_width,
10545 NULL);
10547 if ((tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_VERTICAL
10548 || tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_BOTH)
10549 && column != NULL)
10551 PsppSheetViewColumn *first_column, *last_column;
10552 GList *list;
10554 /* Find the last visible column. */
10555 last_column = NULL;
10556 for (list = g_list_last (tree_view->priv->columns);
10557 list;
10558 list = list->prev)
10560 PsppSheetViewColumn *c = list->data;
10561 if (c->visible)
10563 last_column = c;
10564 break;
10568 /* Find the first visible column. */
10569 first_column = NULL;
10570 for (list = g_list_first (tree_view->priv->columns);
10571 list;
10572 list = list->next)
10574 PsppSheetViewColumn *c = list->data;
10575 if (c->visible)
10577 first_column = c;
10578 break;
10582 if (column == first_column)
10584 cell_area->width -= grid_line_width / 2;
10586 else if (column == last_column)
10588 cell_area->x += grid_line_width / 2;
10589 cell_area->width -= grid_line_width / 2;
10591 else
10593 cell_area->x += grid_line_width / 2;
10594 cell_area->width -= grid_line_width;
10598 if (tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_HORIZONTAL
10599 || tree_view->priv->grid_lines == PSPP_SHEET_VIEW_GRID_LINES_BOTH)
10601 cell_area->y += grid_line_width / 2;
10602 cell_area->height -= grid_line_width;
10606 if (column == NULL)
10608 cell_area->x = 0;
10609 cell_area->width = 0;
10614 * pspp_sheet_view_get_cell_area:
10615 * @tree_view: a #PsppSheetView
10616 * @path: (allow-none): a #GtkTreePath for the row, or %NULL to get only horizontal coordinates
10617 * @column: (allow-none): a #PsppSheetViewColumn for the column, or %NULL to get only vertical coordinates
10618 * @rect: rectangle to fill with cell rect
10620 * Fills the bounding rectangle in bin_window coordinates for the cell at the
10621 * row specified by @path and the column specified by @column. If @path is
10622 * %NULL, or points to a path not currently displayed, the @y and @height fields
10623 * of the rectangle will be filled with 0. If @column is %NULL, the @x and @width
10624 * fields will be filled with 0. The sum of all cell rects does not cover the
10625 * entire tree; there are extra pixels in between rows, for example. The
10626 * returned rectangle is equivalent to the @cell_area passed to
10627 * gtk_cell_renderer_render(). This function is only valid if @tree_view is
10628 * realized.
10630 void
10631 pspp_sheet_view_get_cell_area (PsppSheetView *tree_view,
10632 GtkTreePath *path,
10633 PsppSheetViewColumn *column,
10634 GdkRectangle *rect)
10636 GdkRectangle background_area;
10638 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10639 g_return_if_fail (column == NULL || PSPP_IS_SHEET_VIEW_COLUMN (column));
10640 g_return_if_fail (rect != NULL);
10641 g_return_if_fail (!column || column->tree_view == (GtkWidget *) tree_view);
10642 g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (tree_view)));
10644 pspp_sheet_view_get_background_area (tree_view, path, column,
10645 &background_area);
10646 pspp_sheet_view_adjust_cell_area (tree_view, column, &background_area,
10647 FALSE, rect);
10651 * pspp_sheet_view_get_background_area:
10652 * @tree_view: a #PsppSheetView
10653 * @path: (allow-none): a #GtkTreePath for the row, or %NULL to get only horizontal coordinates
10654 * @column: (allow-none): a #PsppSheetViewColumn for the column, or %NULL to get only vertical coordiantes
10655 * @rect: rectangle to fill with cell background rect
10657 * Fills the bounding rectangle in bin_window coordinates for the cell at the
10658 * row specified by @path and the column specified by @column. If @path is
10659 * %NULL, or points to a node not found in the tree, the @y and @height fields of
10660 * the rectangle will be filled with 0. If @column is %NULL, the @x and @width
10661 * fields will be filled with 0. The returned rectangle is equivalent to the
10662 * @background_area passed to gtk_cell_renderer_render(). These background
10663 * areas tile to cover the entire bin window. Contrast with the @cell_area,
10664 * returned by pspp_sheet_view_get_cell_area(), which returns only the cell
10665 * itself, excluding surrounding borders.
10668 void
10669 pspp_sheet_view_get_background_area (PsppSheetView *tree_view,
10670 GtkTreePath *path,
10671 PsppSheetViewColumn *column,
10672 GdkRectangle *rect)
10674 int node = -1;
10676 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10677 g_return_if_fail (column == NULL || PSPP_IS_SHEET_VIEW_COLUMN (column));
10678 g_return_if_fail (rect != NULL);
10680 rect->x = 0;
10681 rect->y = 0;
10682 rect->width = 0;
10683 rect->height = 0;
10685 if (path)
10687 /* Get vertical coords */
10689 _pspp_sheet_view_find_node (tree_view, path, &node);
10690 if (node < 0)
10691 return;
10693 rect->y = BACKGROUND_FIRST_PIXEL (tree_view, node);
10695 rect->height = ROW_HEIGHT (tree_view);
10698 if (column)
10700 gint x2 = 0;
10702 pspp_sheet_view_get_background_xrange (tree_view, column, &rect->x, &x2);
10703 rect->width = x2 - rect->x;
10708 * pspp_sheet_view_get_visible_rect:
10709 * @tree_view: a #PsppSheetView
10710 * @visible_rect: rectangle to fill
10712 * Fills @visible_rect with the currently-visible region of the
10713 * buffer, in tree coordinates. Convert to bin_window coordinates with
10714 * pspp_sheet_view_convert_tree_to_bin_window_coords().
10715 * Tree coordinates start at 0,0 for row 0 of the tree, and cover the entire
10716 * scrollable area of the tree.
10718 void
10719 pspp_sheet_view_get_visible_rect (PsppSheetView *tree_view,
10720 GdkRectangle *visible_rect)
10722 GtkWidget *widget;
10724 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10726 widget = GTK_WIDGET (tree_view);
10728 if (visible_rect)
10730 GtkAllocation allocation;
10731 gtk_widget_get_allocation (widget, &allocation);
10732 visible_rect->x = gtk_adjustment_get_value (tree_view->priv->hadjustment);
10733 visible_rect->y = gtk_adjustment_get_value (tree_view->priv->vadjustment);
10734 visible_rect->width = allocation.width;
10735 visible_rect->height = allocation.height - TREE_VIEW_HEADER_HEIGHT (tree_view);
10740 * pspp_sheet_view_widget_to_tree_coords:
10741 * @tree_view: a #PsppSheetView
10742 * @wx: X coordinate relative to bin_window
10743 * @wy: Y coordinate relative to bin_window
10744 * @tx: return location for tree X coordinate
10745 * @ty: return location for tree Y coordinate
10747 * Converts bin_window coordinates to coordinates for the
10748 * tree (the full scrollable area of the tree).
10750 * Deprecated: 2.12: Due to historial reasons the name of this function is
10751 * incorrect. For converting coordinates relative to the widget to
10752 * bin_window coordinates, please see
10753 * pspp_sheet_view_convert_widget_to_bin_window_coords().
10756 void
10757 pspp_sheet_view_widget_to_tree_coords (PsppSheetView *tree_view,
10758 gint wx,
10759 gint wy,
10760 gint *tx,
10761 gint *ty)
10763 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10765 if (tx)
10766 *tx = wx + gtk_adjustment_get_value (tree_view->priv->hadjustment);
10767 if (ty)
10768 *ty = wy + tree_view->priv->dy;
10772 * pspp_sheet_view_tree_to_widget_coords:
10773 * @tree_view: a #PsppSheetView
10774 * @tx: tree X coordinate
10775 * @ty: tree Y coordinate
10776 * @wx: return location for X coordinate relative to bin_window
10777 * @wy: return location for Y coordinate relative to bin_window
10779 * Converts tree coordinates (coordinates in full scrollable area of the tree)
10780 * to bin_window coordinates.
10782 * Deprecated: 2.12: Due to historial reasons the name of this function is
10783 * incorrect. For converting bin_window coordinates to coordinates relative
10784 * to bin_window, please see
10785 * pspp_sheet_view_convert_bin_window_to_widget_coords().
10788 void
10789 pspp_sheet_view_tree_to_widget_coords (PsppSheetView *tree_view,
10790 gint tx,
10791 gint ty,
10792 gint *wx,
10793 gint *wy)
10795 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10797 if (wx)
10798 *wx = tx - gtk_adjustment_get_value (tree_view->priv->hadjustment);
10799 if (wy)
10800 *wy = ty - tree_view->priv->dy;
10805 * pspp_sheet_view_convert_widget_to_tree_coords:
10806 * @tree_view: a #PsppSheetView
10807 * @wx: X coordinate relative to the widget
10808 * @wy: Y coordinate relative to the widget
10809 * @tx: return location for tree X coordinate
10810 * @ty: return location for tree Y coordinate
10812 * Converts widget coordinates to coordinates for the
10813 * tree (the full scrollable area of the tree).
10815 * Since: 2.12
10817 void
10818 pspp_sheet_view_convert_widget_to_tree_coords (PsppSheetView *tree_view,
10819 gint wx,
10820 gint wy,
10821 gint *tx,
10822 gint *ty)
10824 gint x, y;
10826 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10828 pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view,
10829 wx, wy,
10830 &x, &y);
10831 pspp_sheet_view_convert_bin_window_to_tree_coords (tree_view,
10832 x, y,
10833 tx, ty);
10837 * pspp_sheet_view_convert_tree_to_widget_coords:
10838 * @tree_view: a #PsppSheetView
10839 * @tx: X coordinate relative to the tree
10840 * @ty: Y coordinate relative to the tree
10841 * @wx: return location for widget X coordinate
10842 * @wy: return location for widget Y coordinate
10844 * Converts tree coordinates (coordinates in full scrollable area of the tree)
10845 * to widget coordinates.
10847 * Since: 2.12
10849 void
10850 pspp_sheet_view_convert_tree_to_widget_coords (PsppSheetView *tree_view,
10851 gint tx,
10852 gint ty,
10853 gint *wx,
10854 gint *wy)
10856 gint x, y;
10858 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10860 pspp_sheet_view_convert_tree_to_bin_window_coords (tree_view,
10861 tx, ty,
10862 &x, &y);
10863 pspp_sheet_view_convert_bin_window_to_widget_coords (tree_view,
10864 x, y,
10865 wx, wy);
10869 * pspp_sheet_view_convert_widget_to_bin_window_coords:
10870 * @tree_view: a #PsppSheetView
10871 * @wx: X coordinate relative to the widget
10872 * @wy: Y coordinate relative to the widget
10873 * @bx: return location for bin_window X coordinate
10874 * @by: return location for bin_window Y coordinate
10876 * Converts widget coordinates to coordinates for the bin_window
10877 * (see pspp_sheet_view_get_bin_window()).
10879 * Since: 2.12
10881 void
10882 pspp_sheet_view_convert_widget_to_bin_window_coords (PsppSheetView *tree_view,
10883 gint wx,
10884 gint wy,
10885 gint *bx,
10886 gint *by)
10888 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10890 if (bx)
10891 *bx = wx + gtk_adjustment_get_value (tree_view->priv->hadjustment);
10892 if (by)
10893 *by = wy - TREE_VIEW_HEADER_HEIGHT (tree_view);
10897 * pspp_sheet_view_convert_bin_window_to_widget_coords:
10898 * @tree_view: a #PsppSheetView
10899 * @bx: bin_window X coordinate
10900 * @by: bin_window Y coordinate
10901 * @wx: return location for widget X coordinate
10902 * @wy: return location for widget Y coordinate
10904 * Converts bin_window coordinates (see pspp_sheet_view_get_bin_window())
10905 * to widget relative coordinates.
10907 * Since: 2.12
10909 void
10910 pspp_sheet_view_convert_bin_window_to_widget_coords (PsppSheetView *tree_view,
10911 gint bx,
10912 gint by,
10913 gint *wx,
10914 gint *wy)
10916 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10918 if (wx)
10919 *wx = bx - gtk_adjustment_get_value (tree_view->priv->hadjustment);
10920 if (wy)
10921 *wy = by + TREE_VIEW_HEADER_HEIGHT (tree_view);
10925 * pspp_sheet_view_convert_tree_to_bin_window_coords:
10926 * @tree_view: a #PsppSheetView
10927 * @tx: tree X coordinate
10928 * @ty: tree Y coordinate
10929 * @bx: return location for X coordinate relative to bin_window
10930 * @by: return location for Y coordinate relative to bin_window
10932 * Converts tree coordinates (coordinates in full scrollable area of the tree)
10933 * to bin_window coordinates.
10935 * Since: 2.12
10937 void
10938 pspp_sheet_view_convert_tree_to_bin_window_coords (PsppSheetView *tree_view,
10939 gint tx,
10940 gint ty,
10941 gint *bx,
10942 gint *by)
10944 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10946 if (bx)
10947 *bx = tx;
10948 if (by)
10949 *by = ty - tree_view->priv->dy;
10953 * pspp_sheet_view_convert_bin_window_to_tree_coords:
10954 * @tree_view: a #PsppSheetView
10955 * @bx: X coordinate relative to bin_window
10956 * @by: Y coordinate relative to bin_window
10957 * @tx: return location for tree X coordinate
10958 * @ty: return location for tree Y coordinate
10960 * Converts bin_window coordinates to coordinates for the
10961 * tree (the full scrollable area of the tree).
10963 * Since: 2.12
10965 void
10966 pspp_sheet_view_convert_bin_window_to_tree_coords (PsppSheetView *tree_view,
10967 gint bx,
10968 gint by,
10969 gint *tx,
10970 gint *ty)
10972 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
10974 if (tx)
10975 *tx = bx;
10976 if (ty)
10977 *ty = by + tree_view->priv->dy;
10983 * pspp_sheet_view_get_visible_range:
10984 * @tree_view: A #PsppSheetView
10985 * @start_path: (allow-none): Return location for start of region, or %NULL.
10986 * @end_path: (allow-none): Return location for end of region, or %NULL.
10988 * Sets @start_path and @end_path to be the first and last visible path.
10989 * Note that there may be invisible paths in between.
10991 * The paths should be freed with gtk_tree_path_free() after use.
10993 * Returns: %TRUE, if valid paths were placed in @start_path and @end_path.
10995 * Since: 2.8
10997 gboolean
10998 pspp_sheet_view_get_visible_range (PsppSheetView *tree_view,
10999 GtkTreePath **start_path,
11000 GtkTreePath **end_path)
11002 int node;
11003 gboolean retval;
11005 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
11007 if (!tree_view->priv->row_count)
11008 return FALSE;
11010 retval = TRUE;
11012 if (start_path)
11014 pspp_sheet_view_find_offset (tree_view,
11015 TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, 0),
11016 &node);
11017 if (node >= 0)
11018 *start_path = _pspp_sheet_view_find_path (tree_view, node);
11019 else
11020 retval = FALSE;
11023 if (end_path)
11025 gint y;
11027 if (tree_view->priv->height < gtk_adjustment_get_page_size (tree_view->priv->vadjustment))
11028 y = tree_view->priv->height - 1;
11029 else
11030 y = TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, gtk_adjustment_get_page_size (tree_view->priv->vadjustment)) - 1;
11032 pspp_sheet_view_find_offset (tree_view, y, &node);
11033 if (node >= 0)
11034 *end_path = _pspp_sheet_view_find_path (tree_view, node);
11035 else
11036 retval = FALSE;
11039 return retval;
11042 static void
11043 unset_reorderable (PsppSheetView *tree_view)
11045 if (tree_view->priv->reorderable)
11047 tree_view->priv->reorderable = FALSE;
11048 g_object_notify (G_OBJECT (tree_view), "reorderable");
11053 * pspp_sheet_view_enable_model_drag_source:
11054 * @tree_view: a #PsppSheetView
11055 * @start_button_mask: Mask of allowed buttons to start drag
11056 * @targets: the table of targets that the drag will support
11057 * @n_targets: the number of items in @targets
11058 * @actions: the bitmask of possible actions for a drag from this
11059 * widget
11061 * Turns @tree_view into a drag source for automatic DND. Calling this
11062 * method sets #PsppSheetView:reorderable to %FALSE.
11064 void
11065 pspp_sheet_view_enable_model_drag_source (PsppSheetView *tree_view,
11066 GdkModifierType start_button_mask,
11067 const GtkTargetEntry *targets,
11068 gint n_targets,
11069 GdkDragAction actions)
11071 TreeViewDragInfo *di;
11073 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11075 gtk_drag_source_set (GTK_WIDGET (tree_view),
11077 targets,
11078 n_targets,
11079 actions);
11081 di = ensure_info (tree_view);
11083 di->start_button_mask = start_button_mask;
11084 di->source_actions = actions;
11085 di->source_set = TRUE;
11087 unset_reorderable (tree_view);
11091 * pspp_sheet_view_enable_model_drag_dest:
11092 * @tree_view: a #PsppSheetView
11093 * @targets: the table of targets that the drag will support
11094 * @n_targets: the number of items in @targets
11095 * @actions: the bitmask of possible actions for a drag from this
11096 * widget
11098 * Turns @tree_view into a drop destination for automatic DND. Calling
11099 * this method sets #PsppSheetView:reorderable to %FALSE.
11101 void
11102 pspp_sheet_view_enable_model_drag_dest (PsppSheetView *tree_view,
11103 const GtkTargetEntry *targets,
11104 gint n_targets,
11105 GdkDragAction actions)
11107 TreeViewDragInfo *di;
11109 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11111 gtk_drag_dest_set (GTK_WIDGET (tree_view),
11113 targets,
11114 n_targets,
11115 actions);
11117 di = ensure_info (tree_view);
11118 di->dest_set = TRUE;
11120 unset_reorderable (tree_view);
11124 * pspp_sheet_view_unset_rows_drag_source:
11125 * @tree_view: a #PsppSheetView
11127 * Undoes the effect of
11128 * pspp_sheet_view_enable_model_drag_source(). Calling this method sets
11129 * #PsppSheetView:reorderable to %FALSE.
11131 void
11132 pspp_sheet_view_unset_rows_drag_source (PsppSheetView *tree_view)
11134 TreeViewDragInfo *di;
11136 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11138 di = get_info (tree_view);
11140 if (di)
11142 if (di->source_set)
11144 gtk_drag_source_unset (GTK_WIDGET (tree_view));
11145 di->source_set = FALSE;
11148 if (!di->dest_set && !di->source_set)
11149 remove_info (tree_view);
11152 unset_reorderable (tree_view);
11156 * pspp_sheet_view_unset_rows_drag_dest:
11157 * @tree_view: a #PsppSheetView
11159 * Undoes the effect of
11160 * pspp_sheet_view_enable_model_drag_dest(). Calling this method sets
11161 * #PsppSheetView:reorderable to %FALSE.
11163 void
11164 pspp_sheet_view_unset_rows_drag_dest (PsppSheetView *tree_view)
11166 TreeViewDragInfo *di;
11168 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11170 di = get_info (tree_view);
11172 if (di)
11174 if (di->dest_set)
11176 gtk_drag_dest_unset (GTK_WIDGET (tree_view));
11177 di->dest_set = FALSE;
11180 if (!di->dest_set && !di->source_set)
11181 remove_info (tree_view);
11184 unset_reorderable (tree_view);
11188 * pspp_sheet_view_set_drag_dest_row:
11189 * @tree_view: a #PsppSheetView
11190 * @path: (allow-none): The path of the row to highlight, or %NULL.
11191 * @pos: Specifies whether to drop before, after or into the row
11193 * Sets the row that is highlighted for feedback.
11195 void
11196 pspp_sheet_view_set_drag_dest_row (PsppSheetView *tree_view,
11197 GtkTreePath *path,
11198 PsppSheetViewDropPosition pos)
11200 GtkTreePath *current_dest;
11202 /* Note; this function is exported to allow a custom DND
11203 * implementation, so it can't touch TreeViewDragInfo
11206 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11208 current_dest = NULL;
11210 if (tree_view->priv->drag_dest_row)
11212 current_dest = gtk_tree_row_reference_get_path (tree_view->priv->drag_dest_row);
11213 gtk_tree_row_reference_free (tree_view->priv->drag_dest_row);
11216 /* special case a drop on an empty model */
11217 tree_view->priv->empty_view_drop = 0;
11219 if (pos == PSPP_SHEET_VIEW_DROP_BEFORE && path
11220 && gtk_tree_path_get_depth (path) == 1
11221 && gtk_tree_path_get_indices (path)[0] == 0)
11223 gint n_children;
11225 n_children = gtk_tree_model_iter_n_children (tree_view->priv->model,
11226 NULL);
11228 if (!n_children)
11229 tree_view->priv->empty_view_drop = 1;
11232 tree_view->priv->drag_dest_pos = pos;
11234 if (path)
11236 tree_view->priv->drag_dest_row =
11237 gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view), tree_view->priv->model, path);
11238 pspp_sheet_view_queue_draw_path (tree_view, path, NULL);
11240 else
11241 tree_view->priv->drag_dest_row = NULL;
11243 if (current_dest)
11245 int node, new_node;
11247 _pspp_sheet_view_find_node (tree_view, current_dest, &node);
11248 _pspp_sheet_view_queue_draw_node (tree_view, node, NULL);
11250 if (node >= 0)
11252 new_node = pspp_sheet_view_node_next (tree_view, node);
11253 if (new_node >= 0)
11254 _pspp_sheet_view_queue_draw_node (tree_view, new_node, NULL);
11256 new_node = pspp_sheet_view_node_prev (tree_view, node);
11257 if (new_node >= 0)
11258 _pspp_sheet_view_queue_draw_node (tree_view, new_node, NULL);
11260 gtk_tree_path_free (current_dest);
11265 * pspp_sheet_view_get_drag_dest_row:
11266 * @tree_view: a #PsppSheetView
11267 * @path: (allow-none): Return location for the path of the highlighted row, or %NULL.
11268 * @pos: (allow-none): Return location for the drop position, or %NULL
11270 * Gets information about the row that is highlighted for feedback.
11272 void
11273 pspp_sheet_view_get_drag_dest_row (PsppSheetView *tree_view,
11274 GtkTreePath **path,
11275 PsppSheetViewDropPosition *pos)
11277 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11279 if (path)
11281 if (tree_view->priv->drag_dest_row)
11282 *path = gtk_tree_row_reference_get_path (tree_view->priv->drag_dest_row);
11283 else
11285 if (tree_view->priv->empty_view_drop)
11286 *path = gtk_tree_path_new_from_indices (0, -1);
11287 else
11288 *path = NULL;
11292 if (pos)
11293 *pos = tree_view->priv->drag_dest_pos;
11297 * pspp_sheet_view_get_dest_row_at_pos:
11298 * @tree_view: a #PsppSheetView
11299 * @drag_x: the position to determine the destination row for
11300 * @drag_y: the position to determine the destination row for
11301 * @path: (allow-none): Return location for the path of the highlighted row, or %NULL.
11302 * @pos: (allow-none): Return location for the drop position, or %NULL
11304 * Determines the destination row for a given position. @drag_x and
11305 * @drag_y are expected to be in widget coordinates. This function is only
11306 * meaningful if @tree_view is realized. Therefore this function will always
11307 * return %FALSE if @tree_view is not realized or does not have a model.
11309 * Return value: whether there is a row at the given position, %TRUE if this
11310 * is indeed the case.
11312 gboolean
11313 pspp_sheet_view_get_dest_row_at_pos (PsppSheetView *tree_view,
11314 gint drag_x,
11315 gint drag_y,
11316 GtkTreePath **path,
11317 PsppSheetViewDropPosition *pos)
11319 gint cell_y;
11320 gint bin_x, bin_y;
11321 gdouble offset_into_row;
11322 gdouble third;
11323 GdkRectangle cell;
11324 PsppSheetViewColumn *column = NULL;
11325 GtkTreePath *tmp_path = NULL;
11327 /* Note; this function is exported to allow a custom DND
11328 * implementation, so it can't touch TreeViewDragInfo
11331 g_return_val_if_fail (tree_view != NULL, FALSE);
11332 g_return_val_if_fail (drag_x >= 0, FALSE);
11333 g_return_val_if_fail (drag_y >= 0, FALSE);
11335 if (path)
11336 *path = NULL;
11338 if (tree_view->priv->bin_window == NULL)
11339 return FALSE;
11341 if (tree_view->priv->row_count == 0)
11342 return FALSE;
11344 /* If in the top third of a row, we drop before that row; if
11345 * in the bottom third, drop after that row; if in the middle,
11346 * and the row has children, drop into the row.
11348 pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view, drag_x, drag_y,
11349 &bin_x, &bin_y);
11351 if (!pspp_sheet_view_get_path_at_pos (tree_view,
11352 bin_x,
11353 bin_y,
11354 &tmp_path,
11355 &column,
11356 NULL,
11357 &cell_y))
11358 return FALSE;
11360 pspp_sheet_view_get_background_area (tree_view, tmp_path, column,
11361 &cell);
11363 offset_into_row = cell_y;
11365 if (path)
11366 *path = tmp_path;
11367 else
11368 gtk_tree_path_free (tmp_path);
11370 tmp_path = NULL;
11372 third = cell.height / 3.0;
11374 if (pos)
11376 if (offset_into_row < third)
11378 *pos = PSPP_SHEET_VIEW_DROP_BEFORE;
11380 else if (offset_into_row < (cell.height / 2.0))
11382 *pos = PSPP_SHEET_VIEW_DROP_INTO_OR_BEFORE;
11384 else if (offset_into_row < third * 2.0)
11386 *pos = PSPP_SHEET_VIEW_DROP_INTO_OR_AFTER;
11388 else
11390 *pos = PSPP_SHEET_VIEW_DROP_AFTER;
11394 return TRUE;
11398 #if GTK3_TRANSITION
11399 /* KEEP IN SYNC WITH PSPP_SHEET_VIEW_BIN_EXPOSE */
11401 * pspp_sheet_view_create_row_drag_icon:
11402 * @tree_view: a #PsppSheetView
11403 * @path: a #GtkTreePath in @tree_view
11405 * Creates a #GdkPixmap representation of the row at @path.
11406 * This image is used for a drag icon.
11408 * Return value: a newly-allocated pixmap of the drag icon.
11410 GdkPixmap *
11411 pspp_sheet_view_create_row_drag_icon (PsppSheetView *tree_view,
11412 GtkTreePath *path)
11414 GtkTreeIter iter;
11415 int node;
11416 gint cell_offset;
11417 GList *list;
11418 GdkRectangle background_area;
11419 GdkRectangle expose_area;
11420 GtkWidget *widget;
11421 /* start drawing inside the black outline */
11422 gint x = 1, y = 1;
11423 GdkDrawable *drawable;
11424 gint bin_window_width;
11425 gboolean rtl;
11427 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
11428 g_return_val_if_fail (path != NULL, NULL);
11430 widget = GTK_WIDGET (tree_view);
11432 if (!gtk_widget_get_realized (widget))
11433 return NULL;
11435 _pspp_sheet_view_find_node (tree_view,
11436 path,
11437 &node);
11439 if (node < 0)
11440 return NULL;
11442 if (!gtk_tree_model_get_iter (tree_view->priv->model,
11443 &iter,
11444 path))
11445 return NULL;
11447 cell_offset = x;
11449 background_area.y = y;
11450 background_area.height = ROW_HEIGHT (tree_view);
11452 bin_window_width = gdk_window_get_width (tree_view->priv->bin_window);
11454 drawable = gdk_pixmap_new (tree_view->priv->bin_window,
11455 bin_window_width + 2,
11456 background_area.height + 2,
11457 -1);
11459 expose_area.x = 0;
11460 expose_area.y = 0;
11461 expose_area.width = bin_window_width + 2;
11462 expose_area.height = background_area.height + 2;
11464 #if GTK3_TRANSITION
11465 gdk_draw_rectangle (drawable,
11466 widget->style->base_gc [gtk_widget_get_state (widget)],
11467 TRUE,
11468 0, 0,
11469 bin_window_width + 2,
11470 background_area.height + 2);
11471 #endif
11473 rtl = gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL;
11475 for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns));
11476 list;
11477 list = (rtl ? list->prev : list->next))
11479 PsppSheetViewColumn *column = list->data;
11480 GdkRectangle cell_area;
11481 gint vertical_separator;
11483 if (!column->visible)
11484 continue;
11486 pspp_sheet_view_column_cell_set_cell_data (column, tree_view->priv->model, &iter);
11488 background_area.x = cell_offset;
11489 background_area.width = column->width;
11491 gtk_widget_style_get (widget,
11492 "vertical-separator", &vertical_separator,
11493 NULL);
11495 cell_area = background_area;
11497 cell_area.y += vertical_separator / 2;
11498 cell_area.height -= vertical_separator;
11500 if (pspp_sheet_view_column_cell_is_visible (column))
11501 _pspp_sheet_view_column_cell_render (column,
11502 drawable,
11503 &background_area,
11504 &cell_area,
11505 &expose_area,
11507 cell_offset += column->width;
11510 #if GTK3_TRANSITION
11511 gdk_draw_rectangle (drawable,
11512 widget->style->black_gc,
11513 FALSE,
11514 0, 0,
11515 bin_window_width + 1,
11516 background_area.height + 1);
11517 #endif
11519 return drawable;
11521 #endif
11524 * pspp_sheet_view_set_destroy_count_func:
11525 * @tree_view: A #PsppSheetView
11526 * @func: (allow-none): Function to be called when a view row is destroyed, or %NULL
11527 * @data: (allow-none): User data to be passed to @func, or %NULL
11528 * @destroy: (allow-none): Destroy notifier for @data, or %NULL
11530 * This function should almost never be used. It is meant for private use by
11531 * ATK for determining the number of visible children that are removed when a row is deleted.
11533 void
11534 pspp_sheet_view_set_destroy_count_func (PsppSheetView *tree_view,
11535 PsppSheetDestroyCountFunc func,
11536 gpointer data,
11537 GDestroyNotify destroy)
11539 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11541 if (tree_view->priv->destroy_count_destroy)
11542 tree_view->priv->destroy_count_destroy (tree_view->priv->destroy_count_data);
11544 tree_view->priv->destroy_count_func = func;
11545 tree_view->priv->destroy_count_data = data;
11546 tree_view->priv->destroy_count_destroy = destroy;
11551 * Interactive search
11555 * pspp_sheet_view_set_enable_search:
11556 * @tree_view: A #PsppSheetView
11557 * @enable_search: %TRUE, if the user can search interactively
11559 * If @enable_search is set, then the user can type in text to search through
11560 * the tree interactively (this is sometimes called "typeahead find").
11562 * Note that even if this is %FALSE, the user can still initiate a search
11563 * using the "start-interactive-search" key binding.
11565 void
11566 pspp_sheet_view_set_enable_search (PsppSheetView *tree_view,
11567 gboolean enable_search)
11569 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11571 enable_search = !!enable_search;
11573 if (tree_view->priv->enable_search != enable_search)
11575 tree_view->priv->enable_search = enable_search;
11576 g_object_notify (G_OBJECT (tree_view), "enable-search");
11581 * pspp_sheet_view_get_enable_search:
11582 * @tree_view: A #PsppSheetView
11584 * Returns whether or not the tree allows to start interactive searching
11585 * by typing in text.
11587 * Return value: whether or not to let the user search interactively
11589 gboolean
11590 pspp_sheet_view_get_enable_search (PsppSheetView *tree_view)
11592 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
11594 return tree_view->priv->enable_search;
11599 * pspp_sheet_view_get_search_column:
11600 * @tree_view: A #PsppSheetView
11602 * Gets the column searched on by the interactive search code.
11604 * Return value: the column the interactive search code searches in.
11606 gint
11607 pspp_sheet_view_get_search_column (PsppSheetView *tree_view)
11609 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), -1);
11611 return (tree_view->priv->search_column);
11615 * pspp_sheet_view_set_search_column:
11616 * @tree_view: A #PsppSheetView
11617 * @column: the column of the model to search in, or -1 to disable searching
11619 * Sets @column as the column where the interactive search code should
11620 * search in for the current model.
11622 * If the search column is set, users can use the "start-interactive-search"
11623 * key binding to bring up search popup. The enable-search property controls
11624 * whether simply typing text will also start an interactive search.
11626 * Note that @column refers to a column of the current model. The search
11627 * column is reset to -1 when the model is changed.
11629 void
11630 pspp_sheet_view_set_search_column (PsppSheetView *tree_view,
11631 gint column)
11633 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11634 g_return_if_fail (column >= -1);
11636 if (tree_view->priv->search_column == column)
11637 return;
11639 tree_view->priv->search_column = column;
11640 g_object_notify (G_OBJECT (tree_view), "search-column");
11644 * pspp_sheet_view_get_search_equal_func:
11645 * @tree_view: A #PsppSheetView
11647 * Returns the compare function currently in use.
11649 * Return value: the currently used compare function for the search code.
11652 PsppSheetViewSearchEqualFunc
11653 pspp_sheet_view_get_search_equal_func (PsppSheetView *tree_view)
11655 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
11657 return tree_view->priv->search_equal_func;
11661 * pspp_sheet_view_set_search_equal_func:
11662 * @tree_view: A #PsppSheetView
11663 * @search_equal_func: the compare function to use during the search
11664 * @search_user_data: (allow-none): user data to pass to @search_equal_func, or %NULL
11665 * @search_destroy: (allow-none): Destroy notifier for @search_user_data, or %NULL
11667 * Sets the compare function for the interactive search capabilities; note
11668 * that somewhat like strcmp() returning 0 for equality
11669 * #PsppSheetViewSearchEqualFunc returns %FALSE on matches.
11671 void
11672 pspp_sheet_view_set_search_equal_func (PsppSheetView *tree_view,
11673 PsppSheetViewSearchEqualFunc search_equal_func,
11674 gpointer search_user_data,
11675 GDestroyNotify search_destroy)
11677 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11678 g_return_if_fail (search_equal_func != NULL);
11680 if (tree_view->priv->search_destroy)
11681 tree_view->priv->search_destroy (tree_view->priv->search_user_data);
11683 tree_view->priv->search_equal_func = search_equal_func;
11684 tree_view->priv->search_user_data = search_user_data;
11685 tree_view->priv->search_destroy = search_destroy;
11686 if (tree_view->priv->search_equal_func == NULL)
11687 tree_view->priv->search_equal_func = pspp_sheet_view_search_equal_func;
11691 * pspp_sheet_view_get_search_entry:
11692 * @tree_view: A #PsppSheetView
11694 * Returns the #GtkEntry which is currently in use as interactive search
11695 * entry for @tree_view. In case the built-in entry is being used, %NULL
11696 * will be returned.
11698 * Return value: the entry currently in use as search entry.
11700 * Since: 2.10
11702 GtkEntry *
11703 pspp_sheet_view_get_search_entry (PsppSheetView *tree_view)
11705 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
11707 if (tree_view->priv->search_custom_entry_set)
11708 return GTK_ENTRY (tree_view->priv->search_entry);
11710 return NULL;
11714 * pspp_sheet_view_set_search_entry:
11715 * @tree_view: A #PsppSheetView
11716 * @entry: (allow-none): the entry the interactive search code of @tree_view should use or %NULL
11718 * Sets the entry which the interactive search code will use for this
11719 * @tree_view. This is useful when you want to provide a search entry
11720 * in our interface at all time at a fixed position. Passing %NULL for
11721 * @entry will make the interactive search code use the built-in popup
11722 * entry again.
11724 * Since: 2.10
11726 void
11727 pspp_sheet_view_set_search_entry (PsppSheetView *tree_view,
11728 GtkEntry *entry)
11730 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11731 g_return_if_fail (entry == NULL || GTK_IS_ENTRY (entry));
11733 if (tree_view->priv->search_custom_entry_set)
11735 if (tree_view->priv->search_entry_changed_id)
11737 g_signal_handler_disconnect (tree_view->priv->search_entry,
11738 tree_view->priv->search_entry_changed_id);
11739 tree_view->priv->search_entry_changed_id = 0;
11741 g_signal_handlers_disconnect_by_func (tree_view->priv->search_entry,
11742 G_CALLBACK (pspp_sheet_view_search_key_press_event),
11743 tree_view);
11745 g_object_unref (tree_view->priv->search_entry);
11747 else if (tree_view->priv->search_window)
11749 gtk_widget_destroy (tree_view->priv->search_window);
11751 tree_view->priv->search_window = NULL;
11754 if (entry)
11756 tree_view->priv->search_entry = g_object_ref (entry);
11757 tree_view->priv->search_custom_entry_set = TRUE;
11759 if (tree_view->priv->search_entry_changed_id == 0)
11761 tree_view->priv->search_entry_changed_id =
11762 g_signal_connect (tree_view->priv->search_entry, "changed",
11763 G_CALLBACK (pspp_sheet_view_search_init),
11764 tree_view);
11767 g_signal_connect (tree_view->priv->search_entry, "key-press-event",
11768 G_CALLBACK (pspp_sheet_view_search_key_press_event),
11769 tree_view);
11771 pspp_sheet_view_search_init (tree_view->priv->search_entry, tree_view);
11773 else
11775 tree_view->priv->search_entry = NULL;
11776 tree_view->priv->search_custom_entry_set = FALSE;
11781 * pspp_sheet_view_set_search_position_func:
11782 * @tree_view: A #PsppSheetView
11783 * @func: (allow-none): the function to use to position the search dialog, or %NULL
11784 * to use the default search position function
11785 * @data: (allow-none): user data to pass to @func, or %NULL
11786 * @destroy: (allow-none): Destroy notifier for @data, or %NULL
11788 * Sets the function to use when positioning the search dialog.
11790 * Since: 2.10
11792 void
11793 pspp_sheet_view_set_search_position_func (PsppSheetView *tree_view,
11794 PsppSheetViewSearchPositionFunc func,
11795 gpointer user_data,
11796 GDestroyNotify destroy)
11798 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
11800 if (tree_view->priv->search_position_destroy)
11801 tree_view->priv->search_position_destroy (tree_view->priv->search_position_user_data);
11803 tree_view->priv->search_position_func = func;
11804 tree_view->priv->search_position_user_data = user_data;
11805 tree_view->priv->search_position_destroy = destroy;
11806 if (tree_view->priv->search_position_func == NULL)
11807 tree_view->priv->search_position_func = pspp_sheet_view_search_position_func;
11811 * pspp_sheet_view_get_search_position_func:
11812 * @tree_view: A #PsppSheetView
11814 * Returns the positioning function currently in use.
11816 * Return value: the currently used function for positioning the search dialog.
11818 * Since: 2.10
11820 PsppSheetViewSearchPositionFunc
11821 pspp_sheet_view_get_search_position_func (PsppSheetView *tree_view)
11823 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), NULL);
11825 return tree_view->priv->search_position_func;
11829 static void
11830 pspp_sheet_view_search_dialog_hide (GtkWidget *search_dialog,
11831 PsppSheetView *tree_view)
11833 if (tree_view->priv->disable_popdown)
11834 return;
11836 if (tree_view->priv->search_entry_changed_id)
11838 g_signal_handler_disconnect (tree_view->priv->search_entry,
11839 tree_view->priv->search_entry_changed_id);
11840 tree_view->priv->search_entry_changed_id = 0;
11842 if (tree_view->priv->typeselect_flush_timeout)
11844 g_source_remove (tree_view->priv->typeselect_flush_timeout);
11845 tree_view->priv->typeselect_flush_timeout = 0;
11848 if (gtk_widget_get_visible (search_dialog))
11850 /* send focus-in event */
11851 send_focus_change (GTK_WIDGET (tree_view->priv->search_entry), FALSE);
11852 gtk_widget_hide (search_dialog);
11853 gtk_entry_set_text (GTK_ENTRY (tree_view->priv->search_entry), "");
11854 send_focus_change (GTK_WIDGET (tree_view), TRUE);
11858 static void
11859 pspp_sheet_view_search_position_func (PsppSheetView *tree_view,
11860 GtkWidget *search_dialog,
11861 gpointer user_data)
11863 gint x, y;
11864 gint tree_x, tree_y;
11865 gint tree_width, tree_height;
11866 GdkWindow *tree_window = gtk_widget_get_window (GTK_WIDGET (tree_view));
11867 GdkScreen *screen = gdk_window_get_screen (tree_window);
11868 GtkRequisition requisition;
11869 gint monitor_num;
11870 GdkRectangle monitor;
11872 monitor_num = gdk_screen_get_monitor_at_window (screen, tree_window);
11873 gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
11875 gtk_widget_realize (search_dialog);
11877 gdk_window_get_origin (tree_window, &tree_x, &tree_y);
11878 tree_width = gdk_window_get_width (tree_window);
11879 tree_height = gdk_window_get_height (tree_window);
11881 gtk_widget_size_request (search_dialog, &requisition);
11883 if (tree_x + tree_width > gdk_screen_get_width (screen))
11884 x = gdk_screen_get_width (screen) - requisition.width;
11885 else if (tree_x + tree_width - requisition.width < 0)
11886 x = 0;
11887 else
11888 x = tree_x + tree_width - requisition.width;
11890 if (tree_y + tree_height + requisition.height > gdk_screen_get_height (screen))
11891 y = gdk_screen_get_height (screen) - requisition.height;
11892 else if (tree_y + tree_height < 0) /* isn't really possible ... */
11893 y = 0;
11894 else
11895 y = tree_y + tree_height;
11897 gtk_window_move (GTK_WINDOW (search_dialog), x, y);
11900 static void
11901 pspp_sheet_view_search_disable_popdown (GtkEntry *entry,
11902 GtkMenu *menu,
11903 gpointer data)
11905 PsppSheetView *tree_view = (PsppSheetView *)data;
11907 tree_view->priv->disable_popdown = 1;
11908 g_signal_connect (menu, "hide",
11909 G_CALLBACK (pspp_sheet_view_search_enable_popdown), data);
11912 #if GTK3_TRANSITION
11913 /* Because we're visible but offscreen, we just set a flag in the preedit
11914 * callback.
11916 static void
11917 pspp_sheet_view_search_preedit_changed (GtkIMContext *im_context,
11918 PsppSheetView *tree_view)
11920 tree_view->priv->imcontext_changed = 1;
11921 if (tree_view->priv->typeselect_flush_timeout)
11923 g_source_remove (tree_view->priv->typeselect_flush_timeout);
11924 tree_view->priv->typeselect_flush_timeout =
11925 gdk_threads_add_timeout (PSPP_SHEET_VIEW_SEARCH_DIALOG_TIMEOUT,
11926 (GSourceFunc) pspp_sheet_view_search_entry_flush_timeout,
11927 tree_view);
11931 #endif
11933 static void
11934 pspp_sheet_view_search_activate (GtkEntry *entry,
11935 PsppSheetView *tree_view)
11937 GtkTreePath *path;
11938 int node;
11940 pspp_sheet_view_search_dialog_hide (tree_view->priv->search_window,
11941 tree_view);
11943 /* If we have a row selected and it's the cursor row, we activate
11944 * the row XXX */
11945 if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
11947 path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
11949 _pspp_sheet_view_find_node (tree_view, path, &node);
11951 if (node >= 0 && pspp_sheet_view_node_is_selected (tree_view, node))
11952 pspp_sheet_view_row_activated (tree_view, path, tree_view->priv->focus_column);
11954 gtk_tree_path_free (path);
11958 static gboolean
11959 pspp_sheet_view_real_search_enable_popdown (gpointer data)
11961 PsppSheetView *tree_view = (PsppSheetView *)data;
11963 tree_view->priv->disable_popdown = 0;
11965 return FALSE;
11968 static void
11969 pspp_sheet_view_search_enable_popdown (GtkWidget *widget,
11970 gpointer data)
11972 gdk_threads_add_timeout_full (G_PRIORITY_HIGH, 200, pspp_sheet_view_real_search_enable_popdown, g_object_ref (data), g_object_unref);
11975 static gboolean
11976 pspp_sheet_view_search_delete_event (GtkWidget *widget,
11977 GdkEventAny *event,
11978 PsppSheetView *tree_view)
11980 g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
11982 pspp_sheet_view_search_dialog_hide (widget, tree_view);
11984 return TRUE;
11987 static gboolean
11988 pspp_sheet_view_search_button_press_event (GtkWidget *widget,
11989 GdkEventButton *event,
11990 PsppSheetView *tree_view)
11992 g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
11994 pspp_sheet_view_search_dialog_hide (widget, tree_view);
11996 if (event->window == tree_view->priv->bin_window)
11997 pspp_sheet_view_button_press (GTK_WIDGET (tree_view), event);
11999 return TRUE;
12002 static gboolean
12003 pspp_sheet_view_search_scroll_event (GtkWidget *widget,
12004 GdkEventScroll *event,
12005 PsppSheetView *tree_view)
12007 gboolean retval = FALSE;
12009 if (event->direction == GDK_SCROLL_UP)
12011 pspp_sheet_view_search_move (widget, tree_view, TRUE);
12012 retval = TRUE;
12014 else if (event->direction == GDK_SCROLL_DOWN)
12016 pspp_sheet_view_search_move (widget, tree_view, FALSE);
12017 retval = TRUE;
12020 /* renew the flush timeout */
12021 if (retval && tree_view->priv->typeselect_flush_timeout
12022 && !tree_view->priv->search_custom_entry_set)
12024 g_source_remove (tree_view->priv->typeselect_flush_timeout);
12025 tree_view->priv->typeselect_flush_timeout =
12026 gdk_threads_add_timeout (PSPP_SHEET_VIEW_SEARCH_DIALOG_TIMEOUT,
12027 (GSourceFunc) pspp_sheet_view_search_entry_flush_timeout,
12028 tree_view);
12031 return retval;
12034 static gboolean
12035 pspp_sheet_view_search_key_press_event (GtkWidget *widget,
12036 GdkEventKey *event,
12037 PsppSheetView *tree_view)
12039 gboolean retval = FALSE;
12041 g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
12042 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
12044 /* close window and cancel the search */
12045 if (!tree_view->priv->search_custom_entry_set
12046 && (event->keyval == GDK_Escape ||
12047 event->keyval == GDK_Tab ||
12048 event->keyval == GDK_KP_Tab ||
12049 event->keyval == GDK_ISO_Left_Tab))
12051 pspp_sheet_view_search_dialog_hide (widget, tree_view);
12052 return TRUE;
12055 /* select previous matching iter */
12056 if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
12058 if (!pspp_sheet_view_search_move (widget, tree_view, TRUE))
12059 gtk_widget_error_bell (widget);
12061 retval = TRUE;
12064 if (((event->state & (GTK_DEFAULT_ACCEL_MOD_MASK | GDK_SHIFT_MASK)) == (GTK_DEFAULT_ACCEL_MOD_MASK | GDK_SHIFT_MASK))
12065 && (event->keyval == GDK_g || event->keyval == GDK_G))
12067 if (!pspp_sheet_view_search_move (widget, tree_view, TRUE))
12068 gtk_widget_error_bell (widget);
12070 retval = TRUE;
12073 /* select next matching iter */
12074 if (event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
12076 if (!pspp_sheet_view_search_move (widget, tree_view, FALSE))
12077 gtk_widget_error_bell (widget);
12079 retval = TRUE;
12082 if (((event->state & (GTK_DEFAULT_ACCEL_MOD_MASK | GDK_SHIFT_MASK)) == GTK_DEFAULT_ACCEL_MOD_MASK)
12083 && (event->keyval == GDK_g || event->keyval == GDK_G))
12085 if (!pspp_sheet_view_search_move (widget, tree_view, FALSE))
12086 gtk_widget_error_bell (widget);
12088 retval = TRUE;
12091 /* renew the flush timeout */
12092 if (retval && tree_view->priv->typeselect_flush_timeout
12093 && !tree_view->priv->search_custom_entry_set)
12095 g_source_remove (tree_view->priv->typeselect_flush_timeout);
12096 tree_view->priv->typeselect_flush_timeout =
12097 gdk_threads_add_timeout (PSPP_SHEET_VIEW_SEARCH_DIALOG_TIMEOUT,
12098 (GSourceFunc) pspp_sheet_view_search_entry_flush_timeout,
12099 tree_view);
12102 return retval;
12105 /* this function returns FALSE if there is a search string but
12106 * nothing was found, and TRUE otherwise.
12108 static gboolean
12109 pspp_sheet_view_search_move (GtkWidget *window,
12110 PsppSheetView *tree_view,
12111 gboolean up)
12113 gboolean ret;
12114 gint len;
12115 gint count = 0;
12116 const gchar *text;
12117 GtkTreeIter iter;
12118 GtkTreeModel *model;
12119 PsppSheetSelection *selection;
12121 text = gtk_entry_get_text (GTK_ENTRY (tree_view->priv->search_entry));
12123 g_return_val_if_fail (text != NULL, FALSE);
12125 len = strlen (text);
12127 if (up && tree_view->priv->selected_iter == 1)
12128 return strlen (text) < 1;
12130 len = strlen (text);
12132 if (len < 1)
12133 return TRUE;
12135 model = pspp_sheet_view_get_model (tree_view);
12136 selection = pspp_sheet_view_get_selection (tree_view);
12138 /* search */
12139 pspp_sheet_selection_unselect_all (selection);
12140 if (!gtk_tree_model_get_iter_first (model, &iter))
12141 return TRUE;
12143 ret = pspp_sheet_view_search_iter (model, selection, &iter, text,
12144 &count, up?((tree_view->priv->selected_iter) - 1):((tree_view->priv->selected_iter + 1)));
12146 if (ret)
12148 /* found */
12149 tree_view->priv->selected_iter += up?(-1):(1);
12150 return TRUE;
12152 else
12154 /* return to old iter */
12155 count = 0;
12156 gtk_tree_model_get_iter_first (model, &iter);
12157 pspp_sheet_view_search_iter (model, selection,
12158 &iter, text,
12159 &count, tree_view->priv->selected_iter);
12160 return FALSE;
12164 static gboolean
12165 pspp_sheet_view_search_equal_func (GtkTreeModel *model,
12166 gint column,
12167 const gchar *key,
12168 GtkTreeIter *iter,
12169 gpointer search_data)
12171 gboolean retval = TRUE;
12172 const gchar *str;
12173 gchar *normalized_string;
12174 gchar *normalized_key;
12175 gchar *case_normalized_string = NULL;
12176 gchar *case_normalized_key = NULL;
12177 GValue value = {0,};
12178 GValue transformed = {0,};
12180 gtk_tree_model_get_value (model, iter, column, &value);
12182 g_value_init (&transformed, G_TYPE_STRING);
12184 if (!g_value_transform (&value, &transformed))
12186 g_value_unset (&value);
12187 return TRUE;
12190 g_value_unset (&value);
12192 str = g_value_get_string (&transformed);
12193 if (!str)
12195 g_value_unset (&transformed);
12196 return TRUE;
12199 normalized_string = g_utf8_normalize (str, -1, G_NORMALIZE_ALL);
12200 normalized_key = g_utf8_normalize (key, -1, G_NORMALIZE_ALL);
12202 if (normalized_string && normalized_key)
12204 case_normalized_string = g_utf8_casefold (normalized_string, -1);
12205 case_normalized_key = g_utf8_casefold (normalized_key, -1);
12207 if (strncmp (case_normalized_key, case_normalized_string, strlen (case_normalized_key)) == 0)
12208 retval = FALSE;
12211 g_value_unset (&transformed);
12212 g_free (normalized_key);
12213 g_free (normalized_string);
12214 g_free (case_normalized_key);
12215 g_free (case_normalized_string);
12217 return retval;
12220 static gboolean
12221 pspp_sheet_view_search_iter (GtkTreeModel *model,
12222 PsppSheetSelection *selection,
12223 GtkTreeIter *iter,
12224 const gchar *text,
12225 gint *count,
12226 gint n)
12228 int node = -1;
12229 GtkTreePath *path;
12231 PsppSheetView *tree_view = pspp_sheet_selection_get_tree_view (selection);
12233 path = gtk_tree_model_get_path (model, iter);
12234 _pspp_sheet_view_find_node (tree_view, path, &node);
12238 gboolean done = FALSE;
12240 if (! tree_view->priv->search_equal_func (model, tree_view->priv->search_column, text, iter, tree_view->priv->search_user_data))
12242 (*count)++;
12243 if (*count == n)
12245 pspp_sheet_view_scroll_to_cell (tree_view, path, NULL,
12246 TRUE, 0.5, 0.0);
12247 pspp_sheet_selection_select_iter (selection, iter);
12248 pspp_sheet_view_real_set_cursor (tree_view, path, FALSE, TRUE, 0);
12250 if (path)
12251 gtk_tree_path_free (path);
12253 return TRUE;
12260 node = pspp_sheet_view_node_next (tree_view, node);
12262 if (node >= 0)
12264 gboolean has_next;
12266 has_next = gtk_tree_model_iter_next (model, iter);
12268 done = TRUE;
12269 gtk_tree_path_next (path);
12271 /* sanity check */
12272 TREE_VIEW_INTERNAL_ASSERT (has_next, FALSE);
12274 else
12276 if (path)
12277 gtk_tree_path_free (path);
12279 /* we've run out of tree, done with this func */
12280 return FALSE;
12283 while (!done);
12285 while (1);
12287 return FALSE;
12290 static void
12291 pspp_sheet_view_search_init (GtkWidget *entry,
12292 PsppSheetView *tree_view)
12294 gint ret;
12295 gint count = 0;
12296 const gchar *text;
12297 GtkTreeIter iter;
12298 GtkTreeModel *model;
12299 PsppSheetSelection *selection;
12301 g_return_if_fail (GTK_IS_ENTRY (entry));
12302 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
12304 text = gtk_entry_get_text (GTK_ENTRY (entry));
12306 model = pspp_sheet_view_get_model (tree_view);
12307 selection = pspp_sheet_view_get_selection (tree_view);
12309 /* search */
12310 pspp_sheet_selection_unselect_all (selection);
12311 if (tree_view->priv->typeselect_flush_timeout
12312 && !tree_view->priv->search_custom_entry_set)
12314 g_source_remove (tree_view->priv->typeselect_flush_timeout);
12315 tree_view->priv->typeselect_flush_timeout =
12316 gdk_threads_add_timeout (PSPP_SHEET_VIEW_SEARCH_DIALOG_TIMEOUT,
12317 (GSourceFunc) pspp_sheet_view_search_entry_flush_timeout,
12318 tree_view);
12321 if (*text == '\0')
12322 return;
12324 if (!gtk_tree_model_get_iter_first (model, &iter))
12325 return;
12327 ret = pspp_sheet_view_search_iter (model, selection,
12328 &iter, text,
12329 &count, 1);
12331 if (ret)
12332 tree_view->priv->selected_iter = 1;
12335 static void
12336 pspp_sheet_view_remove_widget (GtkCellEditable *cell_editable,
12337 PsppSheetView *tree_view)
12339 if (tree_view->priv->edited_column == NULL)
12340 return;
12342 _pspp_sheet_view_column_stop_editing (tree_view->priv->edited_column);
12343 tree_view->priv->edited_column = NULL;
12345 if (gtk_widget_has_focus (GTK_WIDGET (cell_editable)))
12346 gtk_widget_grab_focus (GTK_WIDGET (tree_view));
12348 g_signal_handlers_disconnect_by_func (cell_editable,
12349 pspp_sheet_view_remove_widget,
12350 tree_view);
12351 g_signal_handlers_disconnect_by_func (cell_editable,
12352 pspp_sheet_view_editable_button_press_event,
12353 tree_view);
12354 g_signal_handlers_disconnect_by_func (cell_editable,
12355 pspp_sheet_view_editable_clicked,
12356 tree_view);
12358 gtk_container_remove (GTK_CONTAINER (tree_view),
12359 GTK_WIDGET (cell_editable));
12361 /* FIXME should only redraw a single node */
12362 gtk_widget_queue_draw (GTK_WIDGET (tree_view));
12365 static gboolean
12366 pspp_sheet_view_start_editing (PsppSheetView *tree_view,
12367 GtkTreePath *cursor_path)
12369 GtkTreeIter iter;
12370 GdkRectangle background_area;
12371 GdkRectangle cell_area;
12372 GtkCellEditable *editable_widget = NULL;
12373 gchar *path_string;
12374 guint flags = 0; /* can be 0, as the flags are primarily for rendering */
12375 gint retval = FALSE;
12376 int cursor_node;
12378 g_assert (tree_view->priv->focus_column);
12380 if (!gtk_widget_get_realized (GTK_WIDGET (tree_view)))
12381 return FALSE;
12383 _pspp_sheet_view_find_node (tree_view, cursor_path, &cursor_node);
12384 if (cursor_node < 0)
12385 return FALSE;
12387 path_string = gtk_tree_path_to_string (cursor_path);
12388 gtk_tree_model_get_iter (tree_view->priv->model, &iter, cursor_path);
12390 pspp_sheet_view_column_cell_set_cell_data (tree_view->priv->focus_column,
12391 tree_view->priv->model,
12392 &iter);
12393 pspp_sheet_view_get_background_area (tree_view,
12394 cursor_path,
12395 tree_view->priv->focus_column,
12396 &background_area);
12397 pspp_sheet_view_get_cell_area (tree_view,
12398 cursor_path,
12399 tree_view->priv->focus_column,
12400 &cell_area);
12402 if (_pspp_sheet_view_column_cell_event (tree_view->priv->focus_column,
12403 &editable_widget,
12404 NULL,
12405 path_string,
12406 &background_area,
12407 &cell_area,
12408 flags))
12410 retval = TRUE;
12411 if (editable_widget != NULL)
12413 gint left, right;
12414 GdkRectangle area;
12415 GtkCellRenderer *cell;
12417 area = cell_area;
12418 cell = _pspp_sheet_view_column_get_edited_cell (tree_view->priv->focus_column);
12420 _pspp_sheet_view_column_get_neighbor_sizes (tree_view->priv->focus_column, cell, &left, &right);
12422 area.x += left;
12423 area.width -= right + left;
12425 pspp_sheet_view_real_start_editing (tree_view,
12426 tree_view->priv->focus_column,
12427 cursor_path,
12428 editable_widget,
12429 &area,
12430 NULL,
12431 flags);
12435 g_free (path_string);
12436 return retval;
12439 static gboolean
12440 pspp_sheet_view_editable_button_press_event (GtkWidget *widget,
12441 GdkEventButton *event,
12442 PsppSheetView *sheet_view)
12444 gint node;
12446 node = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget),
12447 "pspp-sheet-view-node"));
12448 return pspp_sheet_view_row_head_clicked (sheet_view,
12449 node,
12450 sheet_view->priv->edited_column,
12451 event);
12454 static void
12455 pspp_sheet_view_editable_clicked (GtkButton *button,
12456 PsppSheetView *sheet_view)
12458 pspp_sheet_view_editable_button_press_event (GTK_WIDGET (button), NULL,
12459 sheet_view);
12462 static gboolean
12463 is_all_selected (GtkWidget *widget)
12465 GtkEntryBuffer *buffer;
12466 gint start_pos, end_pos;
12468 if (!GTK_IS_ENTRY (widget))
12469 return FALSE;
12471 buffer = gtk_entry_get_buffer (GTK_ENTRY (widget));
12472 return (gtk_editable_get_selection_bounds (GTK_EDITABLE (widget),
12473 &start_pos, &end_pos)
12474 && start_pos == 0
12475 && end_pos == gtk_entry_buffer_get_length (buffer));
12478 static gboolean
12479 is_at_left (GtkWidget *widget)
12481 return (GTK_IS_ENTRY (widget)
12482 && gtk_editable_get_position (GTK_EDITABLE (widget)) == 0);
12485 static gboolean
12486 is_at_right (GtkWidget *widget)
12488 GtkEntryBuffer *buffer;
12489 gint length;
12491 if (!GTK_IS_ENTRY (widget))
12492 return FALSE;
12494 buffer = gtk_entry_get_buffer (GTK_ENTRY (widget));
12495 length = gtk_entry_buffer_get_length (buffer);
12496 return gtk_editable_get_position (GTK_EDITABLE (widget)) == length;
12499 static gboolean
12500 pspp_sheet_view_event (GtkWidget *widget,
12501 GdkEventKey *event,
12502 PsppSheetView *tree_view)
12504 PsppSheetViewColumn *column;
12505 GtkTreePath *path;
12506 gboolean handled;
12507 gboolean cancel;
12508 guint keyval;
12509 gint row;
12511 /* Intercept only key press events.
12512 It would make sense to use "key-press-event" instead of "event", but
12513 GtkEntry attaches its own signal handler to "key-press-event" that runs
12514 before ours and overrides our desired behavior for GDK_Up and GDK_Down.
12516 if (event->type != GDK_KEY_PRESS)
12517 return FALSE;
12519 keyval = event->keyval;
12520 cancel = FALSE;
12521 switch (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_MOD1_MASK))
12523 case 0:
12524 switch (event->keyval)
12526 case GDK_Left: case GDK_KP_Left:
12527 case GDK_Home: case GDK_KP_Home:
12528 if (!is_all_selected (widget) && !is_at_left (widget))
12529 return FALSE;
12530 break;
12532 case GDK_Right: case GDK_KP_Right:
12533 case GDK_End: case GDK_KP_End:
12534 if (!is_all_selected (widget) && !is_at_right (widget))
12535 return FALSE;
12536 break;
12538 case GDK_Up: case GDK_KP_Up:
12539 case GDK_Down: case GDK_KP_Down:
12540 break;
12542 case GDK_Page_Up: case GDK_KP_Page_Up:
12543 case GDK_Page_Down: case GDK_KP_Page_Down:
12544 break;
12546 case GDK_Escape:
12547 cancel = TRUE;
12548 break;
12550 case GDK_Return:
12551 keyval = GDK_Down;
12552 break;
12554 case GDK_Tab: case GDK_KP_Tab:
12555 case GDK_ISO_Left_Tab:
12556 keyval = GDK_Tab;
12557 break;
12559 default:
12560 return FALSE;
12562 break;
12564 case GDK_SHIFT_MASK:
12565 switch (event->keyval)
12567 case GDK_Tab:
12568 case GDK_ISO_Left_Tab:
12569 keyval = GDK_Tab;
12570 break;
12572 default:
12573 return FALSE;
12575 break;
12577 case GDK_CONTROL_MASK:
12578 switch (event->keyval)
12580 case GDK_Left: case GDK_KP_Left:
12581 if (!is_all_selected (widget) && !is_at_left (widget))
12582 return FALSE;
12583 break;
12585 case GDK_Right: case GDK_KP_Right:
12586 if (!is_all_selected (widget) && !is_at_right (widget))
12587 return FALSE;
12588 break;
12590 case GDK_Up: case GDK_KP_Up:
12591 case GDK_Down: case GDK_KP_Down:
12592 break;
12594 default:
12595 return FALSE;
12597 break;
12599 default:
12600 return FALSE;
12603 row = tree_view->priv->edited_row;
12604 column = tree_view->priv->edited_column;
12605 path = gtk_tree_path_new_from_indices (row, -1);
12607 pspp_sheet_view_stop_editing (tree_view, cancel);
12608 gtk_widget_grab_focus (GTK_WIDGET (tree_view));
12610 pspp_sheet_view_set_cursor (tree_view, path, column, FALSE);
12611 gtk_tree_path_free (path);
12613 handled = gtk_binding_set_activate (edit_bindings, keyval, event->state,
12614 G_OBJECT (tree_view));
12615 if (handled)
12616 g_signal_stop_emission_by_name (widget, "event");
12618 pspp_sheet_view_get_cursor (tree_view, &path, NULL);
12619 pspp_sheet_view_start_editing (tree_view, path);
12620 gtk_tree_path_free (path);
12622 return handled;
12625 static void
12626 pspp_sheet_view_override_cell_keypresses (GtkWidget *widget,
12627 gpointer data)
12629 PsppSheetView *sheet_view = data;
12631 g_signal_connect (widget, "event",
12632 G_CALLBACK (pspp_sheet_view_event),
12633 sheet_view);
12635 if (GTK_IS_CONTAINER (widget))
12636 gtk_container_foreach (GTK_CONTAINER (widget),
12637 pspp_sheet_view_override_cell_keypresses,
12638 data);
12641 static void
12642 pspp_sheet_view_real_start_editing (PsppSheetView *tree_view,
12643 PsppSheetViewColumn *column,
12644 GtkTreePath *path,
12645 GtkCellEditable *cell_editable,
12646 GdkRectangle *cell_area,
12647 GdkEvent *event,
12648 guint flags)
12650 PsppSheetSelectionMode mode = pspp_sheet_selection_get_mode (tree_view->priv->selection);
12651 gint pre_val = gtk_adjustment_get_value (tree_view->priv->vadjustment);
12652 GtkRequisition requisition;
12653 gint row;
12655 g_return_if_fail (gtk_tree_path_get_depth (path) == 1);
12657 tree_view->priv->edited_column = column;
12658 _pspp_sheet_view_column_start_editing (column, GTK_CELL_EDITABLE (cell_editable));
12660 row = gtk_tree_path_get_indices (path)[0];
12661 tree_view->priv->edited_row = row;
12662 pspp_sheet_view_real_set_cursor (tree_view, path, FALSE, TRUE, 0);
12663 cell_area->y += pre_val - (int)gtk_adjustment_get_value (tree_view->priv->vadjustment);
12665 pspp_sheet_selection_unselect_all_columns (tree_view->priv->selection);
12666 pspp_sheet_selection_select_column (tree_view->priv->selection, column);
12667 tree_view->priv->anchor_column = column;
12669 gtk_widget_size_request (GTK_WIDGET (cell_editable), &requisition);
12671 PSPP_SHEET_VIEW_SET_FLAG (tree_view, PSPP_SHEET_VIEW_DRAW_KEYFOCUS);
12673 if (requisition.height < cell_area->height)
12675 gint diff = cell_area->height - requisition.height;
12676 pspp_sheet_view_put (tree_view,
12677 GTK_WIDGET (cell_editable),
12678 cell_area->x, cell_area->y + diff/2,
12679 cell_area->width, requisition.height);
12681 else
12683 pspp_sheet_view_put (tree_view,
12684 GTK_WIDGET (cell_editable),
12685 cell_area->x, cell_area->y,
12686 cell_area->width, cell_area->height);
12689 gtk_cell_editable_start_editing (GTK_CELL_EDITABLE (cell_editable),
12690 (GdkEvent *)event);
12692 gtk_widget_grab_focus (GTK_WIDGET (cell_editable));
12693 g_signal_connect (cell_editable, "remove-widget",
12694 G_CALLBACK (pspp_sheet_view_remove_widget), tree_view);
12695 if (mode == PSPP_SHEET_SELECTION_RECTANGLE && column->row_head &&
12696 GTK_IS_BUTTON (cell_editable))
12698 g_signal_connect (cell_editable, "button-press-event",
12699 G_CALLBACK (pspp_sheet_view_editable_button_press_event),
12700 tree_view);
12701 g_object_set_data (G_OBJECT (cell_editable), "pspp-sheet-view-node",
12702 GINT_TO_POINTER (row));
12703 g_signal_connect (cell_editable, "clicked",
12704 G_CALLBACK (pspp_sheet_view_editable_clicked),
12705 tree_view);
12708 pspp_sheet_view_override_cell_keypresses (GTK_WIDGET (cell_editable),
12709 tree_view);
12712 void
12713 pspp_sheet_view_stop_editing (PsppSheetView *tree_view,
12714 gboolean cancel_editing)
12716 PsppSheetViewColumn *column;
12717 GtkCellRenderer *cell;
12719 if (tree_view->priv->edited_column == NULL)
12720 return;
12723 * This is very evil. We need to do this, because
12724 * gtk_cell_editable_editing_done may trigger pspp_sheet_view_row_changed
12725 * later on. If pspp_sheet_view_row_changed notices
12726 * tree_view->priv->edited_column != NULL, it'll call
12727 * pspp_sheet_view_stop_editing again. Bad things will happen then.
12729 * Please read that again if you intend to modify anything here.
12732 column = tree_view->priv->edited_column;
12733 tree_view->priv->edited_column = NULL;
12735 cell = _pspp_sheet_view_column_get_edited_cell (column);
12736 gtk_cell_renderer_stop_editing (cell, cancel_editing);
12738 if (!cancel_editing)
12739 gtk_cell_editable_editing_done (column->editable_widget);
12741 tree_view->priv->edited_column = column;
12743 gtk_cell_editable_remove_widget (column->editable_widget);
12748 * pspp_sheet_view_set_hover_selection:
12749 * @tree_view: a #PsppSheetView
12750 * @hover: %TRUE to enable hover selection mode
12752 * Enables of disables the hover selection mode of @tree_view.
12753 * Hover selection makes the selected row follow the pointer.
12754 * Currently, this works only for the selection modes
12755 * %PSPP_SHEET_SELECTION_SINGLE and %PSPP_SHEET_SELECTION_BROWSE.
12757 * Since: 2.6
12759 void
12760 pspp_sheet_view_set_hover_selection (PsppSheetView *tree_view,
12761 gboolean hover)
12763 hover = hover != FALSE;
12765 if (hover != tree_view->priv->hover_selection)
12767 tree_view->priv->hover_selection = hover;
12769 g_object_notify (G_OBJECT (tree_view), "hover-selection");
12774 * pspp_sheet_view_get_hover_selection:
12775 * @tree_view: a #PsppSheetView
12777 * Returns whether hover selection mode is turned on for @tree_view.
12779 * Return value: %TRUE if @tree_view is in hover selection mode
12781 * Since: 2.6
12783 gboolean
12784 pspp_sheet_view_get_hover_selection (PsppSheetView *tree_view)
12786 return tree_view->priv->hover_selection;
12790 * pspp_sheet_view_set_rubber_banding:
12791 * @tree_view: a #PsppSheetView
12792 * @enable: %TRUE to enable rubber banding
12794 * Enables or disables rubber banding in @tree_view. If the selection mode is
12795 * #PSPP_SHEET_SELECTION_MULTIPLE or #PSPP_SHEET_SELECTION_RECTANGLE, rubber
12796 * banding will allow the user to select multiple rows by dragging the mouse.
12798 * Since: 2.10
12800 void
12801 pspp_sheet_view_set_rubber_banding (PsppSheetView *tree_view,
12802 gboolean enable)
12804 enable = enable != FALSE;
12806 if (enable != tree_view->priv->rubber_banding_enable)
12808 tree_view->priv->rubber_banding_enable = enable;
12810 g_object_notify (G_OBJECT (tree_view), "rubber-banding");
12815 * pspp_sheet_view_get_rubber_banding:
12816 * @tree_view: a #PsppSheetView
12818 * Returns whether rubber banding is turned on for @tree_view. If the
12819 * selection mode is #PSPP_SHEET_SELECTION_MULTIPLE or
12820 * #PSPP_SHEET_SELECTION_RECTANGLE, rubber banding will allow the user to
12821 * select multiple rows by dragging the mouse.
12823 * Return value: %TRUE if rubber banding in @tree_view is enabled.
12825 * Since: 2.10
12827 gboolean
12828 pspp_sheet_view_get_rubber_banding (PsppSheetView *tree_view)
12830 return tree_view->priv->rubber_banding_enable;
12834 * pspp_sheet_view_is_rubber_banding_active:
12835 * @tree_view: a #PsppSheetView
12837 * Returns whether a rubber banding operation is currently being done
12838 * in @tree_view.
12840 * Return value: %TRUE if a rubber banding operation is currently being
12841 * done in @tree_view.
12843 * Since: 2.12
12845 gboolean
12846 pspp_sheet_view_is_rubber_banding_active (PsppSheetView *tree_view)
12848 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
12850 if (tree_view->priv->rubber_banding_enable
12851 && tree_view->priv->rubber_band_status == RUBBER_BAND_ACTIVE)
12852 return TRUE;
12854 return FALSE;
12857 static void
12858 pspp_sheet_view_grab_notify (GtkWidget *widget,
12859 gboolean was_grabbed)
12861 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
12863 tree_view->priv->in_grab = !was_grabbed;
12865 if (!was_grabbed)
12867 tree_view->priv->pressed_button = -1;
12869 if (tree_view->priv->rubber_band_status)
12870 pspp_sheet_view_stop_rubber_band (tree_view);
12874 static void
12875 pspp_sheet_view_state_changed (GtkWidget *widget,
12876 GtkStateType previous_state)
12878 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
12880 if (gtk_widget_get_realized (widget))
12882 GtkStyle *style = gtk_widget_get_style (widget);
12883 gdk_window_set_background (tree_view->priv->bin_window, &style->base[gtk_widget_get_state (widget)]);
12886 gtk_widget_queue_draw (widget);
12890 * pspp_sheet_view_get_grid_lines:
12891 * @tree_view: a #PsppSheetView
12893 * Returns which grid lines are enabled in @tree_view.
12895 * Return value: a #PsppSheetViewGridLines value indicating which grid lines
12896 * are enabled.
12898 * Since: 2.10
12900 PsppSheetViewGridLines
12901 pspp_sheet_view_get_grid_lines (PsppSheetView *tree_view)
12903 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), 0);
12905 return tree_view->priv->grid_lines;
12909 * pspp_sheet_view_set_grid_lines:
12910 * @tree_view: a #PsppSheetView
12911 * @grid_lines: a #PsppSheetViewGridLines value indicating which grid lines to
12912 * enable.
12914 * Sets which grid lines to draw in @tree_view.
12916 * Since: 2.10
12918 void
12919 pspp_sheet_view_set_grid_lines (PsppSheetView *tree_view,
12920 PsppSheetViewGridLines grid_lines)
12922 PsppSheetViewPrivate *priv;
12923 PsppSheetViewGridLines old_grid_lines;
12925 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
12927 priv = tree_view->priv;
12929 old_grid_lines = priv->grid_lines;
12930 priv->grid_lines = grid_lines;
12932 if (old_grid_lines != grid_lines)
12934 gtk_widget_queue_draw (GTK_WIDGET (tree_view));
12936 g_object_notify (G_OBJECT (tree_view), "enable-grid-lines");
12941 * pspp_sheet_view_get_special_cells:
12942 * @tree_view: a #PsppSheetView
12944 * Returns which grid lines are enabled in @tree_view.
12946 * Return value: a #PsppSheetViewSpecialCells value indicating whether rows in
12947 * the sheet view contain special cells.
12949 PsppSheetViewSpecialCells
12950 pspp_sheet_view_get_special_cells (PsppSheetView *tree_view)
12952 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), 0);
12954 return tree_view->priv->special_cells;
12958 * pspp_sheet_view_set_special_cells:
12959 * @tree_view: a #PsppSheetView
12960 * @special_cells: a #PsppSheetViewSpecialCells value indicating whether rows in
12961 * the sheet view contain special cells.
12963 * Sets whether rows in the sheet view contain special cells, controlling the
12964 * rendering of row selections.
12966 void
12967 pspp_sheet_view_set_special_cells (PsppSheetView *tree_view,
12968 PsppSheetViewSpecialCells special_cells)
12970 PsppSheetViewPrivate *priv;
12972 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
12974 priv = tree_view->priv;
12976 if (priv->special_cells != special_cells)
12978 priv->special_cells = special_cells;
12979 gtk_widget_queue_draw (GTK_WIDGET (tree_view));
12980 g_object_notify (G_OBJECT (tree_view), "special-cells");
12985 pspp_sheet_view_get_fixed_height (const PsppSheetView *tree_view)
12987 /* XXX (re)calculate fixed_height if necessary */
12988 return tree_view->priv->fixed_height;
12991 void
12992 pspp_sheet_view_set_fixed_height (PsppSheetView *tree_view,
12993 int fixed_height)
12995 g_return_if_fail (fixed_height > 0);
12997 if (tree_view->priv->fixed_height != fixed_height)
12999 tree_view->priv->fixed_height = fixed_height;
13000 g_object_notify (G_OBJECT (tree_view), "fixed-height");
13002 if (!tree_view->priv->fixed_height_set)
13004 tree_view->priv->fixed_height_set = TRUE;
13005 g_object_notify (G_OBJECT (tree_view), "fixed-height-set");
13010 * pspp_sheet_view_set_tooltip_row:
13011 * @tree_view: a #PsppSheetView
13012 * @tooltip: a #GtkTooltip
13013 * @path: a #GtkTreePath
13015 * Sets the tip area of @tooltip to be the area covered by the row at @path.
13016 * See also pspp_sheet_view_set_tooltip_column() for a simpler alternative.
13017 * See also gtk_tooltip_set_tip_area().
13019 * Since: 2.12
13021 void
13022 pspp_sheet_view_set_tooltip_row (PsppSheetView *tree_view,
13023 GtkTooltip *tooltip,
13024 GtkTreePath *path)
13026 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
13027 g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
13029 pspp_sheet_view_set_tooltip_cell (tree_view, tooltip, path, NULL, NULL);
13033 * pspp_sheet_view_set_tooltip_cell:
13034 * @tree_view: a #PsppSheetView
13035 * @tooltip: a #GtkTooltip
13036 * @path: (allow-none): a #GtkTreePath or %NULL
13037 * @column: (allow-none): a #PsppSheetViewColumn or %NULL
13038 * @cell: (allow-none): a #GtkCellRenderer or %NULL
13040 * Sets the tip area of @tooltip to the area @path, @column and @cell have
13041 * in common. For example if @path is %NULL and @column is set, the tip
13042 * area will be set to the full area covered by @column. See also
13043 * gtk_tooltip_set_tip_area().
13045 * See also pspp_sheet_view_set_tooltip_column() for a simpler alternative.
13047 * Since: 2.12
13049 void
13050 pspp_sheet_view_set_tooltip_cell (PsppSheetView *tree_view,
13051 GtkTooltip *tooltip,
13052 GtkTreePath *path,
13053 PsppSheetViewColumn *column,
13054 GtkCellRenderer *cell)
13056 GdkRectangle rect;
13058 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
13059 g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
13060 g_return_if_fail (column == NULL || PSPP_IS_SHEET_VIEW_COLUMN (column));
13061 g_return_if_fail (cell == NULL || GTK_IS_CELL_RENDERER (cell));
13063 /* Determine x values. */
13064 if (column && cell)
13066 GdkRectangle tmp;
13067 gint start, width;
13069 pspp_sheet_view_get_cell_area (tree_view, path, column, &tmp);
13070 pspp_sheet_view_column_cell_get_position (column, cell, &start, &width);
13072 pspp_sheet_view_convert_bin_window_to_widget_coords (tree_view,
13073 tmp.x + start, 0,
13074 &rect.x, NULL);
13075 rect.width = width;
13077 else if (column)
13079 GdkRectangle tmp;
13081 pspp_sheet_view_get_background_area (tree_view, NULL, column, &tmp);
13082 pspp_sheet_view_convert_bin_window_to_widget_coords (tree_view,
13083 tmp.x, 0,
13084 &rect.x, NULL);
13085 rect.width = tmp.width;
13087 else
13089 GtkAllocation allocation;
13090 gtk_widget_get_allocation (GTK_WIDGET (tree_view), &allocation);
13091 rect.x = 0;
13092 rect.width = allocation.width;
13095 /* Determine y values. */
13096 if (path)
13098 GdkRectangle tmp;
13100 pspp_sheet_view_get_background_area (tree_view, path, NULL, &tmp);
13101 pspp_sheet_view_convert_bin_window_to_widget_coords (tree_view,
13102 0, tmp.y,
13103 NULL, &rect.y);
13104 rect.height = tmp.height;
13106 else
13108 rect.y = 0;
13109 rect.height = gtk_adjustment_get_page_size (tree_view->priv->vadjustment);
13112 gtk_tooltip_set_tip_area (tooltip, &rect);
13116 * pspp_sheet_view_get_tooltip_context:
13117 * @tree_view: a #PsppSheetView
13118 * @x: the x coordinate (relative to widget coordinates)
13119 * @y: the y coordinate (relative to widget coordinates)
13120 * @keyboard_tip: whether this is a keyboard tooltip or not
13121 * @model: (allow-none): a pointer to receive a #GtkTreeModel or %NULL
13122 * @path: (allow-none): a pointer to receive a #GtkTreePath or %NULL
13123 * @iter: (allow-none): a pointer to receive a #GtkTreeIter or %NULL
13125 * This function is supposed to be used in a #GtkWidget::query-tooltip
13126 * signal handler for #PsppSheetView. The @x, @y and @keyboard_tip values
13127 * which are received in the signal handler, should be passed to this
13128 * function without modification.
13130 * The return value indicates whether there is a tree view row at the given
13131 * coordinates (%TRUE) or not (%FALSE) for mouse tooltips. For keyboard
13132 * tooltips the row returned will be the cursor row. When %TRUE, then any of
13133 * @model, @path and @iter which have been provided will be set to point to
13134 * that row and the corresponding model. @x and @y will always be converted
13135 * to be relative to @tree_view's bin_window if @keyboard_tooltip is %FALSE.
13137 * Return value: whether or not the given tooltip context points to a row.
13139 * Since: 2.12
13141 gboolean
13142 pspp_sheet_view_get_tooltip_context (PsppSheetView *tree_view,
13143 gint *x,
13144 gint *y,
13145 gboolean keyboard_tip,
13146 GtkTreeModel **model,
13147 GtkTreePath **path,
13148 GtkTreeIter *iter)
13150 GtkTreePath *tmppath = NULL;
13152 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), FALSE);
13153 g_return_val_if_fail (x != NULL, FALSE);
13154 g_return_val_if_fail (y != NULL, FALSE);
13156 if (keyboard_tip)
13158 pspp_sheet_view_get_cursor (tree_view, &tmppath, NULL);
13160 if (!tmppath)
13161 return FALSE;
13163 else
13165 pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view, *x, *y,
13166 x, y);
13168 if (!pspp_sheet_view_get_path_at_pos (tree_view, *x, *y,
13169 &tmppath, NULL, NULL, NULL))
13170 return FALSE;
13173 if (model)
13174 *model = pspp_sheet_view_get_model (tree_view);
13176 if (iter)
13177 gtk_tree_model_get_iter (pspp_sheet_view_get_model (tree_view),
13178 iter, tmppath);
13180 if (path)
13181 *path = tmppath;
13182 else
13183 gtk_tree_path_free (tmppath);
13185 return TRUE;
13188 static gboolean
13189 pspp_sheet_view_set_tooltip_query_cb (GtkWidget *widget,
13190 gint x,
13191 gint y,
13192 gboolean keyboard_tip,
13193 GtkTooltip *tooltip,
13194 gpointer data)
13196 GValue value = { 0, };
13197 GValue transformed = { 0, };
13198 GtkTreeIter iter;
13199 GtkTreePath *path;
13200 GtkTreeModel *model;
13201 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
13203 if (!pspp_sheet_view_get_tooltip_context (PSPP_SHEET_VIEW (widget),
13204 &x, &y,
13205 keyboard_tip,
13206 &model, &path, &iter))
13207 return FALSE;
13209 gtk_tree_model_get_value (model, &iter,
13210 tree_view->priv->tooltip_column, &value);
13212 g_value_init (&transformed, G_TYPE_STRING);
13214 if (!g_value_transform (&value, &transformed))
13216 g_value_unset (&value);
13217 gtk_tree_path_free (path);
13219 return FALSE;
13222 g_value_unset (&value);
13224 if (!g_value_get_string (&transformed))
13226 g_value_unset (&transformed);
13227 gtk_tree_path_free (path);
13229 return FALSE;
13232 gtk_tooltip_set_markup (tooltip, g_value_get_string (&transformed));
13233 pspp_sheet_view_set_tooltip_row (tree_view, tooltip, path);
13235 gtk_tree_path_free (path);
13236 g_value_unset (&transformed);
13238 return TRUE;
13242 * pspp_sheet_view_set_tooltip_column:
13243 * @tree_view: a #PsppSheetView
13244 * @column: an integer, which is a valid column number for @tree_view's model
13246 * If you only plan to have simple (text-only) tooltips on full rows, you
13247 * can use this function to have #PsppSheetView handle these automatically
13248 * for you. @column should be set to the column in @tree_view's model
13249 * containing the tooltip texts, or -1 to disable this feature.
13251 * When enabled, #GtkWidget::has-tooltip will be set to %TRUE and
13252 * @tree_view will connect a #GtkWidget::query-tooltip signal handler.
13254 * Note that the signal handler sets the text with gtk_tooltip_set_markup(),
13255 * so &amp;, &lt;, etc have to be escaped in the text.
13257 * Since: 2.12
13259 void
13260 pspp_sheet_view_set_tooltip_column (PsppSheetView *tree_view,
13261 gint column)
13263 g_return_if_fail (PSPP_IS_SHEET_VIEW (tree_view));
13265 if (column == tree_view->priv->tooltip_column)
13266 return;
13268 if (column == -1)
13270 g_signal_handlers_disconnect_by_func (tree_view,
13271 pspp_sheet_view_set_tooltip_query_cb,
13272 NULL);
13273 gtk_widget_set_has_tooltip (GTK_WIDGET (tree_view), FALSE);
13275 else
13277 if (tree_view->priv->tooltip_column == -1)
13279 g_signal_connect (tree_view, "query-tooltip",
13280 G_CALLBACK (pspp_sheet_view_set_tooltip_query_cb), NULL);
13281 gtk_widget_set_has_tooltip (GTK_WIDGET (tree_view), TRUE);
13285 tree_view->priv->tooltip_column = column;
13286 g_object_notify (G_OBJECT (tree_view), "tooltip-column");
13290 * pspp_sheet_view_get_tooltip_column:
13291 * @tree_view: a #PsppSheetView
13293 * Returns the column of @tree_view's model which is being used for
13294 * displaying tooltips on @tree_view's rows.
13296 * Return value: the index of the tooltip column that is currently being
13297 * used, or -1 if this is disabled.
13299 * Since: 2.12
13301 gint
13302 pspp_sheet_view_get_tooltip_column (PsppSheetView *tree_view)
13304 g_return_val_if_fail (PSPP_IS_SHEET_VIEW (tree_view), 0);
13306 return tree_view->priv->tooltip_column;
13309 gboolean
13310 _gtk_boolean_handled_accumulator (GSignalInvocationHint *ihint,
13311 GValue *return_accu,
13312 const GValue *handler_return,
13313 gpointer dummy)
13315 gboolean continue_emission;
13316 gboolean signal_handled;
13318 signal_handled = g_value_get_boolean (handler_return);
13319 g_value_set_boolean (return_accu, signal_handled);
13320 continue_emission = !signal_handled;
13322 return continue_emission;
13326 GType
13327 pspp_sheet_view_grid_lines_get_type (void)
13329 static GType etype = 0;
13330 if (G_UNLIKELY(etype == 0)) {
13331 static const GEnumValue values[] = {
13332 { PSPP_SHEET_VIEW_GRID_LINES_NONE, "PSPP_SHEET_VIEW_GRID_LINES_NONE", "none" },
13333 { PSPP_SHEET_VIEW_GRID_LINES_HORIZONTAL, "PSPP_SHEET_VIEW_GRID_LINES_HORIZONTAL", "horizontal" },
13334 { PSPP_SHEET_VIEW_GRID_LINES_VERTICAL, "PSPP_SHEET_VIEW_GRID_LINES_VERTICAL", "vertical" },
13335 { PSPP_SHEET_VIEW_GRID_LINES_BOTH, "PSPP_SHEET_VIEW_GRID_LINES_BOTH", "both" },
13336 { 0, NULL, NULL }
13338 etype = g_enum_register_static (g_intern_static_string ("PsppSheetViewGridLines"), values);
13340 return etype;
13343 GType
13344 pspp_sheet_view_special_cells_get_type (void)
13346 static GType etype = 0;
13347 if (G_UNLIKELY(etype == 0)) {
13348 static const GEnumValue values[] = {
13349 { PSPP_SHEET_VIEW_SPECIAL_CELLS_DETECT, "PSPP_SHEET_VIEW_SPECIAL_CELLS_DETECT", "detect" },
13350 { PSPP_SHEET_VIEW_SPECIAL_CELLS_YES, "PSPP_SHEET_VIEW_SPECIAL_CELLS_YES", "yes" },
13351 { PSPP_SHEET_VIEW_SPECIAL_CELLS_NO, "PSPP_SHEET_VIEW_SPECIAL_CELLS_NO", "no" },
13352 { 0, NULL, NULL }
13354 etype = g_enum_register_static (g_intern_static_string ("PsppSheetViewSpecialCells"), values);
13356 return etype;