--- gtk+-2.6.4/gtk/gtktreeview.c 2005-02-24 00:38:20.000000000 +0200 +++ gtk+-2.6.4/gtk/gtktreeview.c 2005-04-06 16:19:38.274719720 +0300 @@ -42,6 +42,7 @@ #include "gtkentry.h" #include "gtkframe.h" #include "gtktreemodelsort.h" +#include "gtkscrolledwindow.h" #define GTK_TREE_VIEW_PRIORITY_VALIDATE (GDK_PRIORITY_REDRAW + 5) #define GTK_TREE_VIEW_PRIORITY_SCROLL_SYNC (GTK_TREE_VIEW_PRIORITY_VALIDATE + 2) @@ -114,6 +115,7 @@ EXPAND_COLLAPSE_CURSOR_ROW, SELECT_CURSOR_PARENT, START_INTERACTIVE_SEARCH, + ROW_INSENSITIVE, LAST_SIGNAL }; @@ -132,7 +134,10 @@ PROP_SEARCH_COLUMN, PROP_FIXED_HEIGHT_MODE, PROP_HOVER_SELECTION, - PROP_HOVER_EXPAND + PROP_HOVER_EXPAND, + PROP_DOTTED_LINES, + PROP_FORCE_LIST_KLUDGE, + PROP_ALLOW_CHECKBOX_MODE }; static void gtk_tree_view_class_init (GtkTreeViewClass *klass); @@ -338,8 +343,6 @@ static void gtk_tree_view_clamp_node_visible (GtkTreeView *tree_view, GtkRBTree *tree, GtkRBNode *node); -static void gtk_tree_view_clamp_column_visible (GtkTreeView *tree_view, - GtkTreeViewColumn *column); static gboolean gtk_tree_view_maybe_begin_dragging_row (GtkTreeView *tree_view, GdkEventMotion *event); static void gtk_tree_view_focus_to_cursor (GtkTreeView *tree_view); @@ -372,6 +375,18 @@ gpointer data); static gboolean expand_collapse_timeout (gpointer data); static gboolean do_expand_collapse (GtkTreeView *tree_view); +static void update_checkbox_mode (GObject *object, + GParamSpec *pspec, + gpointer data); +static void set_dotted_lines (GtkTreeView *tree_view, + gboolean enable); +static void selection_changed (GtkTreeSelection *selection, + gpointer data); +static void check_if_can_focus (GtkTreeView *tree_view); +static gint scroll_row_timeout (gpointer data); + +static void add_scroll_timeout (GtkTreeView *tree_view); +static void remove_scroll_timeout (GtkTreeView *tree_view); /* interactive search */ static void gtk_tree_view_ensure_interactive_directory (GtkTreeView *tree_view); @@ -694,8 +709,54 @@ FALSE, G_PARAM_READWRITE)); + /** + * GtkTreeView:dotted-lines: + * + * Enables or disables the dotted lines for hierarchical trees. + * Hildon patch. + */ + g_object_class_install_property (o_class, + PROP_DOTTED_LINES, + g_param_spec_boolean ("dotted_lines", + P_("Dotted Lines"), + P_("Whether to show or hide dotted lines for hierarchical trees"), + FALSE, + G_PARAM_READWRITE)); + + /** + * GtkTreeView:force-list-kludge: + * + * Hildon kludge for fixing file tree behaviour until a cleaner + * implementation is scheduled: if this property is set, then rows + * can be activated by tapping even if the underlying tree model is + * not technically a list. + */ + g_object_class_install_property (o_class, + PROP_FORCE_LIST_KLUDGE, + g_param_spec_boolean ("force_list_kludge", + P_("Force List Behaviour"), + P_("Whether to activate tapped focused items even if model was not a list"), + FALSE, + G_PARAM_READWRITE)); + + /** + * GtkTreeView:enable-checkbox-mode: + * + * Another Hildon kludge for allowing the existence of GtkTreeViews + * that have activatable columns but that still is not a Text Listbox + * in multiple selection with checkboxes mode. + */ + g_object_class_install_property (o_class, + PROP_ALLOW_CHECKBOX_MODE, + g_param_spec_boolean ("allow_checkbox_mode", + P_("Enable Checkbox Mode"), + P_("Whether to behave like a Listbox in a multiple selection with checkboxes mode, if checkboxes exist"), + TRUE, + G_PARAM_READWRITE)); + /* Style properties */ #define _TREE_VIEW_EXPANDER_SIZE 12 +#define _TREE_VIEW_EXPANDER_INDENT 10 #define _TREE_VIEW_VERTICAL_SEPARATOR 2 #define _TREE_VIEW_HORIZONTAL_SEPARATOR 2 @@ -709,6 +770,15 @@ G_PARAM_READABLE)); gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("expander_indent", + P_("Expander intent"), + P_("Defines the expanders indent"), + 0, + G_MAXINT, + _TREE_VIEW_EXPANDER_INDENT, + G_PARAM_READABLE)); + + gtk_widget_class_install_style_property (widget_class, g_param_spec_int ("vertical_separator", P_("Vertical Separator Width"), P_("Vertical space between cells. Must be an even number"), @@ -754,6 +824,13 @@ GDK_TYPE_COLOR, G_PARAM_READABLE)); + gtk_widget_class_install_style_property (widget_class, + g_param_spec_boolean ("passive_focus", + P_("Enables passive focus"), + P_("Used for tree view passive focus"), + TRUE, + G_PARAM_READABLE)); + /* Signals */ widget_class->set_scroll_adjustments_signal = g_signal_new ("set_scroll_adjustments", @@ -917,6 +994,16 @@ _gtk_marshal_BOOLEAN__NONE, G_TYPE_BOOLEAN, 0); + tree_view_signals[ROW_INSENSITIVE] = + g_signal_new ("row_insensitive", + G_TYPE_FROM_CLASS (o_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkTreeViewClass, row_insensitive), + NULL, NULL, + _gtk_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GTK_TYPE_TREE_PATH); + /* Key bindings */ gtk_tree_view_add_move_binding (binding_set, GDK_Up, 0, GTK_MOVEMENT_DISPLAY_LINES, -1); @@ -1004,12 +1091,13 @@ gtk_binding_entry_add_signal (binding_set, GDK_space, 0, "select_cursor_row", 1, G_TYPE_BOOLEAN, TRUE); + /* Hildon change: Enter shouldn't select gtk_binding_entry_add_signal (binding_set, GDK_Return, 0, "select_cursor_row", 1, G_TYPE_BOOLEAN, TRUE); gtk_binding_entry_add_signal (binding_set, GDK_ISO_Enter, 0, "select_cursor_row", 1, G_TYPE_BOOLEAN, TRUE); gtk_binding_entry_add_signal (binding_set, GDK_KP_Enter, 0, "select_cursor_row", 1, - G_TYPE_BOOLEAN, TRUE); + G_TYPE_BOOLEAN, TRUE);*/ /* expand and collapse rows */ gtk_binding_entry_add_signal (binding_set, GDK_plus, 0, "expand_collapse_cursor_row", 3, @@ -1123,19 +1211,31 @@ gtk_binding_entry_add_signal (binding_set, GDK_f, GDK_CONTROL_MASK, "start_interactive_search", 0); gtk_binding_entry_add_signal (binding_set, GDK_F, GDK_CONTROL_MASK, "start_interactive_search", 0); + + /* Hildon addition: Add key bindings to Right and Left arrows */ + gtk_binding_entry_add_signal (binding_set, GDK_Right, 0, "expand_collapse_cursor_row", 3, + G_TYPE_BOOLEAN, FALSE, G_TYPE_BOOLEAN, TRUE, G_TYPE_BOOLEAN, FALSE); + gtk_binding_entry_add_signal (binding_set, GDK_plus, GDK_SHIFT_MASK, "expand_collapse_cursor_row", 3, + G_TYPE_BOOLEAN, FALSE, G_TYPE_BOOLEAN, TRUE, G_TYPE_BOOLEAN, TRUE); + gtk_binding_entry_add_signal (binding_set, GDK_Left, 0, "expand_collapse_cursor_row", 3, + G_TYPE_BOOLEAN, FALSE, G_TYPE_BOOLEAN, FALSE, G_TYPE_BOOLEAN, FALSE); + gtk_binding_entry_add_signal (binding_set, GDK_Left, GDK_SHIFT_MASK, "expand_collapse_cursor_row", 3, + G_TYPE_BOOLEAN, FALSE, G_TYPE_BOOLEAN, FALSE, G_TYPE_BOOLEAN, TRUE); } static void gtk_tree_view_init (GtkTreeView *tree_view) { tree_view->priv = g_new0 (GtkTreeViewPrivate, 1); - GTK_WIDGET_SET_FLAGS (tree_view, GTK_CAN_FOCUS); + + /* Hildon: focus cannot be gained until at least one row is added */ + GTK_WIDGET_UNSET_FLAGS (tree_view, GTK_CAN_FOCUS); gtk_widget_set_redraw_on_allocate (GTK_WIDGET (tree_view), FALSE); + /* Hildon: Headers invisible by default */ tree_view->priv->flags = GTK_TREE_VIEW_SHOW_EXPANDERS - | GTK_TREE_VIEW_DRAW_KEYFOCUS - | GTK_TREE_VIEW_HEADERS_VISIBLE; + | GTK_TREE_VIEW_DRAW_KEYFOCUS; /* We need some padding */ tree_view->priv->dy = 0; @@ -1165,6 +1265,26 @@ tree_view->priv->hover_selection = FALSE; tree_view->priv->hover_expand = FALSE; + + tree_view->priv->ctrl_pressed = FALSE; + tree_view->priv->shift_pressed = FALSE; + + tree_view->priv->checkbox_mode = FALSE; + tree_view->priv->allow_checkbox_mode = TRUE; + tree_view->priv->pen_down = FALSE; + tree_view->priv->pen_drag_active = FALSE; + tree_view->priv->pen_drag_reverse = FALSE; + tree_view->priv->first_drag_row = NULL; + tree_view->priv->last_drag_row = NULL; + tree_view->priv->queued_expand_row = NULL; + tree_view->priv->queued_select_row = NULL; + tree_view->priv->pen_focus = TRUE; + + /* Hildon: cursor should follow when selection changes */ + g_signal_connect (tree_view->priv->selection, "changed", + G_CALLBACK (selection_changed), tree_view); + + gtk_widget_set_name (GTK_WIDGET (tree_view), "treeview"); } @@ -1223,6 +1343,27 @@ case PROP_HOVER_EXPAND: tree_view->priv->hover_expand = g_value_get_boolean (value); break; + case PROP_DOTTED_LINES: + set_dotted_lines (tree_view, g_value_get_boolean (value)); + break; + case PROP_FORCE_LIST_KLUDGE: + tree_view->priv->force_list_kludge = g_value_get_boolean (value); + break; + case PROP_ALLOW_CHECKBOX_MODE: + if ((tree_view->priv->allow_checkbox_mode = g_value_get_boolean (value))) + { + gtk_widget_set_name (GTK_WIDGET(tree_view), "treeview"); + update_checkbox_mode (NULL, NULL, tree_view); + } + else + { + /* ugly hack - to ensure that checkboxes are independent of the + selection if !allow_checkbox_mode, we must be able to use + different theming in that case */ + gtk_widget_set_name (GTK_WIDGET(tree_view), "no_checkbox_mode"); + tree_view->priv->checkbox_mode = FALSE; + } + break; default: break; } @@ -1276,6 +1417,15 @@ case PROP_HOVER_EXPAND: g_value_set_boolean (value, tree_view->priv->hover_expand); break; + case PROP_DOTTED_LINES: + g_value_set_boolean (value, tree_view->priv->dotted_lines); + break; + case PROP_FORCE_LIST_KLUDGE: + g_value_set_boolean (value, tree_view->priv->force_list_kludge); + break; + case PROP_ALLOW_CHECKBOX_MODE: + g_value_set_boolean (value, tree_view->priv->allow_checkbox_mode); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1376,6 +1526,27 @@ tree_view->priv->destroy_count_data = NULL; } + if (tree_view->priv->first_drag_row) + { + gtk_tree_row_reference_free (tree_view->priv->first_drag_row); + tree_view->priv->first_drag_row = NULL; + } + if (tree_view->priv->last_drag_row) + { + gtk_tree_row_reference_free (tree_view->priv->last_drag_row); + tree_view->priv->last_drag_row = NULL; + } + if (tree_view->priv->queued_expand_row) + { + gtk_tree_row_reference_free (tree_view->priv->queued_expand_row); + tree_view->priv->queued_expand_row = NULL; + } + if (tree_view->priv->queued_select_row) + { + gtk_tree_row_reference_free (tree_view->priv->queued_select_row); + tree_view->priv->queued_select_row = NULL; + } + gtk_tree_row_reference_free (tree_view->priv->cursor); tree_view->priv->cursor = NULL; @@ -1494,6 +1665,8 @@ gtk_tree_view_map_buttons (tree_view); gdk_window_show (widget->window); + + check_if_can_focus (tree_view); } static void @@ -1895,6 +2068,8 @@ gint full_requested_width = 0; gint number_of_expand_columns = 0; gboolean rtl; + GtkWidget *scroll; + GtkPolicyType ptype; tree_view = GTK_TREE_VIEW (widget); @@ -1969,6 +2144,19 @@ allocation.x = width; column->width = real_requested_width; + /* a dirty Hildon hack to force truncation if not enough space. This hack is applied + * only if we are NOT in a scrolled window with hscroll*/ + scroll = gtk_widget_get_ancestor(widget, GTK_TYPE_SCROLLED_WINDOW); + if ((!scroll || + (gtk_scrolled_window_get_policy (GTK_SCROLLED_WINDOW (scroll), &ptype, NULL), ptype == GTK_POLICY_NEVER)) + && (width + real_requested_width > widget->allocation.width)) + { + column->width = widget->allocation.width - width; + if (column->width < 1) + column->width = 1; + gtk_widget_queue_draw (widget); + } + if (column->expand) { if (number_of_expand_columns == 1) @@ -2153,6 +2341,23 @@ GTK_TREE_VIEW_UNSET_FLAG (tree_view, GTK_TREE_VIEW_DRAW_KEYFOCUS); } +/* helper function for gtk_tree_view_button_press */ +static void +activate_callback (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + GtkTreeView *tree_view = GTK_TREE_VIEW (data); + + /* Hildon: if the tree view has no active focus we don't activate + * the selected row */ + if ( !GTK_WIDGET_HAS_FOCUS (GTK_WIDGET(data)) ) + return; + + gtk_tree_view_row_activated (tree_view, path, tree_view->priv->focus_column); +} + static gboolean gtk_tree_view_button_press (GtkWidget *widget, GdkEventButton *event) @@ -2166,6 +2371,7 @@ gint vertical_separator; gint horizontal_separator; gboolean rtl; + gint expander_indent; g_return_val_if_fail (GTK_IS_TREE_VIEW (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); @@ -2176,6 +2382,7 @@ gtk_widget_style_get (widget, "vertical_separator", &vertical_separator, "horizontal_separator", &horizontal_separator, + "expander_indent", &expander_indent, NULL); @@ -2199,6 +2406,14 @@ gint column_handled_click = FALSE; gboolean row_double_click = FALSE; gboolean rtl; + gboolean force_list_kludge; + GtkRBNode *cursor = NULL; + gboolean focus_grab = FALSE; + + if (!GTK_WIDGET_HAS_FOCUS (widget)) + focus_grab = TRUE; + + GTK_TREE_VIEW_UNSET_FLAG (tree_view, GTK_TREE_VIEW_DRAW_KEYFOCUS); /* Empty tree? */ if (tree_view->priv->tree == NULL) @@ -2207,7 +2422,9 @@ return TRUE; } - /* are we in an arrow? */ + /* In Hildon we don't want to use the arrows */ +#if 0 + /* are we in an arrow? */ if (tree_view->priv->prelight_node && GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_ARROW_PRELIT)) { @@ -2226,6 +2443,7 @@ grab_focus_and_unset_draw_keyfocus (tree_view); return TRUE; } +#endif /* find the node that was clicked */ new_y = TREE_WINDOW_Y_TO_RBTREE_Y(tree_view, event->y); @@ -2247,6 +2465,65 @@ background_area.height = ROW_HEIGHT (tree_view, GTK_RBNODE_GET_HEIGHT (node)); background_area.x = 0; + if (tree_view->priv->first_drag_row) + { + gtk_tree_row_reference_free (tree_view->priv->first_drag_row); + tree_view->priv->first_drag_row = NULL; + } + if (tree_view->priv->last_drag_row) + { + gtk_tree_row_reference_free (tree_view->priv->last_drag_row); + tree_view->priv->last_drag_row = NULL; + } + tree_view->priv->first_drag_row = + gtk_tree_row_reference_new (tree_view->priv->model, path); + tree_view->priv->last_drag_row = gtk_tree_row_reference_copy (tree_view->priv->first_drag_row); + + /* force_list_kludge allows pen dragging even if + GTK_TREE_MODEL_LIST_ONLY is not set (to fix file tree) */ + g_object_get (widget, "force_list_kludge", &force_list_kludge, NULL); + + /* Hildon: activate pen dragging, if listbox is not hierarchical and + the pen was not put down in a position that initiates drag'n'drop */ + if (!tree_view->priv->pen_down && + (force_list_kludge || + (gtk_tree_model_get_flags(tree_view->priv->model) + & GTK_TREE_MODEL_LIST_ONLY)) && + (tree_view->priv->checkbox_mode || + !gtk_tree_selection_path_is_selected(tree_view->priv->selection, path))) + { + gpointer drag_data; + + tree_view->priv->pen_down = TRUE; + tree_view->priv->pen_focus = TRUE; + + /* also block attached dnd signal handler */ + drag_data = g_object_get_data (G_OBJECT (widget), "gtk-site-data"); + if (drag_data) + g_signal_handlers_block_matched (widget, + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, + drag_data); + } + + /* For the Hildon buttonpress find out the previously selected row */ + GtkRBTree *cursor_tree = NULL; + GtkTreePath *cursor_path = NULL; + + if (tree_view->priv->cursor) + { + cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor); + if (cursor_path) + { + _gtk_tree_view_find_node (tree_view, cursor_path, + &cursor_tree, &cursor); + gtk_tree_path_free (cursor_path); + } + } + + /* Hildon: in checkbox mode, dragging sets all checkboxes + to the same state as the first toggled checkbox */ + tree_view->priv->new_state = !gtk_tree_selection_path_is_selected(tree_view->priv->selection, path); /* Let the column have a chance at selecting it. */ rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL); @@ -2275,8 +2552,11 @@ if (gtk_tree_view_is_expander_column (tree_view, column) && TREE_VIEW_DRAW_EXPANDERS(tree_view)) { - cell_area.x += depth * tree_view->priv->expander_size; - cell_area.width -= depth * tree_view->priv->expander_size; + gint adjust; + + adjust = depth * tree_view->priv->expander_size + (depth - 1) * expander_indent; + cell_area.x += adjust; + cell_area.width -= adjust; } break; } @@ -2364,15 +2644,19 @@ */ if (event->type == GDK_BUTTON_PRESS) { + /* Hildon: ignore Ctrl and Shift */ +#if 0 if ((event->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK) tree_view->priv->ctrl_pressed = TRUE; if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) tree_view->priv->shift_pressed = TRUE; +#endif focus_cell = _gtk_tree_view_column_get_cell_at_pos (column, event->x - background_area.x); if (focus_cell) gtk_tree_view_column_focus_cell (column, focus_cell); +#if 0 if (event->state & GDK_CONTROL_MASK) { gtk_tree_view_real_set_cursor (tree_view, path, FALSE, TRUE); @@ -2387,6 +2671,86 @@ { gtk_tree_view_real_set_cursor (tree_view, path, TRUE, TRUE); } +#endif + if (tree_view->priv->checkbox_mode) + { + GtkRBTree *tree = NULL; + GtkRBNode *node = NULL; + + _gtk_tree_view_find_node (tree_view, path, &tree, &node); + + /* cursor cannot move to an insensitive row, so we + need to check here to avoid toggling the current + row by clicking on an insensitive row */ + if (_gtk_tree_selection_is_row_selectable (tree_view->priv->selection, + node, path)) + { + gtk_tree_view_real_set_cursor (tree_view, path, + FALSE, TRUE); + gtk_tree_view_real_toggle_cursor_row (tree_view); + } + else + /* Usually this would be emitted by real_set_cursor. + However in this case we never call it. */ + g_signal_emit (tree_view, tree_view_signals[ROW_INSENSITIVE], 0, path); + } + else + { + gboolean queue_row = TRUE; + gboolean force_list_kludge; + + /* force_list_kludge allows rows to be activated even if + GTK_TREE_MODEL_LIST_ONLY is not set (to fix file tree) */ + g_object_get (widget, "force_list_kludge", + &force_list_kludge, NULL); + if ((force_list_kludge || + (gtk_tree_model_get_flags (tree_view->priv->model) & + GTK_TREE_MODEL_LIST_ONLY)) && + gtk_tree_row_reference_valid (tree_view->priv->cursor)) + { + /* special case: text listbox without checkboxes + should activate selected rows when user taps + on cursor row, but not affect selection*/ + GtkTreePath *cursor_path = + gtk_tree_row_reference_get_path (tree_view->priv->cursor); + if (gtk_tree_path_compare (cursor_path, path) == 0) + { + gtk_tree_selection_selected_foreach (tree_view->priv->selection, + activate_callback, + tree_view); + queue_row = FALSE; + } + } + + if (queue_row && + (gtk_tree_selection_get_mode (tree_view->priv->selection) == GTK_SELECTION_MULTIPLE) && + gtk_tree_selection_path_is_selected (tree_view->priv->selection, path)) + { + GtkTreePath *old_cursor_path = NULL; + + /* we don't know if the user is selecting an item or performing + multiple item drag and drop until we know where button is released */ + if (tree_view->priv->queued_select_row) + gtk_tree_row_reference_free (tree_view->priv->queued_select_row); + tree_view->priv->queued_select_row = + gtk_tree_row_reference_new (tree_view->priv->model, path); + + /* however, move focus */ + if (tree_view->priv->cursor) + { + old_cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor); + gtk_tree_row_reference_free (tree_view->priv->cursor); + } + tree_view->priv->cursor = gtk_tree_row_reference_new (tree_view->priv->model, + path); + gtk_tree_view_queue_draw_path (tree_view, path, NULL); + if (old_cursor_path) + gtk_tree_view_queue_draw_path (tree_view, old_cursor_path, NULL); + } + else + gtk_tree_view_real_set_cursor (tree_view, path, + queue_row, TRUE); + } tree_view->priv->ctrl_pressed = FALSE; tree_view->priv->shift_pressed = FALSE; @@ -2412,6 +2776,15 @@ tree_view->priv->press_start_y = event->y; } + /* Hildon: if selected row is tapped -> the row gets activated and expands */ + if (!focus_grab) + { + /* ...although not until button is released */ + gtk_tree_row_reference_free (tree_view->priv->queued_expand_row); + tree_view->priv->queued_expand_row = + gtk_tree_row_reference_new (tree_view->priv->model, path); + } + /* Test if a double click happened on the same row. */ if (event->button == 1) { @@ -2433,6 +2806,8 @@ } } + /* Hildon doesn't support double clicks */ +#if 0 if (row_double_click) { if (tree_view->priv->last_button_press) @@ -2443,6 +2818,7 @@ tree_view->priv->last_button_press_2 = NULL; } else +#endif { if (tree_view->priv->last_button_press) gtk_tree_row_reference_free (tree_view->priv->last_button_press); @@ -2626,6 +3002,28 @@ tree_view = GTK_TREE_VIEW (widget); + /* unblock attached dnd signal handler */ + if (tree_view->priv->pen_down) + { + gpointer drag_data = g_object_get_data (G_OBJECT (widget), "gtk-site-data"); + if (drag_data) + g_signal_handlers_unblock_matched (widget, + G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, + drag_data); + } + + /* stop pen dragging */ + if (tree_view->priv->first_drag_row) + gtk_tree_row_reference_free (tree_view->priv->first_drag_row); + if (tree_view->priv->last_drag_row) + gtk_tree_row_reference_free (tree_view->priv->last_drag_row); + tree_view->priv->first_drag_row = NULL; + tree_view->priv->last_drag_row = NULL; + tree_view->priv->pen_down = FALSE; + tree_view->priv->pen_drag_active = FALSE; + remove_scroll_timeout (tree_view); + if (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_IN_COLUMN_DRAG)) return gtk_tree_view_button_release_drag_column (widget, event); @@ -2635,6 +3033,65 @@ if (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_IN_COLUMN_RESIZE)) return gtk_tree_view_button_release_column_resize (widget, event); + if (gtk_tree_row_reference_valid (tree_view->priv->queued_select_row)) + { + /* unselect other nodes - but only if not drag'n'dropping */ + if (event->window == tree_view->priv->bin_window) + gtk_tree_selection_unselect_all (tree_view->priv->selection); + + gtk_tree_view_real_set_cursor (tree_view, + gtk_tree_row_reference_get_path (tree_view->priv->queued_select_row), + FALSE, TRUE); + gtk_tree_row_reference_free (tree_view->priv->queued_select_row); + tree_view->priv->queued_select_row = NULL; + } + + /* for handling expand/collapse postponed from button_press (since we + don't want expand/collapse before tap on node has been completed) */ + if (gtk_tree_row_reference_valid (tree_view->priv->queued_expand_row) && + tree_view->priv->tree != NULL) + { + GtkTreePath *queued_expand_path; + GtkRBTree *tree; + GtkRBNode *node; + GtkRBNode *old_node; + gint y; + + queued_expand_path = + gtk_tree_row_reference_get_path (tree_view->priv->queued_expand_row); + + if (queued_expand_path) + { + /* must check that cursor hasn't moved elsewhere since button_press */ + y = TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, event->y); + _gtk_rbtree_find_offset (tree_view->priv->tree, y, &tree, &node); + + _gtk_tree_view_find_node (tree_view, queued_expand_path, + &tree, &old_node); + + if (node && old_node == node) + { + if (node->children == NULL) + gtk_tree_view_real_expand_row (tree_view, + queued_expand_path, + tree, + node, + FALSE, TRUE); + else + gtk_tree_view_real_collapse_row (tree_view, + queued_expand_path, + tree, + node, + TRUE); + } + + gtk_tree_path_free (queued_expand_path); + } + + gtk_tree_row_reference_free( tree_view->priv->queued_expand_row); + tree_view->priv->queued_expand_row = NULL; + } + if (tree_view->priv->button_pressed_node == NULL) return FALSE; @@ -3311,6 +3768,7 @@ GtkTreeView *tree_view; GtkRBTree *tree; GtkRBNode *node; + GtkTreePath *path, *last_drag_path, *current_path; gint new_y; tree_view = (GtkTreeView *) widget; @@ -3319,7 +3777,8 @@ return FALSE; /* only check for an initiated drag when a button is pressed */ - if (tree_view->priv->pressed_button >= 0) + /* Hildon: active pen drag overrides drag and drop */ + if (tree_view->priv->pressed_button >= 0 && !tree_view->priv->pen_down) gtk_tree_view_maybe_begin_dragging_row (tree_view, event); new_y = TREE_WINDOW_Y_TO_RBTREE_Y(tree_view, event->y); @@ -3328,6 +3787,99 @@ _gtk_rbtree_find_offset (tree_view->priv->tree, new_y, &tree, &node); + /* Hildon: pen dragging */ + if (tree_view->priv->pen_down && node != NULL && + tree_view->priv->queued_select_row == NULL && + gtk_tree_row_reference_valid (tree_view->priv->last_drag_row)) + { + gint direction; + + last_drag_path = gtk_tree_row_reference_get_path (tree_view->priv->last_drag_row); + path = _gtk_tree_view_find_path (tree_view, tree, node); + direction = gtk_tree_path_compare (path, last_drag_path); + + if (direction != 0) + { + current_path = gtk_tree_path_copy (last_drag_path); + + /* we must ensure that no row is skipped because stylus + is moving faster than motion events are generated */ + do { + if (direction > 0) + { + /* gtk_tree_path_next does not let us know when it failed */ + GtkTreeIter iter; + gtk_tree_model_get_iter (tree_view->priv->model, &iter, current_path); + if (!gtk_tree_model_iter_next (tree_view->priv->model, &iter)) + break; + + gtk_tree_path_next (current_path); + } + else if (!gtk_tree_path_prev (current_path)) + break; + + /* set cursor, and start scrolling */ + gtk_tree_view_real_set_cursor (tree_view, current_path, FALSE, FALSE); + add_scroll_timeout (tree_view); + + if (tree_view->priv->checkbox_mode) + { + /* always set to same state as the first tapped node */ + if (tree_view->priv->new_state) + gtk_tree_selection_select_path (tree_view->priv->selection, + current_path); + else + gtk_tree_selection_unselect_path (tree_view->priv->selection, + current_path); + } + else + { + if (gtk_tree_selection_path_is_selected (tree_view->priv->selection, + current_path)) + { + /* apparently we have reversed the pen drag direction */ + GtkTreePath *reverse_path; + gint reverse_direction; + + reverse_direction = gtk_tree_path_compare (current_path, + last_drag_path); + reverse_path = gtk_tree_path_copy (last_drag_path); + do { + gtk_tree_selection_unselect_path (tree_view->priv->selection, + reverse_path); + tree_view->priv->pen_drag_reverse = TRUE; + if (reverse_direction > 0) + { + GtkTreeIter iter; + gtk_tree_model_get_iter (tree_view->priv->model, &iter, reverse_path); + if (!gtk_tree_model_iter_next (tree_view->priv->model, &iter)) + break; + + gtk_tree_path_next (reverse_path); + } + else if (!gtk_tree_path_prev (reverse_path)) + break; + } while (gtk_tree_path_compare (reverse_path, current_path) != 0); + gtk_tree_path_free (reverse_path); + } + else + { + gtk_tree_selection_select_path (tree_view->priv->selection, + current_path); + tree_view->priv->pen_drag_reverse = FALSE; + } + } + } while (gtk_tree_path_compare(current_path, path) != 0); + gtk_tree_path_free (current_path); + + /* update last_drag_row */ + gtk_tree_row_reference_free (tree_view->priv->last_drag_row); + tree_view->priv->last_drag_row = + gtk_tree_row_reference_new (tree_view->priv->model, path); + gtk_tree_path_free (path); + } + } + /* If we are currently pressing down a button, we don't want to prelight anything else. */ if ((tree_view->priv->button_pressed_node != NULL) && (tree_view->priv->button_pressed_node != node)) @@ -3404,6 +3956,22 @@ 1, 1, w, h); } +/* Hildon: helper function for dotted slash drawing; + returns TRUE or FALSE, depending it there are + more nodes at current level */ +static gboolean +iter_has_next (GtkTreeModel *model, GtkTreeIter *iter) +{ + GtkTreeIter *check_iter; + gboolean result; + + check_iter = gtk_tree_iter_copy(iter); + result = gtk_tree_model_iter_next (model, check_iter); + + gtk_tree_iter_free (check_iter); + return result; +} + /* Warning: Very scary function. * Modify at your own risk * @@ -3433,16 +4001,25 @@ guint flags; gint highlight_x; gint bin_window_width; - GtkTreePath *cursor_path; - GtkTreePath *drag_dest_path; + GtkTreePath *cursor_path = NULL; + GtkTreePath *drag_dest_path = NULL; GList *last_column; gint vertical_separator; gint horizontal_separator; + gint expander_indent; gint focus_line_width; gboolean allow_rules; gboolean has_special_cell; gboolean rtl; gint n_visible_columns; + gboolean dottedlines, passivefocus, res; + + /* Hildon: these variables are added for dotted slash drawing + (Hierarchical listbox) */ + gint i; + gint node_elements = 64; + gboolean *iter_value = NULL; + GtkTreeIter node_iter, parent_iter; g_return_val_if_fail (GTK_IS_TREE_VIEW (widget), FALSE); @@ -3455,8 +4032,12 @@ "vertical_separator", &vertical_separator, "allow_rules", &allow_rules, "focus-line-width", &focus_line_width, + "expander_indent", &expander_indent, + "passive_focus", &passivefocus, NULL); + g_object_get (widget, "dotted_lines", &dottedlines, NULL); + if (tree_view->priv->tree == NULL) { draw_empty_focus (tree_view, &event->area); @@ -3478,6 +4059,8 @@ if (node == NULL) return TRUE; + iter_value = g_new (gboolean, node_elements); + /* find the path for the node */ path = _gtk_tree_view_find_path ((GtkTreeView *)widget, tree, @@ -3486,11 +4069,25 @@ &iter, path); depth = gtk_tree_path_get_depth (path); + + node_iter = iter; + for (i = depth - 1; i >= 1; i--) + { + res = gtk_tree_model_iter_parent (tree_view->priv->model, &parent_iter, &node_iter); + /* Check, if we should grow array */ + if (i >= node_elements - 1) + { + node_elements *= 2; + iter_value = g_renew (gboolean, iter_value, node_elements); + if (!iter_value) + goto done; + } + iter_value[i] = iter_has_next (tree_view->priv->model, &parent_iter); + node_iter = parent_iter; + } + gtk_tree_model_get_iter (tree_view->priv->model, &iter, path); gtk_tree_path_free (path); - cursor_path = NULL; - drag_dest_path = NULL; - if (tree_view->priv->cursor) cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor); @@ -3533,6 +4130,7 @@ do { gboolean parity; + gboolean is_first = TRUE; gboolean is_separator = FALSE; if (tree_view->priv->row_separator_func) @@ -3570,6 +4168,7 @@ GtkTreeViewColumn *column = list->data; const gchar *detail = NULL; GtkStateType state; + gboolean is_last = (rtl ? !list->prev : !list->next); if (!column->visible) continue; @@ -3660,31 +4259,152 @@ else state = GTK_STATE_NORMAL; - /* Draw background */ - gtk_paint_flat_box (widget->style, - event->window, - state, - GTK_SHADOW_NONE, - &event->area, - widget, - detail, - background_area.x, - background_area.y, - background_area.width, - background_area.height); + if (tree_view->priv->pen_focus) + { + if (node != cursor || + (!GTK_WIDGET_HAS_FOCUS (widget) && !passivefocus)) + { + if ((flags & GTK_CELL_RENDERER_SELECTED) + && !tree_view->priv->checkbox_mode) + state = GTK_STATE_SELECTED; + else + state = GTK_STATE_NORMAL; + + /* Draw background */ + gtk_paint_flat_box (widget->style, + event->window, + state, + GTK_SHADOW_NONE, + &event->area, + widget, + detail, + background_area.x, + background_area.y, + background_area.width, + background_area.height); + } + else if ((flags & GTK_CELL_RENDERER_SELECTED) && + !tree_view->priv->checkbox_mode && + node != cursor) + { + gtk_paint_flat_box (widget->style, + event->window, + GTK_STATE_SELECTED, + GTK_SHADOW_NONE, + &event->area, + widget, + detail, + background_area.x, + background_area.y, + background_area.width, + background_area.height); + } + } + else + { + /* Draw background */ + gtk_paint_flat_box (widget->style, + event->window, + state, + GTK_SHADOW_NONE, + &event->area, + widget, + detail, + background_area.x, + background_area.y, + background_area.width, + background_area.height); + } + + /* Hildon change: drawing focus is moved here because it didn't work + properly before. Some changes where also made.*/ + /* draw the big row-spanning focus rectangle, if needed */ + if (node == cursor && + (!passivefocus || GTK_WIDGET_HAS_FOCUS (widget))) + { + gtk_paint_focus (widget->style, + event->window, + GTK_STATE_ACTIVE, + &event->area, + widget, + (is_first + ? (is_last ? "full" : "left") + : (is_last ? "right" : "middle")), + background_area.x - (is_first ? 0 : horizontal_separator / 2), + background_area.y - vertical_separator / 2, + background_area.width + (is_first ? 0 : (is_last ? horizontal_separator / 2 : horizontal_separator)), + background_area.height + vertical_separator); + + is_first = FALSE; + } + else if (node == cursor && passivefocus && + !GTK_WIDGET_HAS_FOCUS (widget)) + { + GtkStyle *style = gtk_rc_get_style_by_paths (gtk_widget_get_settings (widget), + "hildon-focus", + NULL, + G_TYPE_NONE); + gtk_style_attach (style, event->window); + + gtk_paint_focus (style, event->window, GTK_STATE_SELECTED, + &event->area, widget, + (is_first + ? (is_last ? "full" : "left") + : (is_last ? "right" : "middle")), + background_area.x - (is_first ? 0 : horizontal_separator / 2), + background_area.y - vertical_separator / 2, + background_area.width + (is_first ? 0 : (is_last ? horizontal_separator / 2 : horizontal_separator)), + background_area.height + vertical_separator); + + is_first = FALSE; + } + + if (node == cursor) + { + gint width, x_offset; + GtkStateType focus_rect_state; + focus_rect_state = + flags & GTK_CELL_RENDERER_FOCUSED ? GTK_STATE_ACTIVE : + (flags & GTK_CELL_RENDERER_PRELIT ? GTK_STATE_PRELIGHT : + (flags & GTK_CELL_RENDERER_INSENSITIVE ? GTK_STATE_INSENSITIVE : + (flags & GTK_CELL_RENDERER_SELECTED ? GTK_STATE_SELECTED : + GTK_STATE_NORMAL))); + + gtk_tree_view_get_arrow_xrange (tree_view, tree, &x_offset, NULL); + gdk_drawable_get_size (tree_view->priv->bin_window, &width, NULL); + } if (gtk_tree_view_is_expander_column (tree_view, column) && TREE_VIEW_DRAW_EXPANDERS(tree_view)) { + gint px, px2, py, i; + + if (depth <= 1) + px = 0; + else + px = (depth - 1) * tree_view->priv->expander_size + + (depth - 2) * expander_indent; + + /* Hildonlike hack for making the indent look better. + * indent is added to all rows except the first one */ + if (!rtl) - cell_area.x += depth * tree_view->priv->expander_size; - cell_area.width -= depth * tree_view->priv->expander_size; + cell_area.x += depth * tree_view->priv->expander_size + (depth-1) * expander_indent; + cell_area.width -= depth * tree_view->priv->expander_size + (depth-1) * expander_indent; /* If we have an expander column, the highlight underline * starts with that column, so that it indicates which * level of the tree we're dropping at. */ highlight_x = cell_area.x; + + if (!GTK_WIDGET_IS_SENSITIVE (widget)) + { + flags &= ~ (GTK_CELL_RENDERER_PRELIT + GTK_CELL_RENDERER_INSENSITIVE + + GTK_CELL_RENDERER_FOCUSED); + flags &= GTK_CELL_RENDERER_INSENSITIVE; + } + if (is_separator) gtk_paint_hline (widget->style, event->window, @@ -3702,6 +4422,48 @@ &cell_area, &event->area, flags); + + /* Hildon dotted slash line drawing for Hierarchical Listbox + widget */ + if (dottedlines) + { + py = cell_area.y + cell_area.height / 2; + px2 = depth * tree_view->priv->expander_size + + (depth - 1) * expander_indent; + + gdk_gc_set_line_attributes (widget->style->fg_gc[GTK_WIDGET_STATE (widget)], + 1, GDK_LINE_ON_OFF_DASH, GDK_CAP_BUTT, GDK_JOIN_BEVEL); + + gdk_draw_line (tree_view->priv->bin_window, widget->style->fg_gc[GTK_WIDGET_STATE (widget)], + px, py, px2, py); + + if (depth > 1) + { + gdk_draw_line (tree_view->priv->bin_window, widget->style->fg_gc[GTK_WIDGET_STATE (widget)], + px, cell_area.y, px, py); + if (iter_has_next (tree_view->priv->model, &iter)) + { + gdk_draw_line (tree_view->priv->bin_window, widget->style->fg_gc[GTK_WIDGET_STATE (widget)], + px, py, px, cell_area.y + cell_area.height); + } + } + + if (node->children) + { + gdk_draw_line (tree_view->priv->bin_window, widget->style->fg_gc[GTK_WIDGET_STATE (widget)], + px2, py, px2, cell_area.y + cell_area.height); + } + for (i = depth - 1; i >= 2; i--) + { + if (iter_value[i]) + { + px = (i - 1)* tree_view->priv->expander_size + (i - 2) * expander_indent; + gdk_draw_line (tree_view->priv->bin_window, widget->style->fg_gc[GTK_WIDGET_STATE (widget)], + px, cell_area.y, px, cell_area.y + cell_area.height); + } + } + } + if ((node->flags & GTK_RBNODE_IS_PARENT) == GTK_RBNODE_IS_PARENT) { gint x, y; @@ -3803,6 +4565,8 @@ } } + /* Hildon: disabled this */ +#if 0 /* draw the big row-spanning focus rectangle, if needed */ if (!has_special_cell && node == cursor && GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_DRAW_KEYFOCUS) && @@ -3830,6 +4594,7 @@ width, ROW_HEIGHT (tree_view, BACKGROUND_HEIGHT (node))); } +#endif y_offset += max_height; if (node->children) @@ -3847,6 +4612,17 @@ has_child = gtk_tree_model_iter_children (tree_view->priv->model, &iter, &parent); + + /* Check if we need to grow array */ + if (depth >= node_elements - 1) + { + node_elements *= 2; + iter_value = g_renew (gboolean, iter_value, node_elements); + if (!iter_value) + goto done; + } + iter_value[depth] = iter_has_next (tree_view->priv->model, &parent); + depth++; /* Sanity Check! */ @@ -3897,6 +4673,9 @@ if (drag_dest_path) gtk_tree_path_free (drag_dest_path); + if (iter_value) + g_free (iter_value); + return FALSE; } @@ -4179,6 +4958,63 @@ rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL); + /* Special Hildon keyboard interactions */ + if (event->keyval == GDK_Escape) + gtk_tree_selection_unselect_all (tree_view->priv->selection); + + if (event->keyval == GDK_Return && + gtk_tree_row_reference_valid (tree_view->priv->cursor)) + { + gboolean force_list_kludge; + + g_object_get (widget, "force_list_kludge", &force_list_kludge, NULL); + if (force_list_kludge || + (gtk_tree_model_get_flags (tree_view->priv->model) & + GTK_TREE_MODEL_LIST_ONLY)) + { + /* text listbox */ + if (tree_view->priv->checkbox_mode) + { + /* multisel with checkboxes: select key toggles focused */ + gtk_tree_view_real_toggle_cursor_row (tree_view); + } + else + { + /* no checkboxes: select key activates focused */ + GtkTreePath *cursor_path = + gtk_tree_row_reference_get_path (tree_view->priv->cursor); + + gtk_tree_view_row_activated (tree_view, cursor_path, + tree_view->priv->focus_column); + + gtk_tree_path_free (cursor_path); + } + } + else + { + /* hierarchical listbox */ + GtkTreePath *cursor_path; + GtkRBTree *tree; + GtkRBNode *node; + + cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor); + _gtk_tree_view_find_node (tree_view, cursor_path, &tree, &node); + + if (node->children == NULL) + gtk_tree_view_real_expand_row (tree_view, + cursor_path, + tree, + node, + FALSE, TRUE); + else + gtk_tree_view_real_collapse_row (tree_view, + cursor_path, + tree, + node, + TRUE); + } + } + if (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_IN_COLUMN_DRAG)) { if (event->keyval == GDK_Escape) @@ -4420,6 +5256,7 @@ /* FIXME Is this function necessary? Can I get an enter_notify event * w/o either an expose event or a mouse motion event? + * Hildon => it is necessary to make pen dragging work correctly */ static gboolean gtk_tree_view_enter_notify (GtkWidget *widget, @@ -4434,6 +5271,10 @@ tree_view = GTK_TREE_VIEW (widget); + /* stop "automatic" pen dragging */ + tree_view->priv->pen_drag_active = FALSE; + remove_scroll_timeout (tree_view); + /* Sanity check it */ if (event->window != tree_view->priv->bin_window) return FALSE; @@ -4463,6 +5304,9 @@ tree_view = GTK_TREE_VIEW (widget); tree_view->priv->pressed_button = -1; + if (tree_view->priv->pen_down && tree_view->priv->queued_select_row == NULL) + tree_view->priv->pen_drag_active = TRUE; + if (event->mode == GDK_CROSSING_GRAB) return TRUE; @@ -4535,6 +5379,7 @@ gboolean retval = FALSE; gboolean is_separator = FALSE; gint focus_pad; + gint expander_indent; /* double check the row needs validating */ if (! GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_INVALID) && @@ -4551,6 +5396,7 @@ gtk_widget_style_get (GTK_WIDGET (tree_view), "focus-padding", &focus_pad, "horizontal_separator", &horizontal_separator, + "expander_indent", &expander_indent, NULL); for (list = tree_view->priv->columns; list; list = list->next) @@ -4577,13 +5423,16 @@ { height = MAX (height, tmp_height); height = MAX (height, tree_view->priv->expander_size); + + /* Hildon addition */ + height -= 1; } else height = 2 + 2 * focus_pad; if (gtk_tree_view_is_expander_column (tree_view, column) && TREE_VIEW_DRAW_EXPANDERS (tree_view)) { - tmp_width = tmp_width + horizontal_separator + depth * (tree_view->priv->expander_size); + tmp_width = tmp_width + horizontal_separator + depth * (tree_view->priv->expander_size) + (depth - 1) * expander_indent; } else tmp_width = tmp_width + horizontal_separator; @@ -5585,6 +6434,16 @@ #endif /* 0 */ static void +add_scroll_timeout (GtkTreeView *tree_view) +{ + if (tree_view->priv->scroll_timeout == 0) + { + tree_view->priv->scroll_timeout = + g_timeout_add (150, scroll_row_timeout, tree_view); + } +} + +static void remove_scroll_timeout (GtkTreeView *tree_view) { if (tree_view->priv->scroll_timeout != 0) @@ -6130,10 +6989,9 @@ tree_view->priv->open_dest_timeout = g_timeout_add (AUTO_EXPAND_TIMEOUT, open_row_timeout, tree_view); } - else if (tree_view->priv->scroll_timeout == 0) + else { - tree_view->priv->scroll_timeout = - g_timeout_add (150, scroll_row_timeout, tree_view); + add_scroll_timeout (tree_view); } if (target == gdk_atom_intern ("GTK_TREE_MODEL_ROW", FALSE)) @@ -6901,8 +7759,6 @@ GtkMovementStep step, gint count) { - GdkModifierType state; - g_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), FALSE); g_return_val_if_fail (step == GTK_MOVEMENT_LOGICAL_POSITIONS || step == GTK_MOVEMENT_VISUAL_POSITIONS || @@ -6919,6 +7775,8 @@ GTK_TREE_VIEW_SET_FLAG (tree_view, GTK_TREE_VIEW_DRAW_KEYFOCUS); gtk_widget_grab_focus (GTK_WIDGET (tree_view)); + /* Hildon: Ignore ctrl and shift */ +#if 0 if (gtk_get_current_event_state (&state)) { if ((state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK) @@ -6926,6 +7784,7 @@ if ((state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) tree_view->priv->shift_pressed = TRUE; } +#endif /* else we assume not pressed */ switch (step) @@ -7092,6 +7951,27 @@ done: if (!tree_view->priv->fixed_height_mode) install_presize_handler (tree_view); + + /* Hildon: has row now been dimmed? If so, unselect it */ + _gtk_tree_view_find_node (tree_view, path, &tree, &node); + if (!_gtk_tree_selection_is_row_selectable (tree_view->priv->selection, + node, + path)) + { + if (gtk_tree_path_compare (path, + gtk_tree_row_reference_get_path(tree_view->priv->cursor)) + == 0) + { + gtk_tree_row_reference_free (tree_view->priv->cursor); + tree_view->priv->cursor = NULL; + } + + gtk_tree_selection_unselect_path (tree_view->priv->selection, path); + gtk_tree_view_collapse_row (tree_view, path); + } + + check_if_can_focus (tree_view); + if (free_path) gtk_tree_path_free (path); } @@ -7196,6 +8076,11 @@ install_presize_handler (tree_view); if (free_path) gtk_tree_path_free (path); + + /* Hildon: after a focusable row has been added, the + entire widget becomes focusable if it wasn't before */ + if ((GTK_WIDGET_FLAGS (tree_view) & GTK_CAN_FOCUS) == 0) + check_if_can_focus (tree_view); } static void @@ -7295,6 +8180,16 @@ _gtk_rbtree_traverse (node->children, node->children->root, G_POST_ORDER, check_selection_helper, data); } +static gboolean +check_if_can_focus_idle (GtkTreeView *tree_view) +{ + check_if_can_focus (tree_view); + + tree_view->priv->check_if_can_focus_idle_id = 0; + + return FALSE; +} + static void gtk_tree_view_row_deleted (GtkTreeModel *model, GtkTreePath *path, @@ -7357,6 +8252,11 @@ tree_view->priv->tree = NULL; _gtk_rbtree_remove (tree); + + /* Hildon: no nodes -> not focusable */ + /* FIXME this looks superfluos to me. check_if_can_focus is called + * at the end of this function .. -- Jorn */ + GTK_WIDGET_UNSET_FLAGS (tree_view, GTK_CAN_FOCUS); } else { @@ -7375,6 +8275,13 @@ if (selection_changed) g_signal_emit_by_name (tree_view->priv->selection, "changed"); + + /* FIXME whacky hack to work around the treeview not being in a clean state + * when in a tree a row has been removed, but has_child_toggled not been + * called yet */ + if (tree_view->priv->check_if_can_focus_idle_id == 0) + tree_view->priv->check_if_can_focus_idle_id = + g_idle_add ((GSourceFunc) check_if_can_focus_idle, tree_view); } static void @@ -7508,6 +8415,7 @@ GList *list; GtkTreeViewColumn *tmp_column = NULL; gint total_width; + gint expander_indent, depth; gboolean indent_expanders; gboolean rtl; @@ -7535,14 +8443,19 @@ gtk_widget_style_get (GTK_WIDGET (tree_view), "indent_expanders", &indent_expanders, + "expander_indent", &expander_indent, NULL); + /* Hildonlike hack for making the indent look better. + * indent is added to all rows except the first one */ + depth = _gtk_rbtree_get_depth (tree); + if (indent_expanders) { if (rtl) - x_offset -= tree_view->priv->expander_size * _gtk_rbtree_get_depth (tree); + x_offset -= tree_view->priv->expander_size * depth + (depth) * expander_indent; else - x_offset += tree_view->priv->expander_size * _gtk_rbtree_get_depth (tree); + x_offset += tree_view->priv->expander_size * depth + (depth) * expander_indent; } if (x1) { @@ -7617,9 +8530,11 @@ gboolean retval = FALSE; gint tmpheight; gint horizontal_separator; + gint expander_indent; gtk_widget_style_get (GTK_WIDGET (tree_view), "horizontal_separator", &horizontal_separator, + "expander_indent", &expander_indent, NULL); if (height) @@ -7657,7 +8572,7 @@ if (gtk_tree_view_is_expander_column (tree_view, column) && TREE_VIEW_DRAW_EXPANDERS (tree_view)) { - if (depth * tree_view->priv->expander_size + horizontal_separator + width > column->requested_width) + if ((depth - 1) *expander_indent + depth * tree_view->priv->expander_size + horizontal_separator + width > column->requested_width) { _gtk_tree_view_column_cell_set_dirty (column, TRUE); retval = TRUE; @@ -7747,6 +8662,7 @@ } } +#if 0 static void gtk_tree_view_clamp_column_visible (GtkTreeView *tree_view, GtkTreeViewColumn *column) @@ -7762,6 +8678,7 @@ gtk_adjustment_set_value (tree_view->priv->hadjustment, column->button->allocation.x); } +#endif /* This function could be more efficient. I'll optimize it if profiling seems * to imply that it is important */ @@ -8290,7 +9207,7 @@ area.x = x_offset; area.y = CELL_FIRST_PIXEL (tree_view, tree, node, vertical_separator); - area.width = expander_size + 2; + area.width = expander_size; area.height = MAX (CELL_HEIGHT (node, vertical_separator), (expander_size - vertical_separator)); if (node == tree_view->priv->button_pressed_node) @@ -8397,7 +9314,10 @@ GtkRBNode *cursor_node = NULL; GtkRBTree *new_cursor_tree = NULL; GtkRBNode *new_cursor_node = NULL; + GtkRBTree *old_cursor_tree; + GtkRBNode *old_cursor_node; GtkTreePath *cursor_path = NULL; + GtkTreePath *new_cursor_path = NULL; if (! GTK_WIDGET_HAS_FOCUS (tree_view)) return; @@ -8415,12 +9335,30 @@ if (cursor_tree == NULL) /* FIXME: we lost the cursor; should we get the first? */ return; - if (count == -1) - _gtk_rbtree_prev_full (cursor_tree, cursor_node, - &new_cursor_tree, &new_cursor_node); - else - _gtk_rbtree_next_full (cursor_tree, cursor_node, - &new_cursor_tree, &new_cursor_node); + + old_cursor_tree = cursor_tree; + old_cursor_node = cursor_node; + do { + if (count == -1) + _gtk_rbtree_prev_full (old_cursor_tree, old_cursor_node, + &new_cursor_tree, &new_cursor_node); + else + _gtk_rbtree_next_full (old_cursor_tree, old_cursor_node, + &new_cursor_tree, &new_cursor_node); + + if (new_cursor_node) + { + if (new_cursor_path) + gtk_tree_path_free (new_cursor_path); + + new_cursor_path = _gtk_tree_view_find_path (tree_view, new_cursor_tree, new_cursor_node); + old_cursor_tree = new_cursor_tree; + old_cursor_node = new_cursor_node; + } + } while (new_cursor_node && + !_gtk_tree_selection_is_row_selectable (tree_view->priv->selection, + new_cursor_node, + new_cursor_path)); /* * If the list has only one item and multi-selection is set then select @@ -8450,7 +9388,33 @@ if (new_cursor_node) { cursor_path = _gtk_tree_view_find_path (tree_view, new_cursor_tree, new_cursor_node); - gtk_tree_view_real_set_cursor (tree_view, cursor_path, TRUE, TRUE); + + if (tree_view->priv->checkbox_mode) + gtk_tree_view_real_set_cursor (tree_view, cursor_path, FALSE, TRUE); + else + gtk_tree_view_real_set_cursor (tree_view, cursor_path, TRUE, TRUE); + + if (tree_view->priv->pen_drag_active) + { + if (gtk_tree_path_compare (gtk_tree_row_reference_get_path (tree_view->priv->last_drag_row), + gtk_tree_row_reference_get_path (tree_view->priv->first_drag_row)) == 0) + tree_view->priv->pen_drag_reverse = FALSE; + + if (tree_view->priv->pen_drag_reverse) + { + gtk_tree_selection_select_path (tree_view->priv->selection, + cursor_path); + gtk_tree_selection_unselect_path (tree_view->priv->selection, + gtk_tree_row_reference_get_path (tree_view->priv->last_drag_row)); + } + + gtk_tree_row_reference_free (tree_view->priv->last_drag_row); + + tree_view->priv->last_drag_row = + gtk_tree_row_reference_new (tree_view->priv->model, + cursor_path); + } + gtk_tree_path_free (cursor_path); } else @@ -8467,6 +9431,8 @@ { GtkRBTree *cursor_tree = NULL; GtkRBNode *cursor_node = NULL; + GtkRBTree *old_cursor_tree = NULL; + GtkRBNode *old_cursor_node = NULL; GtkTreePath *cursor_path = NULL; gint y; gint vertical_separator; @@ -8474,6 +9440,9 @@ if (! GTK_WIDGET_HAS_FOCUS (tree_view)) return; + if (tree_view->priv->tree == NULL) + return; + if (gtk_tree_row_reference_valid (tree_view->priv->cursor)) cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor); else @@ -8504,7 +9473,65 @@ _gtk_rbtree_find_offset (tree_view->priv->tree, y, &cursor_tree, &cursor_node); cursor_path = _gtk_tree_view_find_path (tree_view, cursor_tree, cursor_node); - g_return_if_fail (cursor_path != NULL); + + while (cursor_node && + !_gtk_tree_selection_is_row_selectable (tree_view->priv->selection, + cursor_node, + cursor_path)) + { + old_cursor_tree = cursor_tree; + old_cursor_node = cursor_node; + + if (count < 0) + _gtk_rbtree_prev_full (old_cursor_tree, old_cursor_node, + &cursor_tree, &cursor_node); + else + _gtk_rbtree_next_full (old_cursor_tree, old_cursor_node, + &cursor_tree, &cursor_node); + + if (cursor_path) + { + gtk_tree_path_free(cursor_path); + cursor_path = NULL; + } + + if (cursor_node) + cursor_path = _gtk_tree_view_find_path (tree_view, cursor_tree, cursor_node); + } + + if (cursor_path == NULL) + { + /* looks like we reached the end without finding a sensitive row, + so search backwards and try to find the last sensitive row as + the next best thing */ + _gtk_rbtree_find_offset (tree_view->priv->tree, y, &cursor_tree, &cursor_node); + cursor_path = _gtk_tree_view_find_path (tree_view, cursor_tree, cursor_node); + while (cursor_node && + !_gtk_tree_selection_is_row_selectable (tree_view->priv->selection, + cursor_node, + cursor_path)) + { + old_cursor_tree = cursor_tree; + old_cursor_node = cursor_node; + + if (count < 0) + _gtk_rbtree_next_full (old_cursor_tree, old_cursor_node, + &cursor_tree, &cursor_node); + else + _gtk_rbtree_prev_full (old_cursor_tree, old_cursor_node, + &cursor_tree, &cursor_node); + + if (cursor_path) + { + gtk_tree_path_free(cursor_path); + cursor_path = NULL; + } + + if (cursor_node) + cursor_path = _gtk_tree_view_find_path (tree_view, cursor_tree, cursor_node); + } + } + gtk_tree_view_real_set_cursor (tree_view, cursor_path, TRUE, TRUE); gtk_tree_view_clamp_node_visible (tree_view, cursor_tree, cursor_node); gtk_tree_path_free (cursor_path); @@ -8514,6 +9541,8 @@ gtk_tree_view_move_cursor_left_right (GtkTreeView *tree_view, gint count) { + /* Hildon: cursor is always displayed on an entire row anyway */ +#if 0 GtkRBTree *cursor_tree = NULL; GtkRBNode *cursor_node = NULL; GtkTreePath *cursor_path = NULL; @@ -8589,12 +9618,15 @@ g_signal_emit (tree_view, tree_view_signals[CURSOR_CHANGED], 0); } gtk_tree_view_clamp_column_visible (tree_view, tree_view->priv->focus_column); +#endif } static void gtk_tree_view_move_cursor_start_end (GtkTreeView *tree_view, gint count) { + /* Hildon: cursor is always displayed on an entire row anyway */ +#if 0 GtkRBTree *cursor_tree; GtkRBNode *cursor_node; GtkTreePath *path; @@ -8631,6 +9663,7 @@ path = _gtk_tree_view_find_path (tree_view, cursor_tree, cursor_node); gtk_tree_view_real_set_cursor (tree_view, path, TRUE, TRUE); gtk_tree_path_free (path); +#endif } static gboolean @@ -8670,7 +9703,7 @@ GtkTreePath *cursor_path = NULL; GtkTreeSelectMode mode = 0; - if (! GTK_WIDGET_HAS_FOCUS (tree_view)) + if (! GTK_WIDGET_HAS_FOCUS (tree_view) && !tree_view->priv->checkbox_mode) return FALSE; if (tree_view->priv->cursor) @@ -8731,7 +9764,7 @@ GtkRBNode *cursor_node = NULL; GtkTreePath *cursor_path = NULL; - if (! GTK_WIDGET_HAS_FOCUS (tree_view)) + if (! GTK_WIDGET_HAS_FOCUS (tree_view) && !tree_view->priv->checkbox_mode) return FALSE; cursor_path = NULL; @@ -8774,6 +9807,7 @@ GtkTreePath *cursor_path = NULL; GtkRBTree *tree; GtkRBNode *node; + gboolean hildon_row; if (! GTK_WIDGET_HAS_FOCUS (tree_view)) return FALSE; @@ -8796,10 +9830,32 @@ && gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL) expand = !expand; - if (expand) - gtk_tree_view_real_expand_row (tree_view, cursor_path, tree, node, open_all, TRUE); + /* Keyboard Navigation: if we can't expand/collapse row, we should either move active focus + to child item (right arrow) or move active focus to the parent item (left arrow) */ + if (expand) + { + hildon_row = gtk_tree_view_real_expand_row (tree_view, cursor_path, tree, + node, open_all, TRUE); + + if (!hildon_row || !node->children) + g_signal_emit_by_name (gtk_widget_get_ancestor (GTK_WIDGET (tree_view), + GTK_TYPE_WINDOW), + "move_focus", + GTK_DIR_TAB_FORWARD); + } else - gtk_tree_view_real_collapse_row (tree_view, cursor_path, tree, node, TRUE); + { + hildon_row = gtk_tree_view_real_collapse_row (tree_view, cursor_path, tree, node, TRUE); + + if (hildon_row == FALSE) + { + g_signal_emit_by_name (gtk_widget_get_ancestor (GTK_WIDGET(tree_view), + GTK_TYPE_WINDOW), + "move_focus", + GTK_DIR_TAB_BACKWARD); + gtk_tree_view_real_select_cursor_parent (tree_view); + } + } gtk_tree_path_free (cursor_path); @@ -9327,6 +10383,14 @@ tree_view->priv->last_button_press_2 = NULL; gtk_tree_row_reference_free (tree_view->priv->scroll_to_path); tree_view->priv->scroll_to_path = NULL; + gtk_tree_row_reference_free (tree_view->priv->first_drag_row); + tree_view->priv->first_drag_row = NULL; + gtk_tree_row_reference_free (tree_view->priv->last_drag_row); + tree_view->priv->last_drag_row = NULL; + gtk_tree_row_reference_free (tree_view->priv->queued_expand_row); + tree_view->priv->queued_expand_row = NULL; + gtk_tree_row_reference_free (tree_view->priv->queued_select_row); + tree_view->priv->queued_select_row = NULL; tree_view->priv->scroll_to_column = NULL; @@ -9402,6 +10466,8 @@ install_presize_handler (tree_view); } + check_if_can_focus (tree_view); + g_object_notify (G_OBJECT (tree_view), "model"); if (GTK_WIDGET_REALIZED (tree_view)) @@ -9744,6 +10810,10 @@ G_CALLBACK (column_sizing_notify), tree_view); + g_signal_handlers_disconnect_by_func (column, + G_CALLBACK (update_checkbox_mode), + tree_view); + _gtk_tree_view_column_unset_tree_view (column); tree_view->priv->columns = g_list_remove (tree_view->priv->columns, column); @@ -9773,6 +10843,8 @@ g_object_unref (column); g_signal_emit (tree_view, tree_view_signals[COLUMNS_CHANGED], 0); + update_checkbox_mode (NULL, NULL, tree_view); + return tree_view->priv->n_columns; } @@ -9815,6 +10887,9 @@ g_signal_connect (column, "notify::sizing", G_CALLBACK (column_sizing_notify), tree_view); + g_signal_connect (column, "notify::visible", + G_CALLBACK (update_checkbox_mode), tree_view); + tree_view->priv->columns = g_list_insert (tree_view->priv->columns, column, position); tree_view->priv->n_columns++; @@ -9838,6 +10913,9 @@ g_signal_emit (tree_view, tree_view_signals[COLUMNS_CHANGED], 0); + update_checkbox_mode (NULL, NULL, tree_view); + check_if_can_focus (tree_view); + return tree_view->priv->n_columns; } @@ -10295,7 +11373,6 @@ GtkTreeViewColumn *column) { g_return_if_fail (GTK_IS_TREE_VIEW (tree_view)); - g_signal_emit (tree_view, tree_view_signals[ROW_ACTIVATED], 0, path, column); } @@ -10560,6 +11637,16 @@ GtkTreeIter iter; GtkTreeIter temp; gboolean expand; + gint vertical_separator; + GtkTreePath *collapse_path; + GtkRBTree *tree2; + GtkRBNode *node2; + GtkTreePath *child_path = NULL; + GtkTreeIter parent_iter; + GtkTreeIter child_iter; + GdkRectangle visible_rect; + gint children, n; + guint total_height; remove_auto_expand_timeout (tree_view); @@ -10573,8 +11660,12 @@ if (! gtk_tree_model_iter_has_child (tree_view->priv->model, &iter)) return FALSE; + /* Hildon: insensitive rows cannot be expanded */ + if (!_gtk_tree_selection_is_row_selectable (tree_view->priv->selection, + node, path)) + return FALSE; - if (node->children && open_all) + if (node->children && open_all) { gboolean retval = FALSE; GtkTreePath *tmp_path = gtk_tree_path_copy (path); @@ -10603,6 +11694,37 @@ return retval; } + /* Hildon: collapse other items in the same level */ + gtk_widget_style_get (GTK_WIDGET (tree_view), + "vertical_separator", &vertical_separator, NULL); + + /* find the first child */ + collapse_path = gtk_tree_path_copy (path); + while (gtk_tree_path_prev (collapse_path)) + ; + + do { + if (gtk_tree_path_compare (collapse_path, path) != 0) + { + _gtk_tree_view_find_node (tree_view, collapse_path, &tree2, &node2); + + if (tree2 == NULL) + /* end reached already */ + break; + + if (node2->children != NULL && + gtk_tree_view_real_collapse_row (tree_view, collapse_path, + tree2, node2, FALSE)) + /* no need to do anything else since only one row may + be expanded on any particular level at any time */ + break; + } + + gtk_tree_path_next (collapse_path); + } while (1); + + gtk_tree_path_free (collapse_path); + g_signal_emit (tree_view, tree_view_signals[TEST_EXPAND_ROW], 0, &iter, path, &expand); if (expand) @@ -10643,6 +11765,42 @@ GTK_RBNODE_SET_FLAG (node, GTK_RBNODE_IS_SEMI_COLLAPSED); } + /* autoscroll if necessary */ + validate_visible_area (tree_view); + gtk_tree_model_get_iter (tree_view->priv->model, &parent_iter, path); + _gtk_tree_view_find_node (tree_view, path, &tree2, &node2); + validate_row (tree_view, tree2, node2, &parent_iter, path); + total_height = CELL_HEIGHT (node2, vertical_separator); + children = gtk_tree_model_iter_n_children (tree_view->priv->model, &parent_iter); + for (n = 0; n < children; n++) + { + gtk_tree_model_iter_nth_child (tree_view->priv->model, + &child_iter, &parent_iter, n); + + /* must free here so the path of last child is kept for later */ + if (child_path != NULL) + gtk_tree_path_free (child_path); + + child_path = gtk_tree_model_get_path (tree_view->priv->model, &child_iter); + _gtk_tree_view_find_node (tree_view, child_path, &tree2, &node2); + + if (CELL_HEIGHT (node2, 0) == 0) + validate_row (tree_view, tree2, node2, &child_iter, child_path); + + total_height += CELL_HEIGHT (node2, vertical_separator); + } + + gtk_tree_view_get_visible_rect (tree_view, &visible_rect); + + /* KNOWN BUG: If no autocollapse was performed earlier above, these calls + to gtk_tree_view_scroll_to_cell do nothing although they should. */ + if (total_height > visible_rect.height) + gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0.0, 0.0); + else + gtk_tree_view_scroll_to_cell (tree_view, child_path, NULL, FALSE, 0.0, 0.0); + + gtk_tree_path_free (child_path); + install_presize_handler (tree_view); g_signal_emit (tree_view, tree_view_signals[ROW_EXPANDED], 0, &iter, path); @@ -11070,6 +12228,16 @@ GtkRBTree *tree = NULL; GtkRBNode *node = NULL; + _gtk_tree_view_find_node (tree_view, path, &tree, &node); + + /* Hildon: cursor cannot move to an insensitive row */ + if (!_gtk_tree_selection_is_row_selectable (tree_view->priv->selection, + node, path)) + { + g_signal_emit (tree_view, tree_view_signals[ROW_INSENSITIVE], 0, path); + return; + } + if (gtk_tree_row_reference_valid (tree_view->priv->cursor)) { GtkTreePath *cursor_path; @@ -11083,7 +12251,6 @@ tree_view->priv->cursor = gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view), tree_view->priv->model, path); - _gtk_tree_view_find_node (tree_view, path, &tree, &node); if (tree != NULL) { GtkRBTree *new_tree = NULL; @@ -11093,7 +12260,8 @@ { GtkTreeSelectMode mode = 0; - if (tree_view->priv->ctrl_pressed) + if (tree_view->priv->ctrl_pressed || + tree_view->priv->pen_drag_active) mode |= GTK_TREE_SELECT_MODE_TOGGLE; if (tree_view->priv->shift_pressed) mode |= GTK_TREE_SELECT_MODE_EXTEND; @@ -11213,6 +12381,9 @@ { g_return_if_fail (GTK_IS_TREE_VIEW (tree_view)); g_return_if_fail (path != NULL); + + tree_view->priv->pen_focus = FALSE; + if (focus_column) g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (focus_column)); if (focus_cell) @@ -11414,6 +12585,7 @@ GtkRBNode *node = NULL; gint vertical_separator; gint horizontal_separator; + gint expander_indent; g_return_if_fail (GTK_IS_TREE_VIEW (tree_view)); g_return_if_fail (column == NULL || GTK_IS_TREE_VIEW_COLUMN (column)); @@ -11424,6 +12596,7 @@ gtk_widget_style_get (GTK_WIDGET (tree_view), "vertical_separator", &vertical_separator, "horizontal_separator", &horizontal_separator, + "expander_indent", &expander_indent, NULL); rect->x = 0; @@ -11453,9 +12626,11 @@ TREE_VIEW_DRAW_EXPANDERS (tree_view)) { gint depth = gtk_tree_path_get_depth (path) - 1; + gint adjust; - rect->x += depth * tree_view->priv->expander_size; - rect->width -= depth * tree_view->priv->expander_size; + adjust = depth * tree_view->priv->expander_size + (depth - 1) * expander_indent; + rect->x += adjust; + rect->width -= adjust; rect->width = MAX (rect->width, 0); } } @@ -12077,8 +13252,13 @@ if (gtk_tree_view_is_expander_column (tree_view, column) && TREE_VIEW_DRAW_EXPANDERS(tree_view)) { - cell_area.x += depth * tree_view->priv->expander_size; - cell_area.width -= depth * tree_view->priv->expander_size; + gint adjust, expander_indent; + + gtk_widget_style_get (widget, "expander_indent", &expander_indent, NULL); + + adjust = depth * tree_view->priv->expander_size + (depth - 1) * expander_indent; + cell_area.x += adjust; + cell_area.width -= adjust; } if (gtk_tree_view_column_cell_is_visible (column)) @@ -13062,3 +14242,138 @@ tree_view->priv->pressed_button = -1; } +/* Hildon addition: iterates through columns and cells, looks for + a cell with "activatable" attribute and sets or unsets + priv->checkbox_mode accordingly (except when checkbox mode + is disabled by unsetting allow_checkbox_mode). + */ +static void +update_checkbox_mode (GObject *object, GParamSpec *pspec, gpointer data) +{ + GtkTreeView *tree_view = GTK_TREE_VIEW (data); + GList *columns = gtk_tree_view_get_columns (tree_view); + GList *list; + gboolean allow_checkbox_mode; + + g_object_get (GTK_WIDGET (data), + "allow_checkbox_mode", &allow_checkbox_mode, NULL); + g_return_if_fail (allow_checkbox_mode); + + for (list = columns; list; list = list->next) + { + GtkTreeViewColumn *col = GTK_TREE_VIEW_COLUMN (list->data); + if (gtk_tree_view_column_get_visible (col) && + _gtk_tree_view_column_has_activatable_cell (col)) + { + /* checkbox column found */ + tree_view->priv->checkbox_mode = TRUE; + g_list_free (columns); + return; + } + } + + /* no checkbox column was found */ + tree_view->priv->checkbox_mode = FALSE; + g_list_free (columns); +} + +static void +set_dotted_lines (GtkTreeView *tree_view, gboolean enable) +{ + if (enable != tree_view->priv->dotted_lines) + { + tree_view->priv->dotted_lines = enable; + gtk_widget_queue_draw (GTK_WIDGET (tree_view)); + } +} + +/* This function is used to ensure two things: + * - in single selection mode, focus will always equal selection + * - in multiple selection mode, focus is removed if cursor row is + * explicitly unselected + */ +static void +selection_changed (GtkTreeSelection *selection, gpointer data) +{ + GtkTreeView *tree_view = GTK_TREE_VIEW(data); + GtkTreePath *cursor_path = NULL; + GtkTreeIter iter; + + /* if there are checkboxes, cursor row doesn't have to be selected */ + if (tree_view->priv->checkbox_mode) + return; + + if (gtk_tree_row_reference_valid (tree_view->priv->cursor)) + cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor); + + if (cursor_path == NULL || + !gtk_tree_selection_path_is_selected (selection, cursor_path)) + { + GtkTreePath *selected_path; + GtkRBTree *tree = NULL; + GtkRBNode *node = NULL; + + if (gtk_tree_selection_get_mode (selection) != GTK_SELECTION_MULTIPLE && + gtk_tree_selection_get_selected (selection, NULL, &iter)) + { + selected_path = gtk_tree_model_get_path (tree_view->priv->model, + &iter); + gtk_tree_view_real_set_cursor (tree_view, selected_path, TRUE, TRUE); + _gtk_tree_view_find_node (tree_view, selected_path, &tree, &node); + gtk_tree_view_clamp_node_visible (tree_view, tree, node); + gtk_tree_path_free (selected_path); + gtk_widget_grab_focus (GTK_WIDGET (tree_view)); + } + else + { + gtk_tree_row_reference_free (tree_view->priv->cursor); + tree_view->priv->cursor = NULL; + } + } + + if (cursor_path) + gtk_tree_path_free (cursor_path); +} + +/* Helper function for ensuring that GtkTreeView is focusable + * if and only if it contains at least one sensitive top-level row. + * Should be called whenever the existence of a sensitive top-level row + * might have changed. + */ +static void +check_if_can_focus (GtkTreeView *tree_view) +{ + GtkTreeModel *model = gtk_tree_view_get_model (tree_view); + GtkTreeIter iter; + + if (model == NULL || !GTK_WIDGET_MAPPED (tree_view)) + return; + + if (gtk_tree_model_get_iter_first (model, &iter) == FALSE) + { + GTK_WIDGET_UNSET_FLAGS (tree_view, GTK_CAN_FOCUS); + return; + } + + do { + GtkTreePath *path = gtk_tree_model_get_path (model, &iter); + GtkRBTree *tree; + GtkRBNode *node; + + _gtk_tree_view_find_node (tree_view, path, &tree, &node); + + if (_gtk_tree_selection_is_row_selectable (tree_view->priv->selection, + node, path)) + { + GTK_WIDGET_SET_FLAGS (tree_view, GTK_CAN_FOCUS); + if (!gtk_tree_row_reference_valid (tree_view->priv->cursor)) + gtk_tree_view_real_set_cursor (tree_view, path, + !tree_view->priv->checkbox_mode, + TRUE); + + return; + } + } while (gtk_tree_model_iter_next (model, &iter)); + + GTK_WIDGET_UNSET_FLAGS (tree_view, GTK_CAN_FOCUS); +}