--- a/gtk/gtktreeview.c +++ b/gtk/gtktreeview.c @@ -43,6 +43,8 @@ #include "gtkframe.h" #include "gtktreemodelsort.h" #include "gtktooltip.h" +#include "gtkicontheme.h" +#include "gtkeventbox.h" #include "gtkprivate.h" #include "gtkalias.h" @@ -54,6 +56,9 @@ #define GTK_TREE_VIEW_SEARCH_DIALOG_TIMEOUT 5000 #define AUTO_EXPAND_TIMEOUT 500 +#define HILDON_TICK_MARK_SIZE 48 +#define HILDON_ROW_HEADER_HEIGHT 35 + /* The "background" areas of all rows/cells add up to cover the entire tree. * The background includes all inter-row and inter-cell spacing. * The "cell" areas are the cell_area passed in to gtk_cell_renderer_render(), @@ -120,6 +125,8 @@ EXPAND_COLLAPSE_CURSOR_ROW, SELECT_CURSOR_PARENT, START_INTERACTIVE_SEARCH, + ROW_INSENSITIVE, + HILDON_ROW_TAPPED, LAST_SIGNAL }; @@ -144,7 +151,10 @@ PROP_RUBBER_BANDING, PROP_ENABLE_GRID_LINES, PROP_ENABLE_TREE_LINES, - PROP_TOOLTIP_COLUMN + PROP_TOOLTIP_COLUMN, + PROP_HILDON_UI_MODE, + PROP_ACTION_AREA_VISIBLE, + PROP_ACTION_AREA_ORIENTATION }; /* object signals */ @@ -478,9 +488,16 @@ static void add_scroll_timeout (GtkTreeView *tree_view); static void remove_scroll_timeout (GtkTreeView *tree_view); -static guint tree_view_signals [LAST_SIGNAL] = { 0 }; +static gboolean gtk_tree_view_tap_and_hold_query (GtkWidget *widget, + GdkEvent *event); +static void free_queued_select_row (GtkTreeView *tree_view); +static void free_queued_activate_row (GtkTreeView *tree_view); +static void free_queued_actions (GtkTreeView *tree_view); - +static void hildon_tree_view_set_action_area_height (GtkTreeView *tree_view); +static void hildon_tree_view_setup_row_header_layout (GtkTreeView *tree_view); + +static guint tree_view_signals [LAST_SIGNAL] = { 0 }; /* GType Methods */ @@ -544,6 +561,12 @@ widget_class->grab_notify = gtk_tree_view_grab_notify; widget_class->state_changed = gtk_tree_view_state_changed; + g_signal_override_class_closure (g_signal_lookup ("tap-and-hold-query", + GTK_TYPE_WIDGET), + GTK_TYPE_TREE_VIEW, + g_cclosure_new (G_CALLBACK (gtk_tree_view_tap_and_hold_query), + NULL, NULL)); + /* GtkContainer signals */ container_class->remove = gtk_tree_view_remove; container_class->forall = gtk_tree_view_forall; @@ -715,7 +738,7 @@ g_param_spec_boolean ("show-expanders", P_("Show Expanders"), P_("View has expanders"), - TRUE, + FALSE, GTK_PARAM_READWRITE)); /** @@ -732,7 +755,7 @@ P_("Extra indentation for each level"), 0, G_MAXINT, - 0, + 10, GTK_PARAM_READWRITE)); g_object_class_install_property (o_class, @@ -740,7 +763,7 @@ g_param_spec_boolean ("rubber-banding", P_("Rubber Banding"), P_("Whether to enable selection of multiple items by dragging the mouse pointer"), - FALSE, + TRUE, GTK_PARAM_READWRITE)); g_object_class_install_property (o_class, @@ -770,10 +793,73 @@ -1, GTK_PARAM_READWRITE)); + /** + * GtkTreeView:hildon-ui-mode: + * + * Specifies which UI mode to use. A setting of #HILDON_UI_MODE_NORMAL + * will cause the tree view to disable selections and emit row-activated + * as soon as a row is pressed. When #HILDON_UI_MODE_EDIT is set, + * selections can be made according to the setting of the mode on + * GtkTreeSelection. + * + * Toggling this property will cause the tree view to select an + * appropriate selection mode if not already done. + * + * Since: maemo 5.0 + * Stability: unstable + */ + g_object_class_install_property (o_class, + PROP_HILDON_UI_MODE, + g_param_spec_enum ("hildon-ui-mode", + P_("Hildon UI Mode"), + P_("The Hildon UI mode according to which the tree view should behave"), + HILDON_TYPE_UI_MODE, + HILDON_UI_MODE_NORMAL, + GTK_PARAM_READWRITE)); + + /** + * GtkTreeView:action-area-visible: + * + * Makes the action area of the GtkTreeView visible or invisible. + * Based on the value of the GtkTreeView:action-area-orientation + * property a certain height will be allocated above the first row + * for the action area. + * + * Since: maemo 5.0 + * Stability: unstable + */ + g_object_class_install_property (o_class, + PROP_ACTION_AREA_VISIBLE, + g_param_spec_boolean ("action-area-visible", + P_("Action Area Visible"), + P_("Whether the action area above the first row is visible"), + FALSE, + GTK_PARAM_READWRITE)); + + /** + * GtkTreeView:action-area-orientation: + * + * Sets the orientation of the action area. This is either + * horizontal (landscape) or vertical (portrait). The height of + * the action area depends on this setting. + * + * Since: maemo 5.0 + * Stability: unstable + */ + g_object_class_install_property (o_class, + PROP_ACTION_AREA_ORIENTATION, + g_param_spec_enum ("action-area-orientation", + P_("Action Area Orientation"), + P_("Determines the orientation of the action area."), + GTK_TYPE_ORIENTATION, + GTK_ORIENTATION_HORIZONTAL, + GTK_PARAM_READWRITE)); + /* Style properties */ #define _TREE_VIEW_EXPANDER_SIZE 12 #define _TREE_VIEW_VERTICAL_SEPARATOR 2 #define _TREE_VIEW_HORIZONTAL_SEPARATOR 2 +#define _TREE_VIEW_SEPARATOR_HEIGHT 2 gtk_widget_class_install_style_property (widget_class, g_param_spec_int ("expander-size", @@ -872,6 +958,43 @@ "\1\1", GTK_PARAM_READABLE)); + /** + * GtkTreeView:separator-height: + * + * Height in pixels of a separator. + * + * Since: maemo 3.0 + * Stability: Unstable + */ + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("separator-height", + P_("Separator height"), + P_("Height of the separator"), + 0, + G_MAXINT, + _TREE_VIEW_SEPARATOR_HEIGHT, + GTK_PARAM_READABLE)); + + /** + * GtkTreeView:row-height: + * + * Height in pixels of a row. When set, all rows will use this height, + * except for row separators and row headers. A value of -1 means this + * value is unset. Setting this property does not imply fixed height + * mode will be turned on, so columns are still properly autosized. + * + * Since: maemo 5.0 + * Stability: Unstable + */ + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("row-height", + P_("Row height"), + P_("Height of a row"), + -1, + G_MAXINT, + -1, + GTK_PARAM_READABLE)); + /* Signals */ /** * GtkTreeView::set-scroll-adjustments @@ -1109,6 +1232,36 @@ _gtk_marshal_BOOLEAN__VOID, G_TYPE_BOOLEAN, 0); + /** + * GtkTreeView::row-insensitive: + * @tree_view: the object which received the signal. + * @path: the path where the cursor is tried to be moved. + * + * Emitted when the user tries to move cursor to an insesitive row. + * + * Since: maemo 1.0 + * Stability: Unstable + */ + 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); + + tree_view_signals[HILDON_ROW_TAPPED] = + g_signal_new ("hildon_row_tapped", + G_TYPE_FROM_CLASS (o_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + 0, + NULL, NULL, + _gtk_marshal_VOID__BOXED, + G_TYPE_NONE, 1, + GTK_TYPE_TREE_PATH); + /* Key bindings */ gtk_tree_view_add_move_binding (binding_set, GDK_Up, 0, TRUE, GTK_MOVEMENT_DISPLAY_LINES, -1); @@ -1333,9 +1486,7 @@ gtk_widget_set_can_focus (GTK_WIDGET (tree_view), TRUE); gtk_widget_set_redraw_on_allocate (GTK_WIDGET (tree_view), FALSE); - tree_view->priv->flags = GTK_TREE_VIEW_SHOW_EXPANDERS - | GTK_TREE_VIEW_DRAW_KEYFOCUS - | GTK_TREE_VIEW_HEADERS_VISIBLE; + tree_view->priv->flags = GTK_TREE_VIEW_DRAW_KEYFOCUS; /* We need some padding */ tree_view->priv->dy = 0; @@ -1368,9 +1519,27 @@ tree_view->priv->hover_selection = FALSE; tree_view->priv->hover_expand = FALSE; - tree_view->priv->level_indentation = 0; + tree_view->priv->level_indentation = 10; + + tree_view->priv->queued_select_row = NULL; + tree_view->priv->queued_expand_row = NULL; + tree_view->priv->queued_activate_row = NULL; + tree_view->priv->queued_tapped_row = NULL; + + tree_view->priv->highlighted_node = NULL; + tree_view->priv->highlighted_tree = NULL; + + tree_view->priv->queued_ctrl_pressed = FALSE; + tree_view->priv->queued_shift_pressed = FALSE; + + tree_view->priv->hildon_ui_mode = HILDON_UI_MODE_NORMAL; + gtk_widget_style_get (GTK_WIDGET (tree_view), + "hildon-mode", &tree_view->priv->hildon_mode, + NULL); - tree_view->priv->rubber_banding_enable = FALSE; + tree_view->priv->level_indentation = 10; + + tree_view->priv->rubber_banding_enable = TRUE; tree_view->priv->grid_lines = GTK_TREE_VIEW_GRID_LINES_NONE; tree_view->priv->tree_lines_enabled = FALSE; @@ -1384,9 +1553,13 @@ tree_view->priv->event_last_x = -10000; tree_view->priv->event_last_y = -10000; -} - + tree_view->priv->rows_offset = 0; + tree_view->priv->action_area_visible = FALSE; + tree_view->priv->action_area_orientation = GTK_ORIENTATION_HORIZONTAL; + tree_view->priv->action_area_event_box = NULL; + tree_view->priv->action_area_box = NULL; +} /* GObject Methods */ @@ -1460,6 +1633,15 @@ case PROP_TOOLTIP_COLUMN: gtk_tree_view_set_tooltip_column (tree_view, g_value_get_int (value)); break; + case PROP_HILDON_UI_MODE: + hildon_tree_view_set_hildon_ui_mode (tree_view, g_value_get_enum (value)); + break; + case PROP_ACTION_AREA_VISIBLE: + hildon_tree_view_set_action_area_visible (tree_view, g_value_get_boolean (value)); + break; + case PROP_ACTION_AREA_ORIENTATION: + hildon_tree_view_set_action_area_orientation (tree_view, g_value_get_enum (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1535,6 +1717,15 @@ case PROP_TOOLTIP_COLUMN: g_value_set_int (value, tree_view->priv->tooltip_column); break; + case PROP_HILDON_UI_MODE: + g_value_set_enum (value, tree_view->priv->hildon_ui_mode); + break; + case PROP_ACTION_AREA_VISIBLE: + g_value_set_boolean (value, tree_view->priv->action_area_visible); + break; + case PROP_ACTION_AREA_ORIENTATION: + g_value_set_enum (value, tree_view->priv->action_area_orientation); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1595,6 +1786,8 @@ tree_view->priv->prelight_node = NULL; tree_view->priv->expanded_collapsed_node = NULL; tree_view->priv->expanded_collapsed_tree = NULL; + tree_view->priv->highlighted_node = NULL; + tree_view->priv->highlighted_tree = NULL; } static void @@ -1650,6 +1843,30 @@ tree_view->priv->top_row = NULL; } + if (tree_view->priv->queued_select_row != NULL) + { + gtk_tree_row_reference_free (tree_view->priv->queued_select_row); + tree_view->priv->queued_select_row = NULL; + } + + if (tree_view->priv->queued_expand_row != NULL) + { + gtk_tree_row_reference_free (tree_view->priv->queued_expand_row); + tree_view->priv->queued_expand_row = NULL; + } + + if (tree_view->priv->queued_activate_row != NULL) + { + gtk_tree_row_reference_free (tree_view->priv->queued_activate_row); + tree_view->priv->queued_activate_row = NULL; + } + + if (tree_view->priv->queued_tapped_row != NULL) + { + gtk_tree_row_reference_free (tree_view->priv->queued_tapped_row); + tree_view->priv->queued_tapped_row = NULL; + } + if (tree_view->priv->column_drop_func_data && tree_view->priv->column_drop_func_data_destroy) { @@ -1701,6 +1918,24 @@ tree_view->priv->row_separator_data = NULL; } + if (tree_view->priv->row_header_destroy && tree_view->priv->row_header_data) + { + (* tree_view->priv->row_header_destroy) (tree_view->priv->row_header_data); + tree_view->priv->row_header_data = NULL; + } + + if (tree_view->priv->row_header_layout) + { + g_object_unref (tree_view->priv->row_header_layout); + tree_view->priv->row_header_layout = NULL; + } + + if (tree_view->priv->tickmark_icon) + { + g_object_unref (tree_view->priv->tickmark_icon); + tree_view->priv->tickmark_icon = NULL; + } + gtk_tree_view_set_model (tree_view, NULL); if (tree_view->priv->hadjustment) @@ -2036,7 +2271,7 @@ if (tree_view->priv->tree == NULL) tree_view->priv->height = 0; else - tree_view->priv->height = tree_view->priv->tree->root->offset; + tree_view->priv->height = tree_view->priv->tree->root->offset + tree_view->priv->rows_offset; } static void @@ -2228,6 +2463,13 @@ number_of_expand_columns++; } + if (tree_view->priv->hildon_mode == HILDON_FREMANTLE + && tree_view->priv->hildon_ui_mode == HILDON_UI_MODE_EDIT + && tree_view->priv->selection->type == GTK_SELECTION_MULTIPLE) + { + full_requested_width += HILDON_TICK_MARK_SIZE; + } + /* Only update the expand value if the width of the widget has changed, * or the number of expand columns has changed, or if there are no expand * columns, or if we didn't have an size-allocation yet after the @@ -2381,6 +2623,17 @@ allocation.y = child->y; allocation.width = child->width; allocation.height = child->height; + + if (tree_view->priv->rows_offset != 0 + && tree_view->priv->action_area_event_box == child->widget) + { + /* Set the child's location to be the area above the first row */ + allocation.x = 0; + allocation.y = -tree_view->priv->dy; + allocation.width = MAX (widget->allocation.width, tree_view->priv->width); + allocation.height = tree_view->priv->rows_offset; + } + gtk_widget_size_allocate (child->widget, &allocation); } @@ -2521,25 +2774,41 @@ static inline gboolean row_is_separator (GtkTreeView *tree_view, GtkTreeIter *iter, + gboolean *is_header, GtkTreePath *path) { gboolean is_separator = FALSE; + GtkTreeIter tmpiter; - if (tree_view->priv->row_separator_func) + if (tree_view->priv->row_separator_func + || tree_view->priv->row_header_func) { - GtkTreeIter tmpiter; - if (iter) - tmpiter = *iter; + tmpiter = *iter; else - { - if (!gtk_tree_model_get_iter (tree_view->priv->model, &tmpiter, path)) - return FALSE; - } + gtk_tree_model_get_iter (tree_view->priv->model, &tmpiter, path); + } - is_separator = tree_view->priv->row_separator_func (tree_view->priv->model, - &tmpiter, - tree_view->priv->row_separator_data); + if (tree_view->priv->row_separator_func) + { + is_separator = (* tree_view->priv->row_separator_func) (tree_view->priv->model, + &tmpiter, + tree_view->priv->row_separator_data); + } + + if (tree_view->priv->row_header_func) + { + gboolean tmp; + + tmp = (* tree_view->priv->row_header_func) (tree_view->priv->model, + &tmpiter, + NULL, + tree_view->priv->row_header_data); + + is_separator |= tmp; + + if (is_header) + *is_header = tmp; } return is_separator; @@ -2589,6 +2858,7 @@ gboolean row_double_click = FALSE; gboolean rtl; gboolean node_selected; + gboolean node_is_selectable; /* Empty tree? */ if (tree_view->priv->tree == NULL) @@ -2633,7 +2903,7 @@ /* Get the path and the node */ path = _gtk_tree_view_find_path (tree_view, tree, node); - path_is_selectable = !row_is_separator (tree_view, NULL, path); + path_is_selectable = !row_is_separator (tree_view, NULL, NULL, path); if (!path_is_selectable) { @@ -2689,6 +2959,17 @@ break; } + if (tree_view->priv->hildon_mode == HILDON_FREMANTLE + && tree_view->priv->hildon_ui_mode == HILDON_UI_MODE_EDIT + && tree_view->priv->selection->type == GTK_SELECTION_MULTIPLE + && (gint)event->x < background_area.x + HILDON_TICK_MARK_SIZE) + { + GList *list; + + list = (rtl ? g_list_first (tree_view->priv->columns) : g_list_last (tree_view->priv->columns)); + column = list->data; + } + if (column == NULL) { gtk_tree_path_free (path); @@ -2765,6 +3046,39 @@ gtk_tree_path_free (anchor); } + node_selected = GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_IS_SELECTED); + node_is_selectable = + _gtk_tree_selection_row_is_selectable (tree_view->priv->selection, + node, path); + + /* Save press to possibly begin a drag + */ + if (!column_handled_click && + !tree_view->priv->in_grab && + tree_view->priv->pressed_button < 0) + { + tree_view->priv->pressed_button = event->button; + tree_view->priv->press_start_x = event->x; + tree_view->priv->press_start_y = event->y; + + if (tree_view->priv->hildon_mode == HILDON_DIABLO + && tree_view->priv->rubber_banding_enable + && node_is_selectable + && !node_selected + && tree_view->priv->selection->type == GTK_SELECTION_MULTIPLE) + { + tree_view->priv->press_start_y += tree_view->priv->dy; + tree_view->priv->rubber_band_x = event->x; + tree_view->priv->rubber_band_y = event->y + tree_view->priv->dy; + tree_view->priv->rubber_band_status = RUBBER_BAND_MAYBE_START; + + if ((event->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK) + tree_view->priv->rubber_band_modify = TRUE; + if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) + tree_view->priv->rubber_band_extend = TRUE; + } + } + /* select */ node_selected = GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_IS_SELECTED); pre_val = tree_view->priv->vadjustment->value; @@ -2782,7 +3096,106 @@ if (focus_cell) gtk_tree_view_column_focus_cell (column, focus_cell); - if (event->state & GTK_MODIFY_SELECTION_MOD_MASK) + /* The most reliable way is to use another row reference, + * instead of trying to get it done with the intricate + * logic below. + */ + gtk_tree_row_reference_free (tree_view->priv->queued_tapped_row); + tree_view->priv->queued_tapped_row = + gtk_tree_row_reference_new (tree_view->priv->model, path); + + if (tree_view->priv->hildon_mode == HILDON_FREMANTLE + && tree_view->priv->hildon_ui_mode == HILDON_UI_MODE_NORMAL) + { + /* This row should be activated on button-release */ + gtk_tree_row_reference_free (tree_view->priv->queued_activate_row); + tree_view->priv->queued_activate_row = gtk_tree_row_reference_new (tree_view->priv->model, path); + + /* Mark the node as selected to create a highlight effect */ + tree_view->priv->highlighted_tree = tree; + tree_view->priv->highlighted_node = node; + gtk_tree_view_queue_draw_path (tree_view, path, NULL); + } + else if (tree_view->priv->hildon_mode == HILDON_DIABLO + && node_selected + && !column_handled_click + && gtk_tree_row_reference_valid (tree_view->priv->cursor)) + { + GtkTreePath *cursor_path; + + cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor); + if (!gtk_tree_path_compare (cursor_path, path)) + { + gtk_tree_row_reference_free (tree_view->priv->queued_activate_row); + tree_view->priv->queued_activate_row = gtk_tree_row_reference_new (tree_view->priv->model, path); + } + + gtk_tree_path_free (cursor_path); + } + + if (node_is_selectable + && tree_view->priv->hildon_mode == HILDON_DIABLO + && !column_handled_click + && !tree_view->priv->queued_activate_row + && tree_view->priv->rubber_band_status == RUBBER_BAND_OFF + && gtk_tree_selection_get_mode (tree_view->priv->selection) == GTK_SELECTION_MULTIPLE) + { + GtkTreePath *old_cursor_path = NULL; + + /* We do not know at this stage if a user is going to do + * a DnD or tap and hold operation, so avoid clearing + * the current selection. + */ + 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; + + /* Do move the 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); + + if (old_cursor_path) + { + gtk_tree_view_queue_draw_path (tree_view, + old_cursor_path, NULL); + gtk_tree_path_free (old_cursor_path); + } + + tree_view->priv->queued_ctrl_pressed = tree_view->priv->modify_selection_pressed; + tree_view->priv->queued_shift_pressed = tree_view->priv->extend_selection_pressed; + tree_view->priv->queued_select_row = + gtk_tree_row_reference_new (tree_view->priv->model, path); + + gtk_tree_view_queue_draw_path (tree_view, path, NULL); + } + else if (node_is_selectable + && tree_view->priv->hildon_mode == HILDON_FREMANTLE + && tree_view->priv->hildon_ui_mode == HILDON_UI_MODE_EDIT + && !column_handled_click + && !tree_view->priv->queued_activate_row) + { + /* In new-style we do not want to set cursor, + * instead we highlight the node. + */ + 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; + + tree_view->priv->highlighted_node = node; + tree_view->priv->highlighted_tree = tree; + + tree_view->priv->queued_select_row = + gtk_tree_row_reference_new (tree_view->priv->model, path); + + gtk_tree_view_queue_draw_path (tree_view, path, NULL); + } + /* Else, set the cursor as usual */ + else if (event->state & GTK_MODIFY_SELECTION_MOD_MASK) { gtk_tree_view_real_set_cursor (tree_view, path, FALSE, TRUE); gtk_tree_view_real_toggle_cursor_row (tree_view); @@ -2794,7 +3207,10 @@ } else { - gtk_tree_view_real_set_cursor (tree_view, path, TRUE, TRUE); + if (tree_view->priv->queued_activate_row) + gtk_tree_view_real_set_cursor (tree_view, path, FALSE, TRUE); + else + gtk_tree_view_real_set_cursor (tree_view, path, TRUE, TRUE); } tree_view->priv->modify_selection_pressed = FALSE; @@ -2811,74 +3227,24 @@ cell_area.y += dval; background_area.y += dval; - /* Save press to possibly begin a drag - */ - if (!column_handled_click && - !tree_view->priv->in_grab && - tree_view->priv->pressed_button < 0) - { - tree_view->priv->pressed_button = event->button; - tree_view->priv->press_start_x = event->x; - tree_view->priv->press_start_y = event->y; - - if (tree_view->priv->rubber_banding_enable - && !node_selected - && tree_view->priv->selection->type == GTK_SELECTION_MULTIPLE) + if (event->button == 1) + { + if (GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_IS_PARENT)) { - tree_view->priv->press_start_y += tree_view->priv->dy; - tree_view->priv->rubber_band_x = event->x; - tree_view->priv->rubber_band_y = event->y + tree_view->priv->dy; - tree_view->priv->rubber_band_status = RUBBER_BAND_MAYBE_START; - - if ((event->state & GTK_MODIFY_SELECTION_MOD_MASK) == GTK_MODIFY_SELECTION_MOD_MASK) - tree_view->priv->rubber_band_modify = TRUE; - if ((event->state & GTK_EXTEND_SELECTION_MOD_MASK) == GTK_EXTEND_SELECTION_MOD_MASK) - tree_view->priv->rubber_band_extend = TRUE; + /* The behavior is as follows: + * - For a tap on a collapsed node: always expand (and the + * cursor moves to it. + * - For a tap on an expxanded node: collapse if and only + * if the node is currently the cursor node. + */ + if (!node->children + || (node_selected && node->children)) + { + 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 && event->type == GDK_BUTTON_PRESS) - { - int double_click_time, double_click_distance; - - g_object_get (gtk_settings_get_default (), - "gtk-double-click-time", &double_click_time, - "gtk-double-click-distance", &double_click_distance, - NULL); - - /* Same conditions as _gdk_event_button_generate */ - if (tree_view->priv->last_button_x != -1 && - (event->time < tree_view->priv->last_button_time + double_click_time) && - (ABS (event->x - tree_view->priv->last_button_x) <= double_click_distance) && - (ABS (event->y - tree_view->priv->last_button_y) <= double_click_distance)) - { - /* We do no longer compare paths of this row and the - * row clicked previously. We use the double click - * distance to decide whether this is a valid click, - * allowing the mouse to slightly move over another row. - */ - row_double_click = TRUE; - - tree_view->priv->last_button_time = 0; - tree_view->priv->last_button_x = -1; - tree_view->priv->last_button_y = -1; - } - else - { - tree_view->priv->last_button_time = event->time; - tree_view->priv->last_button_x = event->x; - tree_view->priv->last_button_y = event->y; - } - } - - if (row_double_click) - { - gtk_grab_remove (widget); - gtk_tree_view_row_activated (tree_view, path, column); - - if (tree_view->priv->pressed_button == event->button) - tree_view->priv->pressed_button = -1; } gtk_tree_path_free (path); @@ -3039,6 +3405,9 @@ GdkEventButton *event) { GtkTreeView *tree_view = GTK_TREE_VIEW (widget); + gint new_y; + GtkRBTree *tree; + GtkRBNode *node; if (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_IN_COLUMN_DRAG)) return gtk_tree_view_button_release_drag_column (widget, event); @@ -3052,6 +3421,171 @@ 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 (tree_view->priv->tree) + { + /* Get the node where the mouse was released */ + new_y = TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, event->y); + if (new_y < 0) + new_y = 0; + _gtk_rbtree_find_offset (tree_view->priv->tree, new_y, &tree, &node); + } + else + { + /* We just set tree and node to NULL otherwise. We still want + * to run through below's logic to free row references where needed. + */ + tree = NULL; + node = NULL; + } + + if (gtk_tree_row_reference_valid (tree_view->priv->queued_select_row)) + { + GtkTreePath *path; + GtkRBTree *select_tree; + GtkRBNode *select_node; + + path = gtk_tree_row_reference_get_path (tree_view->priv->queued_select_row); + _gtk_tree_view_find_node (tree_view, path, + &select_tree, &select_node); + + if (tree == select_tree && node == select_node) + { + if (tree_view->priv->queued_ctrl_pressed) + { + gtk_tree_view_real_set_cursor (tree_view, path, FALSE, TRUE); + gtk_tree_view_real_toggle_cursor_row (tree_view); + GTK_TREE_VIEW_UNSET_FLAG (tree_view, GTK_TREE_VIEW_DRAW_KEYFOCUS); + } + else if (tree_view->priv->queued_shift_pressed) + { + gtk_tree_view_real_set_cursor (tree_view, path, FALSE, TRUE); + gtk_tree_view_real_select_cursor_row (tree_view, FALSE); + GTK_TREE_VIEW_UNSET_FLAG (tree_view, GTK_TREE_VIEW_DRAW_KEYFOCUS); + } + else + gtk_tree_view_real_set_cursor (tree_view, path, TRUE, TRUE); + } + + free_queued_select_row (tree_view); + gtk_tree_path_free (path); + tree_view->priv->queued_ctrl_pressed = FALSE; + tree_view->priv->queued_shift_pressed = FALSE; + } + + if (gtk_tree_row_reference_valid (tree_view->priv->queued_activate_row)) + { + GtkTreePath *path; + GtkRBTree *activate_tree; + GtkRBNode *activate_node; + + path = gtk_tree_row_reference_get_path (tree_view->priv->queued_activate_row); + + if (tree_view->priv->hildon_mode == HILDON_FREMANTLE + && tree_view->priv->hildon_ui_mode == HILDON_UI_MODE_NORMAL) + { + if (tree_view->priv->highlighted_node) + { + _gtk_tree_view_queue_draw_node (tree_view, + tree_view->priv->highlighted_tree, + tree_view->priv->highlighted_node, + NULL); + + tree_view->priv->highlighted_tree = NULL; + tree_view->priv->highlighted_node = NULL; + } + } + + _gtk_tree_view_find_node (tree_view, path, + &activate_tree, &activate_node); + + /* Only emit activated if the mouse was released from the + * same row where the mouse was pressed. + */ + if (tree == activate_tree && node == activate_node) + { + gtk_tree_view_row_activated (tree_view, path, + tree_view->priv->focus_column); + } + + gtk_tree_path_free (path); + + gtk_tree_row_reference_free (tree_view->priv->queued_activate_row); + tree_view->priv->queued_activate_row = NULL; + } + + if (gtk_tree_row_reference_valid (tree_view->priv->queued_expand_row)) + { + GtkTreePath *path; + GtkRBTree *expand_tree; + GtkRBNode *expand_node = NULL; + + path = gtk_tree_row_reference_get_path (tree_view->priv->queued_expand_row); + + if (tree_view->priv->hildon_mode == HILDON_FREMANTLE) + { + /* We should not take the cursor into accont. We do check + * with the node where the mouse was released. + */ + _gtk_tree_view_find_node (tree_view, path, + &expand_tree, &expand_node); + + if (tree != expand_tree || node != expand_node) + expand_node = NULL; + } + else + { + GtkTreePath *cursor_path; + + cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor); + + if (!gtk_tree_path_compare (cursor_path, path)) + _gtk_tree_view_find_node (tree_view, path, + &expand_tree, &expand_node); + + gtk_tree_path_free (cursor_path); + } + + if (expand_node) + { + if (!expand_node->children) + gtk_tree_view_real_expand_row (tree_view, path, + expand_tree, expand_node, + FALSE, TRUE); + else + gtk_tree_view_real_collapse_row (tree_view, path, + expand_tree, expand_node, TRUE); + } + + gtk_tree_path_free (path); + + gtk_tree_row_reference_free (tree_view->priv->queued_expand_row); + tree_view->priv->queued_expand_row = NULL; + } + + /* The hildon-row-tapped signal is executed as the last, so that + * any action (selection change, activation, expansion/collapse) + * has already been processed. + */ + if (gtk_tree_row_reference_valid (tree_view->priv->queued_tapped_row)) + { + GtkTreePath *path; + GtkRBTree *tapped_tree; + GtkRBNode *tapped_node; + + path = gtk_tree_row_reference_get_path (tree_view->priv->queued_tapped_row); + _gtk_tree_view_find_node (tree_view, path, + &tapped_tree, &tapped_node); + + if (tree == tapped_tree && node == tapped_node) + g_signal_emit (tree_view, tree_view_signals[HILDON_ROW_TAPPED], + 0, path); + + gtk_tree_path_free (path); + + gtk_tree_row_reference_free (tree_view->priv->queued_tapped_row); + tree_view->priv->queued_tapped_row = NULL; + } + if (tree_view->priv->button_pressed_node == NULL) return FALSE; @@ -3331,6 +3865,22 @@ } static void +ensure_unhighlighted (GtkTreeView *tree_view) +{ + /* Unconditionally unhighlight */ + if (tree_view->priv->highlighted_node) + { + _gtk_tree_view_queue_draw_node (tree_view, + tree_view->priv->highlighted_tree, + tree_view->priv->highlighted_node, + NULL); + + tree_view->priv->highlighted_tree = NULL; + tree_view->priv->highlighted_node = NULL; + } +} + +static void update_prelight (GtkTreeView *tree_view, gint x, gint y) @@ -3780,13 +4330,6 @@ gtk_tree_path_free (tmp_path); - /* ... and the cursor to the end path */ - tmp_path = _gtk_tree_view_find_path (tree_view, - tree_view->priv->rubber_band_end_tree, - tree_view->priv->rubber_band_end_node); - gtk_tree_view_real_set_cursor (GTK_TREE_VIEW (tree_view), tmp_path, FALSE, FALSE); - gtk_tree_path_free (tmp_path); - _gtk_tree_selection_emit_changed (tree_view->priv->selection); } @@ -3868,6 +4411,8 @@ GTK_RBNODE_UNSET_FLAG (start_node, GTK_RBNODE_IS_SELECTED); } + add_scroll_timeout (tree_view); + _gtk_tree_view_queue_draw_node (tree_view, start_tree, start_node, NULL); node_not_selectable: @@ -3903,6 +4448,7 @@ { GtkRBTree *start_tree, *end_tree; GtkRBNode *start_node, *end_node; + GtkTreePath *path; _gtk_rbtree_find_offset (tree_view->priv->tree, MIN (tree_view->priv->press_start_y, tree_view->priv->rubber_band_y), &start_tree, &start_node); _gtk_rbtree_find_offset (tree_view->priv->tree, MAX (tree_view->priv->press_start_y, tree_view->priv->rubber_band_y), &end_tree, &end_node); @@ -4001,6 +4547,17 @@ tree_view->priv->rubber_band_end_tree = end_tree; tree_view->priv->rubber_band_end_node = end_node; + + /* In maemo the cursor needs to follow the stylus */ + if (gtk_tree_view_get_path_at_pos (tree_view, + tree_view->priv->rubber_band_x, + RBTREE_Y_TO_TREE_WINDOW_Y (tree_view, tree_view->priv->rubber_band_y), + &path, + NULL, NULL, NULL)) + { + gtk_tree_view_real_set_cursor (tree_view, path, FALSE, FALSE); + gtk_tree_path_free (path); + } } static void @@ -4009,8 +4566,6 @@ gint x, y; GdkRectangle old_area; GdkRectangle new_area; - GdkRectangle common; - GdkRegion *invalid_region; old_area.x = MIN (tree_view->priv->press_start_x, tree_view->priv->rubber_band_x); old_area.y = MIN (tree_view->priv->press_start_y, tree_view->priv->rubber_band_y) - tree_view->priv->dy; @@ -4027,30 +4582,6 @@ new_area.width = ABS (x - tree_view->priv->press_start_x) + 1; new_area.height = ABS (y - tree_view->priv->press_start_y) + 1; - invalid_region = gdk_region_rectangle (&old_area); - gdk_region_union_with_rect (invalid_region, &new_area); - - gdk_rectangle_intersect (&old_area, &new_area, &common); - if (common.width > 2 && common.height > 2) - { - GdkRegion *common_region; - - /* make sure the border is invalidated */ - common.x += 1; - common.y += 1; - common.width -= 2; - common.height -= 2; - - common_region = gdk_region_rectangle (&common); - - gdk_region_subtract (invalid_region, common_region); - gdk_region_destroy (common_region); - } - - gdk_window_invalidate_region (tree_view->priv->bin_window, invalid_region, TRUE); - - gdk_region_destroy (invalid_region); - tree_view->priv->rubber_band_x = x; tree_view->priv->rubber_band_y = y; @@ -4115,6 +4646,20 @@ if (tree_view->priv->rubber_band_status == RUBBER_BAND_MAYBE_START) { + if (tree_view->priv->hildon_mode == HILDON_DIABLO + && gtk_tree_row_reference_valid (tree_view->priv->queued_select_row)) + { + GtkTreePath *path; + + /* We now know we won't rubber band -- select the row */ + path = gtk_tree_row_reference_get_path (tree_view->priv->queued_select_row); + gtk_tree_view_real_set_cursor (tree_view, path, FALSE, FALSE); + + gtk_tree_path_free (path); + } + + free_queued_actions (tree_view); + gtk_grab_add (GTK_WIDGET (tree_view)); gtk_tree_view_update_rubber_band (tree_view); @@ -4127,8 +4672,27 @@ add_scroll_timeout (tree_view); } + /* If the drag-threshold has been passed, the row should + * not be activated or selected and the highlight removed. + */ + if (tree_view->priv->hildon_mode == HILDON_FREMANTLE + && gtk_drag_check_threshold (widget, + tree_view->priv->press_start_x, + tree_view->priv->press_start_y, + event->x, event->y)) + { + if (tree_view->priv->hildon_ui_mode == HILDON_UI_MODE_NORMAL) + free_queued_activate_row (tree_view); + else if (tree_view->priv->hildon_ui_mode == HILDON_UI_MODE_EDIT) + free_queued_select_row (tree_view); + + gtk_tree_row_reference_free (tree_view->priv->queued_tapped_row); + tree_view->priv->queued_tapped_row = NULL; + } + /* only check for an initiated drag when a button is pressed */ if (tree_view->priv->pressed_button >= 0 + && tree_view->priv->hildon_mode == HILDON_DIABLO && !tree_view->priv->rubber_band_status) gtk_tree_view_maybe_begin_dragging_row (tree_view, event); @@ -4310,6 +4874,46 @@ } } +static void +gtk_tree_view_draw_row_header (GtkTreeView *tree_view, + GtkTreeIter *iter, + GdkEventExpose *event, + GdkRectangle *cell_area) +{ + gchar *label = NULL; + int width, height; + gboolean is_header; + GtkWidget *widget = GTK_WIDGET (tree_view); + + g_return_if_fail (tree_view->priv->row_header_func != NULL); + + is_header = (* tree_view->priv->row_header_func) (tree_view->priv->model, + iter, + &label, + tree_view->priv->row_header_data); + + g_return_if_fail (is_header == TRUE); + g_return_if_fail (tree_view->priv->row_header_layout != NULL); + + pango_layout_set_text (tree_view->priv->row_header_layout, + label, strlen (label)); + pango_layout_get_pixel_size (tree_view->priv->row_header_layout, + &width, &height); + + gtk_paint_layout (widget->style, + event->window, + widget->state, + TRUE, + &event->area, + widget, + "treeview-group-header", + cell_area->x + (cell_area->width - width) / 2, + cell_area->y + cell_area->height - height, + tree_view->priv->row_header_layout); + + g_free (label); +} + /* Warning: Very scary function. * Modify at your own risk * @@ -4356,6 +4960,8 @@ gboolean got_pointer = FALSE; gboolean row_ending_details; gboolean draw_vgrid_lines, draw_hgrid_lines; + gint expose_start; + gboolean render_checkboxes = FALSE; rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL); @@ -4379,11 +4985,27 @@ validate_visible_area (tree_view); - new_y = TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, event->area.y); + if (G_UNLIKELY (tree_view->priv->rows_offset != 0) + && tree_view->priv->dy <= tree_view->priv->rows_offset + && event->area.y <= tree_view->priv->rows_offset - tree_view->priv->dy) + { + /* As long as a part of the button window is visible ... */ + expose_start = tree_view->priv->rows_offset - tree_view->priv->dy; + y_offset = -_gtk_rbtree_find_offset (tree_view->priv->tree, + tree_view->priv->rows_offset, + &tree, &node); + } + else + { + new_y = TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, event->area.y); + + if (new_y < 0) + new_y = 0; + + expose_start = event->area.y; + y_offset = -_gtk_rbtree_find_offset (tree_view->priv->tree, new_y, &tree, &node); + } - if (new_y < 0) - new_y = 0; - y_offset = -_gtk_rbtree_find_offset (tree_view->priv->tree, new_y, &tree, &node); bin_window_width = gdk_window_get_width (tree_view->priv->bin_window); bin_window_height = gdk_window_get_height (tree_view->priv->bin_window); @@ -4395,7 +5017,7 @@ GTK_SHADOW_NONE, &event->area, widget, - "cell_even", + "cell_blank", 0, tree_view->priv->height, bin_window_width, bin_window_height - tree_view->priv->height); @@ -4449,6 +5071,13 @@ n_visible_columns ++; } + if (tree_view->priv->hildon_mode == HILDON_FREMANTLE + && tree_view->priv->hildon_ui_mode == HILDON_UI_MODE_EDIT + && tree_view->priv->selection->type == GTK_SELECTION_MULTIPLE) + { + render_checkboxes = TRUE; + } + /* Find the last column */ for (last_column = g_list_last (tree_view->priv->columns); last_column && !(GTK_TREE_VIEW_COLUMN (last_column->data)->visible); @@ -4472,8 +5101,9 @@ gboolean is_separator = FALSE; gboolean is_first = FALSE; gboolean is_last = FALSE; + gboolean is_header = FALSE; - is_separator = row_is_separator (tree_view, &iter, NULL); + is_separator = row_is_separator (tree_view, &iter, &is_header, NULL); max_height = ROW_HEIGHT (tree_view, BACKGROUND_HEIGHT (node)); @@ -4481,7 +5111,7 @@ highlight_x = 0; /* should match x coord of first cell */ expander_cell_width = 0; - background_area.y = y_offset + event->area.y; + background_area.y = y_offset + expose_start; background_area.height = max_height; flags = 0; @@ -4489,7 +5119,8 @@ if (GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_IS_PRELIT)) flags |= GTK_CELL_RENDERER_PRELIT; - if (GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_IS_SELECTED)) + if (GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_IS_SELECTED) + || node == tree_view->priv->highlighted_node) flags |= GTK_CELL_RENDERER_SELECTED; parity = _gtk_rbtree_node_find_parity (tree, node); @@ -4503,12 +5134,13 @@ list = (rtl ? list->prev : list->next)) { GtkTreeViewColumn *column = list->data; - gtk_tree_view_column_cell_set_cell_data (column, - tree_view->priv->model, - &iter, - GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_IS_PARENT), - node->children?TRUE:FALSE); - } + gtk_tree_view_column_cell_set_cell_data_with_hint (column, + tree_view->priv->model, + &iter, + GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_IS_PARENT), + node->children?TRUE:FALSE, + GTK_TREE_CELL_DATA_HINT_KEY_FOCUS); + } has_special_cell = gtk_tree_view_has_special_cell (tree_view); @@ -4543,7 +5175,11 @@ background_area.x = cell_offset; background_area.width = column->width; - cell_area = background_area; + /* Nasty hack to get background handling for free */ + if (render_checkboxes && column == last_column->data) + background_area.width += HILDON_TICK_MARK_SIZE; + + cell_area = background_area; cell_area.y += vertical_separator / 2; cell_area.x += horizontal_separator / 2; cell_area.height -= vertical_separator; @@ -4682,6 +5318,93 @@ background_area.height); } + /* Nasty hack to get background handling for free */ + if (render_checkboxes && column == last_column->data) + { + background_area.width -= HILDON_TICK_MARK_SIZE; + cell_area.width -= HILDON_TICK_MARK_SIZE; + + if (GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_IS_SELECTED)) + gdk_draw_pixbuf (event->window, + NULL, + tree_view->priv->tickmark_icon, + 0, 0, + background_area.x + background_area.width, + background_area.y + (background_area.height - HILDON_TICK_MARK_SIZE) / 2, + HILDON_TICK_MARK_SIZE, + HILDON_TICK_MARK_SIZE, + GDK_RGB_DITHER_MAX, + 0, 0); + } + + if (node == drag_highlight) + { + /* Draw indicator for the drop + */ + gint highlight_y = -1; + GtkRBTree *tree = NULL; + GtkRBNode *node = NULL; + gint width; + + switch (tree_view->priv->drag_dest_pos) + { + case GTK_TREE_VIEW_DROP_BEFORE: + highlight_y = background_area.y - 1; + if (highlight_y < 0) + highlight_y = 0; + break; + + case GTK_TREE_VIEW_DROP_AFTER: + highlight_y = background_area.y + background_area.height - 1; + break; + + case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE: + case GTK_TREE_VIEW_DROP_INTO_OR_AFTER: + _gtk_tree_view_find_node (tree_view, drag_dest_path, &tree, &node); + + if (tree == NULL) + break; + gdk_drawable_get_size (tree_view->priv->bin_window, + &width, NULL); + + if (row_ending_details) + gtk_paint_focus (widget->style, + tree_view->priv->bin_window, + gtk_widget_get_state (widget), + &event->area, + widget, + (is_first + ? (is_last ? "treeview-drop-indicator" : "treeview-drop-indicator-left" ) + : (is_last ? "treeview-drop-indicator-right" : "tree-view-drop-indicator-middle" )), + 0, BACKGROUND_FIRST_PIXEL (tree_view, tree, node) + - focus_line_width / 2, + width, ROW_HEIGHT (tree_view, BACKGROUND_HEIGHT (node)) + - focus_line_width + 1); + else + gtk_paint_focus (widget->style, + tree_view->priv->bin_window, + gtk_widget_get_state (widget), + &event->area, + widget, + "treeview-drop-indicator", + 0, BACKGROUND_FIRST_PIXEL (tree_view, tree, node) + - focus_line_width / 2, + width, ROW_HEIGHT (tree_view, BACKGROUND_HEIGHT (node)) + - focus_line_width + 1); + break; + } + + if (highlight_y >= 0) + { + gdk_draw_line (event->window, + widget->style->fg_gc[gtk_widget_get_state (widget)], + rtl ? highlight_x + expander_cell_width : highlight_x, + highlight_y, + rtl ? 0 : bin_window_width, + highlight_y); + } + } + if (gtk_tree_view_is_expander_column (tree_view, column)) { if (!rtl) @@ -4702,7 +5425,14 @@ highlight_x = cell_area.x; expander_cell_width = cell_area.width; - if (is_separator) + if (is_header) + { + gtk_tree_view_draw_row_header (tree_view, + &iter, + event, + &cell_area); + } + else if (is_separator) gtk_paint_hline (widget->style, event->window, state, @@ -4737,7 +5467,14 @@ } else { - if (is_separator) + if (is_header) + { + gtk_tree_view_draw_row_header (tree_view, + &iter, + event, + &cell_area); + } + else if (is_separator) gtk_paint_hline (widget->style, event->window, state, @@ -4862,73 +5599,6 @@ cell_offset += column->width; } - if (node == drag_highlight) - { - /* Draw indicator for the drop - */ - gint highlight_y = -1; - GtkRBTree *tree = NULL; - GtkRBNode *node = NULL; - gint width; - - switch (tree_view->priv->drag_dest_pos) - { - case GTK_TREE_VIEW_DROP_BEFORE: - highlight_y = background_area.y - 1; - if (highlight_y < 0) - highlight_y = 0; - break; - - case GTK_TREE_VIEW_DROP_AFTER: - highlight_y = background_area.y + background_area.height - 1; - break; - - case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE: - case GTK_TREE_VIEW_DROP_INTO_OR_AFTER: - _gtk_tree_view_find_node (tree_view, drag_dest_path, &tree, &node); - - if (tree == NULL) - break; - width = gdk_window_get_width (tree_view->priv->bin_window); - - if (row_ending_details) - gtk_paint_focus (widget->style, - tree_view->priv->bin_window, - gtk_widget_get_state (widget), - &event->area, - widget, - (is_first - ? (is_last ? "treeview-drop-indicator" : "treeview-drop-indicator-left" ) - : (is_last ? "treeview-drop-indicator-right" : "tree-view-drop-indicator-middle" )), - 0, BACKGROUND_FIRST_PIXEL (tree_view, tree, node) - - focus_line_width / 2, - width, ROW_HEIGHT (tree_view, BACKGROUND_HEIGHT (node)) - - focus_line_width + 1); - else - gtk_paint_focus (widget->style, - tree_view->priv->bin_window, - gtk_widget_get_state (widget), - &event->area, - widget, - "treeview-drop-indicator", - 0, BACKGROUND_FIRST_PIXEL (tree_view, tree, node) - - focus_line_width / 2, - width, ROW_HEIGHT (tree_view, BACKGROUND_HEIGHT (node)) - - focus_line_width + 1); - break; - } - - if (highlight_y >= 0) - { - gtk_tree_view_draw_line (tree_view, event->window, - GTK_TREE_VIEW_FOREGROUND_LINE, - rtl ? highlight_x + expander_cell_width : highlight_x, - highlight_y, - rtl ? 0 : bin_window_width, - highlight_y); - } - } - /* 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) && @@ -5042,21 +5712,6 @@ done: gtk_tree_view_draw_grid_lines (tree_view, event, n_visible_columns); - if (tree_view->priv->rubber_band_status == RUBBER_BAND_ACTIVE) - { - GdkRectangle *rectangles; - gint n_rectangles; - - gdk_region_get_rectangles (event->region, - &rectangles, - &n_rectangles); - - while (n_rectangles--) - gtk_tree_view_paint_rubber_band (tree_view, &rectangles[n_rectangles]); - - g_free (rectangles); - } - if (cursor_path) gtk_tree_path_free (cursor_path); @@ -5504,6 +6159,15 @@ * the typeahead find capabilities. */ if (gtk_widget_has_focus (GTK_WIDGET (tree_view)) && tree_view->priv->enable_search + /* These are usually handled via keybindings, but these do not + * function in Fremantle mode. Therefore we need to explicitly + * check for these there. + */ + && event->keyval != GDK_ISO_Enter + && event->keyval != GDK_KP_Enter + && event->keyval != GDK_Return + && event->keyval != GDK_space + && event->keyval != GDK_KP_Space && !tree_view->priv->search_custom_entry_set) { GdkEvent *new_event; @@ -5730,13 +6394,15 @@ gint grid_line_width; gboolean wide_separators; gint separator_height; + gint row_height; + gboolean is_header = FALSE; /* double check the row needs validating */ if (! GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_INVALID) && ! GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_COLUMN_INVALID)) return FALSE; - is_separator = row_is_separator (tree_view, iter, NULL); + is_separator = row_is_separator (tree_view, iter, &is_header, NULL); gtk_widget_style_get (GTK_WIDGET (tree_view), "focus-padding", &focus_pad, @@ -5746,6 +6412,7 @@ "grid-line-width", &grid_line_width, "wide-separators", &wide_separators, "separator-height", &separator_height, + "row-height", &row_height, NULL); draw_vgrid_lines = @@ -5793,10 +6460,12 @@ } else { - if (wide_separators) - height = separator_height + 2 * focus_pad; - else - height = 2 + 2 * focus_pad; + if (is_header) + { + height = HILDON_ROW_HEADER_HEIGHT; + } + else + height = separator_height + 2 * focus_pad; } if (gtk_tree_view_is_expander_column (tree_view, column)) @@ -5817,6 +6486,14 @@ tmp_width += grid_line_width; } + if (tree_view->priv->hildon_mode == HILDON_FREMANTLE + && tree_view->priv->hildon_ui_mode == HILDON_UI_MODE_EDIT + && tree_view->priv->selection->type == GTK_SELECTION_MULTIPLE) + { + tmp_width += HILDON_TICK_MARK_SIZE; + height = MAX (height, HILDON_TICK_MARK_SIZE); + } + if (tmp_width > column->requested_width) { retval = TRUE; @@ -5827,6 +6504,9 @@ if (draw_hgrid_lines) height += grid_line_width; + if (row_height != -1 && !is_separator && !is_header) + height = row_height; + if (height != GTK_RBNODE_GET_HEIGHT (node)) { retval = TRUE; @@ -6180,33 +6860,166 @@ { GtkRequisition requisition; - /* We temporarily guess a size, under the assumption that it will be the - * same when we get our next size_allocate. If we don't do this, we'll be - * in an inconsistent state if we call top_row_to_dy. */ + /* We temporarily guess a size, under the assumption that it will be the + * same when we get our next size_allocate. If we don't do this, we'll be + * in an inconsistent state if we call top_row_to_dy. */ + + gtk_widget_size_request (GTK_WIDGET (tree_view), &requisition); + tree_view->priv->hadjustment->upper = MAX (tree_view->priv->hadjustment->upper, (gfloat)requisition.width); + tree_view->priv->vadjustment->upper = MAX (tree_view->priv->vadjustment->upper, (gfloat)requisition.height); + gtk_adjustment_changed (tree_view->priv->hadjustment); + gtk_adjustment_changed (tree_view->priv->vadjustment); + gtk_widget_queue_resize (GTK_WIDGET (tree_view)); + } + + if (tree_view->priv->scroll_to_path) + { + gtk_tree_row_reference_free (tree_view->priv->scroll_to_path); + tree_view->priv->scroll_to_path = NULL; + } + + if (above_path) + gtk_tree_path_free (above_path); + + if (tree_view->priv->scroll_to_column) + { + tree_view->priv->scroll_to_column = NULL; + } + if (need_redraw) + gtk_widget_queue_draw (GTK_WIDGET (tree_view)); +} + +/* You can pass in focus_pad, separator_height to save + * calls to gtk_widget_style_get() (especially for + * gtk_tree_view_nodes_set_fixed_height). + */ +static inline int +determine_row_height (GtkTreeView *tree_view, + GtkTreeIter *iter, + int focus_pad, + int separator_height) +{ + int height; + gboolean is_separator, is_header = FALSE; + + /* Determine the correct height for this node. This is + * analogous to what is found in validate_row(). + */ + is_separator = row_is_separator (tree_view, iter, &is_header, NULL); + + if (!is_separator) + height = tree_view->priv->fixed_height; + else if (is_header) + height = HILDON_ROW_HEADER_HEIGHT; + else + { + if (focus_pad == -1 || separator_height == -1) + gtk_widget_style_get (GTK_WIDGET (tree_view), + "focus-padding", &focus_pad, + "separator-height", &separator_height, + NULL); + + height = separator_height + 2 * focus_pad; + } + + return height; +} + +/* This function is basically an extended and non-recursive version of + * _gtk_rbtree_set_fixed_height() which also keeps track of the current + * iter. We can then pass this iter in to the row separator and + * row header funcs. + */ +static void +gtk_tree_view_nodes_set_fixed_height (GtkTreeView *tree_view) +{ + int focus_pad; + int separator_height; + GtkRBTree *tree; + GtkRBNode *node; + GtkTreeIter iter; + + tree = tree_view->priv->tree; + + if (tree == NULL) + return; + + node = tree->root; + g_assert (node); + + /* Rewind tree, we start at the first node */ + while (node->left != tree->nil) + node = node->left; + + gtk_tree_model_get_iter_first (tree_view->priv->model, &iter); + + gtk_widget_style_get (GTK_WIDGET (tree_view), + "focus-padding", &focus_pad, + "separator-height", &separator_height, + NULL); + + /* This loop is equivalent to the one in bin_expose, but with + * the extra assertions removed. + */ + do + { + if (GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_INVALID)) + { + int height; + + height = determine_row_height (tree_view, &iter, + focus_pad, separator_height); + + _gtk_rbtree_node_set_height (tree, node, height); + _gtk_rbtree_node_mark_valid (tree, node); + } + + if (node->children) + { + GtkTreeIter parent = iter; + + tree = node->children; + node = tree->root; + + g_assert (node != tree->nil); - gtk_widget_size_request (GTK_WIDGET (tree_view), &requisition); - tree_view->priv->hadjustment->upper = MAX (tree_view->priv->hadjustment->upper, (gfloat)requisition.width); - tree_view->priv->vadjustment->upper = MAX (tree_view->priv->vadjustment->upper, (gfloat)requisition.height); - gtk_adjustment_changed (tree_view->priv->hadjustment); - gtk_adjustment_changed (tree_view->priv->vadjustment); - gtk_widget_queue_resize (GTK_WIDGET (tree_view)); - } + while (node->left != tree->nil) + node = node->left; - if (tree_view->priv->scroll_to_path) - { - gtk_tree_row_reference_free (tree_view->priv->scroll_to_path); - tree_view->priv->scroll_to_path = NULL; - } + gtk_tree_model_iter_children (tree_view->priv->model, + &iter, &parent); + } + else + { + gboolean done = FALSE; - if (above_path) - gtk_tree_path_free (above_path); + do + { + node = _gtk_rbtree_next (tree, node); + if (node != NULL) + { + gtk_tree_model_iter_next (tree_view->priv->model, &iter); + done = TRUE; + } + else + { + GtkTreeIter parent_iter = iter; - if (tree_view->priv->scroll_to_column) - { - tree_view->priv->scroll_to_column = NULL; + node = tree->parent_node; + tree = tree->parent_tree; + + if (!tree) + /* We are done */ + return; + + gtk_tree_model_iter_parent (tree_view->priv->model, + &iter, &parent_iter); + } + } + while (!done); + } } - if (need_redraw) - gtk_widget_queue_draw (GTK_WIDGET (tree_view)); + while (TRUE); } static void @@ -6227,6 +7040,29 @@ node = tree->root; path = _gtk_tree_view_find_path (tree_view, tree, node); + + /* Search for the first regular row */ + while (node) + { + gboolean is_header = FALSE, is_separator; + + is_separator = row_is_separator (tree_view, NULL, &is_header, path); + if (!is_separator && !is_header) + break; + + _gtk_rbtree_next_full (tree, node, &tree, &node); + + gtk_tree_path_free (path); + + if (node) + path = _gtk_tree_view_find_path (tree_view, tree, node); + else + path = NULL; + } + + if (!path) + return; + gtk_tree_model_get_iter (tree_view->priv->model, &iter, path); validate_row (tree_view, tree, node, &iter, path); @@ -6236,8 +7072,16 @@ tree_view->priv->fixed_height = ROW_HEIGHT (tree_view, GTK_RBNODE_GET_HEIGHT (node)); } - _gtk_rbtree_set_fixed_height (tree_view->priv->tree, - tree_view->priv->fixed_height, TRUE); + /* If a separator or row header func has been set, we cannot + * uniformly set the same height on all rows. We fall back to + * a slower alternative. + */ + if (tree_view->priv->row_separator_func + || tree_view->priv->row_header_func) + gtk_tree_view_nodes_set_fixed_height (tree_view); + else + _gtk_rbtree_set_fixed_height (tree_view->priv->tree, + tree_view->priv->fixed_height, TRUE); } /* Our strategy for finding nodes to validate is a little convoluted. We find @@ -7206,6 +8050,9 @@ set_source_row (context, model, path); + /* Clear pending actions on row */ + free_queued_actions (tree_view); + out: if (path) gtk_tree_path_free (path); @@ -7708,12 +8555,20 @@ gtk_tree_view_has_special_cell (GtkTreeView *tree_view) { GList *list; + guint n_specials = 0; for (list = tree_view->priv->columns; list; list = list->next) { if (!((GtkTreeViewColumn *)list->data)->visible) continue; - if (_gtk_tree_view_column_count_special_cells (list->data)) + /* We return true if there is more than one special cell. Since + * we do not want to have the per-cell focus rectangles when there + * is only a single activatable cell, we return FALSE for + * n_specials == 1 + */ + n_specials += _gtk_tree_view_column_count_special_cells (list->data); + + if (n_specials > 1) return TRUE; } @@ -7813,148 +8668,11 @@ GtkDirectionType dir, gboolean clamp_column_visible) { - GtkWidget *focus_child; - - GList *last_column, *first_column; - GList *tmp_list; - gboolean rtl; - - if (! GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_HEADERS_VISIBLE)) - return FALSE; - - focus_child = GTK_CONTAINER (tree_view)->focus_child; - - first_column = tree_view->priv->columns; - while (first_column) - { - if (gtk_widget_get_can_focus (GTK_TREE_VIEW_COLUMN (first_column->data)->button) && - GTK_TREE_VIEW_COLUMN (first_column->data)->visible && - (GTK_TREE_VIEW_COLUMN (first_column->data)->clickable || - GTK_TREE_VIEW_COLUMN (first_column->data)->reorderable)) - break; - first_column = first_column->next; - } - - /* No headers are visible, or are focusable. We can't focus in or out. - */ - if (first_column == NULL) - return FALSE; - - last_column = g_list_last (tree_view->priv->columns); - while (last_column) - { - if (gtk_widget_get_can_focus (GTK_TREE_VIEW_COLUMN (last_column->data)->button) && - GTK_TREE_VIEW_COLUMN (last_column->data)->visible && - (GTK_TREE_VIEW_COLUMN (last_column->data)->clickable || - GTK_TREE_VIEW_COLUMN (last_column->data)->reorderable)) - break; - last_column = last_column->prev; - } - - - rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL); - - switch (dir) - { - case GTK_DIR_TAB_BACKWARD: - case GTK_DIR_TAB_FORWARD: - case GTK_DIR_UP: - case GTK_DIR_DOWN: - if (focus_child == NULL) - { - if (tree_view->priv->focus_column != NULL && - gtk_widget_get_can_focus (tree_view->priv->focus_column->button)) - focus_child = tree_view->priv->focus_column->button; - else - focus_child = GTK_TREE_VIEW_COLUMN (first_column->data)->button; - gtk_widget_grab_focus (focus_child); - break; - } - return FALSE; - - case GTK_DIR_LEFT: - case GTK_DIR_RIGHT: - if (focus_child == NULL) - { - if (tree_view->priv->focus_column != NULL) - focus_child = tree_view->priv->focus_column->button; - else if (dir == GTK_DIR_LEFT) - focus_child = GTK_TREE_VIEW_COLUMN (last_column->data)->button; - else - focus_child = GTK_TREE_VIEW_COLUMN (first_column->data)->button; - gtk_widget_grab_focus (focus_child); - break; - } - - if (gtk_widget_child_focus (focus_child, dir)) - { - /* The focus moves inside the button. */ - /* This is probably a great example of bad UI */ - break; - } - - /* We need to move the focus among the row of buttons. */ - for (tmp_list = tree_view->priv->columns; tmp_list; tmp_list = tmp_list->next) - if (GTK_TREE_VIEW_COLUMN (tmp_list->data)->button == focus_child) - break; - - if ((tmp_list == first_column && dir == (rtl ? GTK_DIR_RIGHT : GTK_DIR_LEFT)) - || (tmp_list == last_column && dir == (rtl ? GTK_DIR_LEFT : GTK_DIR_RIGHT))) - { - gtk_widget_error_bell (GTK_WIDGET (tree_view)); - break; - } - - while (tmp_list) - { - GtkTreeViewColumn *column; - - if (dir == (rtl ? GTK_DIR_LEFT : GTK_DIR_RIGHT)) - tmp_list = tmp_list->next; - else - tmp_list = tmp_list->prev; - - if (tmp_list == NULL) - { - g_warning ("Internal button not found"); - break; - } - column = tmp_list->data; - if (column->button && - column->visible && - gtk_widget_get_can_focus (column->button)) - { - focus_child = column->button; - gtk_widget_grab_focus (column->button); - break; - } - } - break; - default: - g_assert_not_reached (); - break; - } - - /* if focus child is non-null, we assume it's been set to the current focus child + /* Skip headers when acquiring focus; behave the same as the headers + * are invisible. */ - if (focus_child) - { - for (tmp_list = tree_view->priv->columns; tmp_list; tmp_list = tmp_list->next) - if (GTK_TREE_VIEW_COLUMN (tmp_list->data)->button == focus_child) - { - tree_view->priv->focus_column = GTK_TREE_VIEW_COLUMN (tmp_list->data); - break; - } - - if (clamp_column_visible) - { - gtk_tree_view_clamp_column_visible (tree_view, - tree_view->priv->focus_column, - FALSE); - } - } - return (focus_child != NULL); + return FALSE; } /* This function returns in 'path' the first focusable path, if the given path @@ -7978,7 +8696,9 @@ if (!tree || !node) return FALSE; - while (node && row_is_separator (tree_view, NULL, *path)) + while (node && + !_gtk_tree_selection_row_is_selectable (tree_view->priv->selection, + node, *path)) { if (search_forward) _gtk_rbtree_next_full (tree, node, &tree, &node); @@ -8097,6 +8817,31 @@ tree_view->priv->fixed_height = -1; _gtk_rbtree_mark_invalid (tree_view->priv->tree); + /* Cache the hildon-mode because it is slow to call gtk_widget_style_get + * too much. */ + gtk_widget_style_get (GTK_WIDGET (tree_view), + "hildon-mode", &tree_view->priv->hildon_mode, + NULL); + + /* Reset the UI mode */ + hildon_tree_view_set_hildon_ui_mode (tree_view, + tree_view->priv->hildon_ui_mode); + + if (tree_view->priv->row_header_layout) + hildon_tree_view_setup_row_header_layout (tree_view); + + if (tree_view->priv->tickmark_icon) + g_object_unref (tree_view->priv->tickmark_icon); + + tree_view->priv->tickmark_icon = + gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), + "widgets_tickmark_list", + HILDON_TICK_MARK_SIZE, + 0, NULL); + + if (tree_view->priv->action_area_visible) + hildon_tree_view_set_action_area_height (tree_view); + gtk_widget_queue_resize (widget); } @@ -8354,10 +9099,39 @@ if (tree == NULL) goto done; + /* Unselect the row if it just became insensitive */ + if (!_gtk_tree_selection_row_is_selectable (tree_view->priv->selection, + node, path) + && gtk_tree_row_reference_valid (tree_view->priv->cursor)) + { + GtkTreePath *cursor_path; + + cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor); + if (! gtk_tree_path_compare (path, cursor_path)) + { + 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_path_free (cursor_path); + } + if (tree_view->priv->fixed_height_mode && tree_view->priv->fixed_height >= 0) { - _gtk_rbtree_node_set_height (tree, node, tree_view->priv->fixed_height); + if (tree_view->priv->row_separator_func + || tree_view->priv->row_header_func) + { + int height; + + height = determine_row_height (tree_view, iter, -1, -1); + _gtk_rbtree_node_set_height (tree, node, height); + } + else + _gtk_rbtree_node_set_height (tree, node, tree_view->priv->fixed_height); + if (gtk_widget_get_realized (GTK_WIDGET (tree_view))) gtk_tree_view_node_queue_redraw (tree_view, tree, node); } @@ -8407,7 +9181,13 @@ if (tree_view->priv->fixed_height_mode && tree_view->priv->fixed_height >= 0) - height = tree_view->priv->fixed_height; + { + if (tree_view->priv->row_separator_func + || tree_view->priv->row_header_func) + height = determine_row_height (tree_view, iter, -1, -1); + else + height = tree_view->priv->fixed_height; + } else height = 0; @@ -8420,7 +9200,13 @@ gtk_tree_model_get_iter (model, iter, path); if (tree_view->priv->tree == NULL) - tree_view->priv->tree = _gtk_rbtree_new (); + { + tree_view->priv->tree = _gtk_rbtree_new (); + + if (G_UNLIKELY (tree_view->priv->rows_offset != 0)) + _gtk_rbtree_set_base_offset (tree_view->priv->tree, + tree_view->priv->rows_offset); + } tmptree = tree = tree_view->priv->tree; @@ -8485,6 +9271,23 @@ tmpnode = _gtk_rbtree_insert_after (tree, tmpnode, height, FALSE); } + /* If this was the first node inserted in the tree, we want to + * select it. + */ + if (tree_view->priv->hildon_mode == HILDON_FREMANTLE + && tree_view->priv->hildon_ui_mode == HILDON_UI_MODE_EDIT + && tree_view->priv->selection->type != GTK_SELECTION_MULTIPLE + && gtk_tree_selection_count_selected_rows (tree_view->priv->selection) < 1) + { + GtkTreePath *tmppath; + + tmppath = gtk_tree_path_new_first (); + search_first_focusable_path (tree_view, &tmppath, + TRUE, NULL, NULL); + gtk_tree_selection_select_path (tree_view->priv->selection, tmppath); + gtk_tree_path_free (tmppath); + } + done: if (height > 0) { @@ -8631,6 +9434,8 @@ /* Ensure we don't have a dangling pointer to a dead node */ ensure_unprelighted (tree_view); + free_queued_actions (tree_view); + ensure_unhighlighted (tree_view); /* Cancel editting if we've started */ gtk_tree_view_stop_editing (tree_view, TRUE); @@ -8638,6 +9443,12 @@ /* If we have a node expanded/collapsed timeout, remove it */ remove_expand_collapse_timeout (tree_view); + /* Stop rubber banding as row deletion invalidates the start/end + * rbtree/nodes. Should consider updating the pointers instead. + */ + if (tree_view->priv->rubber_band_status) + gtk_tree_view_stop_rubber_band (tree_view); + if (tree_view->priv->destroy_count_func) { gint child_count = 0; @@ -8666,6 +9477,24 @@ install_scroll_sync_handler (tree_view); + if (selection_changed + && tree_view->priv->hildon_mode == HILDON_FREMANTLE + && tree_view->priv->hildon_ui_mode == HILDON_UI_MODE_EDIT + && tree_view->priv->selection->type != GTK_SELECTION_MULTIPLE + && gtk_tree_selection_count_selected_rows (tree_view->priv->selection) < 1) + { + GtkTreePath *tmppath; + + /* One item must be selected, now there is none. If iter_first + * does not exist, the view is empty. + */ + tmppath = gtk_tree_path_new_first (); + search_first_focusable_path (tree_view, &tmppath, + TRUE, NULL, NULL); + gtk_tree_selection_select_path (tree_view->priv->selection, tmppath); + gtk_tree_path_free (tmppath); + } + gtk_widget_queue_resize (GTK_WIDGET (tree_view)); if (selection_changed) @@ -8714,6 +9543,8 @@ /* we need to be unprelighted */ ensure_unprelighted (tree_view); + free_queued_actions (tree_view); + ensure_unhighlighted (tree_view); /* clear the timeout */ cancel_arrow_animation (tree_view); @@ -9590,6 +10421,9 @@ { GtkTreePath *cursor_path; + if (tree_view->priv->hildon_mode == HILDON_FREMANTLE) + return; + if ((tree_view->priv->tree == NULL) || (! gtk_widget_get_realized (GTK_WIDGET (tree_view)))) return; @@ -9628,8 +10462,11 @@ if (cursor_path) { - if (tree_view->priv->selection->type == GTK_SELECTION_MULTIPLE) - gtk_tree_view_real_set_cursor (tree_view, cursor_path, FALSE, FALSE); + if ((tree_view->priv->selection->type == GTK_SELECTION_MULTIPLE) + && tree_view->priv->hildon_mode == HILDON_DIABLO) + { + gtk_tree_view_real_set_cursor (tree_view, cursor_path, FALSE, FALSE); + } else gtk_tree_view_real_set_cursor (tree_view, cursor_path, TRUE, FALSE); } @@ -9833,6 +10670,8 @@ if (y >= tree_view->priv->height) y = tree_view->priv->height - 1; + else if (tree_view->priv->rows_offset != 0 && y < tree_view->priv->rows_offset) + y = tree_view->priv->rows_offset; tree_view->priv->cursor_offset = _gtk_rbtree_find_offset (tree_view->priv->tree, y, @@ -9942,11 +10781,12 @@ if (column->visible == FALSE) goto loop_end; - gtk_tree_view_column_cell_set_cell_data (column, - tree_view->priv->model, - &iter, - GTK_RBNODE_FLAG_SET (cursor_node, GTK_RBNODE_IS_PARENT), - cursor_node->children?TRUE:FALSE); + gtk_tree_view_column_cell_set_cell_data_with_hint (column, + tree_view->priv->model, + &iter, + GTK_RBNODE_FLAG_SET (cursor_node, GTK_RBNODE_IS_PARENT), + cursor_node->children?TRUE:FALSE, + GTK_TREE_CELL_DATA_HINT_KEY_FOCUS); if (rtl) { @@ -10007,6 +10847,12 @@ gtk_tree_view_get_cursor (tree_view, &old_path, NULL); + /* Immediately return when there is no cursor. (Like all other + * cursor movement handlers -- should prolly go upstream). + */ + if (!old_path) + return; + cursor_tree = tree_view->priv->tree; cursor_node = cursor_tree->root; @@ -10115,6 +10961,54 @@ return FALSE; } + if (start_editing) + { + GList *list; + gboolean rtl; + GtkTreeIter iter; + + /* In case we have only one activatable cell in the tree view, we have + * a special case. We will have to set the cell data on the cursor + * row in order to figure out. + */ + gtk_tree_model_get_iter (tree_view->priv->model, &iter, cursor_path); + rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL); + + for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns)); + list; + list = (rtl ? list->prev : list->next)) + { + GtkTreeViewColumn *column = list->data; + gtk_tree_view_column_cell_set_cell_data_with_hint (column, + tree_view->priv->model, + &iter, + GTK_RBNODE_FLAG_SET (cursor_node, GTK_RBNODE_IS_PARENT), + cursor_node->children?TRUE:FALSE, + GTK_TREE_CELL_DATA_HINT_KEY_FOCUS); + } + + if (!gtk_tree_view_has_special_cell (tree_view)) + { + /* We now set focus_column to the first column with an activatable + * cell that we can find. This is either none, or the one with + * the only activatable cell that is around. + */ + for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns)); + list; + list = (rtl ? list->prev : list->next)) + { + GtkTreeViewColumn *column = list->data; + + if (column->visible + && _gtk_tree_view_column_count_special_cells (column)) + { + tree_view->priv->focus_column = column; + break; + } + } + } + } + if (!tree_view->priv->extend_selection_pressed && start_editing && tree_view->priv->focus_column) { @@ -10235,18 +11129,40 @@ if (_gtk_tree_view_find_node (tree_view, cursor_path, &tree, &node)) return FALSE; - /* Don't handle the event if we aren't an expander */ - if (!((node->flags & GTK_RBNODE_IS_PARENT) == GTK_RBNODE_IS_PARENT)) - return FALSE; - if (!logical && 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); + { + gboolean expanded; + + expanded = gtk_tree_view_real_expand_row (tree_view, cursor_path, + tree, node, open_all, TRUE); + if (!expanded + && !gtk_widget_keynav_failed (GTK_WIDGET (tree_view), GTK_DIR_RIGHT)) + { + GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (tree_view)); + if (toplevel) + gtk_widget_child_focus (toplevel, GTK_DIR_TAB_FORWARD); + } + } else - gtk_tree_view_real_collapse_row (tree_view, cursor_path, tree, node, TRUE); + { + gboolean collapsed; + + collapsed = gtk_tree_view_real_collapse_row (tree_view, cursor_path, + tree, node, TRUE); + if (!collapsed + && !gtk_widget_keynav_failed (GTK_WIDGET (tree_view), GTK_DIR_LEFT)) + { + GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (tree_view)); + if (toplevel) + gtk_widget_child_focus (toplevel, GTK_DIR_TAB_BACKWARD); + + gtk_tree_view_real_select_cursor_parent (tree_view); + } + } gtk_tree_path_free (cursor_path); @@ -10361,6 +11277,7 @@ } tree_view->priv->search_window = gtk_window_new (GTK_WINDOW_POPUP); + gtk_window_set_is_temporary (GTK_WINDOW (tree_view->priv->search_window), TRUE); gtk_window_set_screen (GTK_WINDOW (tree_view->priv->search_window), screen); if (GTK_WINDOW (toplevel)->group) @@ -10792,6 +11709,11 @@ gtk_tree_row_reference_free (tree_view->priv->scroll_to_path); tree_view->priv->scroll_to_path = NULL; + tree_view->priv->highlighted_tree = NULL; + tree_view->priv->highlighted_node = NULL; + + free_queued_actions (tree_view); + tree_view->priv->scroll_to_column = NULL; g_object_unref (tree_view->priv->model); @@ -10859,8 +11781,24 @@ if (gtk_tree_model_get_iter (tree_view->priv->model, &iter, path)) { tree_view->priv->tree = _gtk_rbtree_new (); + + if (G_UNLIKELY (tree_view->priv->rows_offset != 0)) + _gtk_rbtree_set_base_offset (tree_view->priv->tree, + tree_view->priv->rows_offset); + gtk_tree_view_build_tree (tree_view, tree_view->priv->tree, &iter, 1, FALSE); } + + if (tree_view->priv->hildon_mode == HILDON_FREMANTLE + && tree_view->priv->hildon_ui_mode == HILDON_UI_MODE_EDIT + && tree_view->priv->selection->type != GTK_SELECTION_MULTIPLE) + { + /* Select the first item */ + search_first_focusable_path (tree_view, &path, + TRUE, NULL, NULL); + gtk_tree_selection_select_path (tree_view->priv->selection, path); + } + gtk_tree_path_free (path); /* FIXME: do I need to do this? gtk_tree_view_create_buttons (tree_view); */ @@ -12079,6 +13017,12 @@ GtkTreeIter iter; GtkTreeIter temp; gboolean expand; + gint dy; + GtkTreeIter tmpiter; + GtkTreePath *tmppath; + GtkRBTree *tmptree; + GtkRBNode *tmpnode; + gint branch_height; if (animate) g_object_get (gtk_widget_get_settings (GTK_WIDGET (tree_view)), @@ -12147,12 +13091,54 @@ gtk_tree_path_get_depth (path) + 1, open_all); - remove_expand_collapse_timeout (tree_view); + remove_expand_collapse_timeout (tree_view); + + if (animate) + add_expand_collapse_timeout (tree_view, tree, node, TRUE); + + /* We do validate new nodes ourselves below, but we still need + * somebody to take care of the actual resizing. + */ + if (gtk_widget_get_mapped (GTK_WIDGET (tree_view))) + gtk_widget_queue_resize (GTK_WIDGET (tree_view)); + + install_presize_handler (tree_view); + + gtk_tree_model_get_iter (tree_view->priv->model, &tmpiter, path); + _gtk_tree_view_find_node (tree_view, path, &tmptree, &tmpnode); + + validate_row (tree_view, tmptree, tmpnode, &tmpiter, path); + branch_height = ROW_HEIGHT (tree_view, GTK_RBNODE_GET_HEIGHT (tmpnode)); + dy = _gtk_rbtree_node_find_offset (tmptree, tmpnode); + + tmppath = gtk_tree_path_copy (path); + gtk_tree_path_down (tmppath); + gtk_tree_model_get_iter (tree_view->priv->model, &tmpiter, tmppath); + + do + { + _gtk_tree_view_find_node (tree_view, tmppath, &tmptree, &tmpnode); + + validate_row (tree_view, tmptree, tmpnode, &tmpiter, tmppath); + branch_height += ROW_HEIGHT (tree_view, GTK_RBNODE_GET_HEIGHT (tmpnode)); + + gtk_tree_path_next (tmppath); + } + while (gtk_tree_model_iter_next (tree_view->priv->model, &tmpiter)); + + gtk_tree_path_prev (tmppath); - if (animate) - add_expand_collapse_timeout (tree_view, tree, node, TRUE); + /* We scroll to the just expanded row if the expanding row and its children + * do not fit in the view. + * Otherwise, if the last child is not visible, we scroll to it to make + * sure all children are visible. + */ + if (branch_height > GTK_WIDGET (tree_view)->allocation.height - TREE_VIEW_HEADER_HEIGHT (tree_view)) + gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0.0, 0.0); + else if (dy + branch_height > tree_view->priv->vadjustment->value + tree_view->priv->vadjustment->page_size) + gtk_tree_view_scroll_to_cell (tree_view, tmppath, NULL, TRUE, 1.0, 0.0); - install_presize_handler (tree_view); + gtk_tree_path_free (tmppath); g_signal_emit (tree_view, tree_view_signals[ROW_EXPANDED], 0, &iter, path); if (open_all && node->children) @@ -12556,6 +13542,20 @@ GtkRBTree *tree = NULL; GtkRBNode *node = NULL; + _gtk_tree_view_find_node (tree_view, path, &tree, &node); + + /* If we are in legacy or in (new-style) edit mode, row-insensitive + * can be emitted. + */ + if ((tree_view->priv->hildon_mode == HILDON_DIABLO + || tree_view->priv->hildon_ui_mode == HILDON_UI_MODE_EDIT) + && !_gtk_tree_selection_row_is_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; @@ -12573,18 +13573,21 @@ * path maps to a non-existing path and we will silently bail out. * We unset tree and node to avoid further processing. */ - if (!row_is_separator (tree_view, NULL, path) - && _gtk_tree_view_find_node (tree_view, path, &tree, &node) == FALSE) + if (tree_view->priv->hildon_mode == HILDON_DIABLO) { - tree_view->priv->cursor = - gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view), - tree_view->priv->model, - path); - } - else - { - tree = NULL; - node = NULL; + if (!row_is_separator (tree_view, NULL, NULL, path) + && _gtk_tree_view_find_node (tree_view, path, &tree, &node) == FALSE) + { + tree_view->priv->cursor = + gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view), + tree_view->priv->model, + path); + } + else + { + tree = NULL; + node = NULL; + } } if (tree != NULL) @@ -12596,8 +13599,13 @@ { GtkTreeSelectMode mode = 0; - if (tree_view->priv->modify_selection_pressed) - mode |= GTK_TREE_SELECT_MODE_TOGGLE; + if (tree_view->priv->modify_selection_pressed || + (tree_view->priv->hildon_mode == HILDON_FREMANTLE + && tree_view->priv->hildon_ui_mode == HILDON_UI_MODE_EDIT + && tree_view->priv->selection->type == GTK_SELECTION_MULTIPLE)) + { + mode |= GTK_TREE_SELECT_MODE_TOGGLE; + } if (tree_view->priv->extend_selection_pressed) mode |= GTK_TREE_SELECT_MODE_EXTEND; @@ -12622,7 +13630,9 @@ } } - g_signal_emit (tree_view, tree_view_signals[CURSOR_CHANGED], 0); + /* Only in the original mode we want to handle the cursor */ + if (tree_view->priv->hildon_mode == HILDON_DIABLO) + g_signal_emit (tree_view, tree_view_signals[CURSOR_CHANGED], 0); } /** @@ -13780,6 +14790,7 @@ gboolean is_separator = FALSE; gboolean rtl; cairo_t *cr; + gboolean is_header = FALSE; g_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), NULL); g_return_val_if_fail (path != NULL, NULL); @@ -13804,7 +14815,7 @@ path)) return NULL; - is_separator = row_is_separator (tree_view, &iter, NULL); + is_separator = row_is_separator (tree_view, &iter, &is_header, NULL); cell_offset = x; @@ -13872,7 +14883,14 @@ if (gtk_tree_view_column_cell_is_visible (column)) { - if (is_separator) + if (is_header) + { + gtk_tree_view_draw_row_header (tree_view, + &iter, + NULL, + &cell_area); + } + else if (is_separator) gtk_paint_hline (widget->style, drawable, GTK_STATE_NORMAL, @@ -15141,6 +16159,83 @@ gtk_widget_queue_resize (GTK_WIDGET (tree_view)); } +HildonTreeViewRowHeaderFunc +hildon_tree_view_get_row_header_func (GtkTreeView *tree_view) +{ + g_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), NULL); + + return tree_view->priv->row_header_func; +} + +static void +hildon_tree_view_setup_row_header_layout (GtkTreeView *tree_view) +{ + GdkColor font_color; + GtkStyle *font_style; + GtkWidget *widget = GTK_WIDGET (tree_view); + + font_style = gtk_rc_get_style_by_paths (gtk_settings_get_default (), + "EmpSmallSystemFont", + NULL, G_TYPE_NONE); + if (font_style) + { + pango_layout_set_font_description (tree_view->priv->row_header_layout, + font_style->font_desc); + } + + if (gtk_style_lookup_color (widget->style, "SecondaryTextColor", &font_color)) + { + PangoAttrList *list; + PangoAttribute *attr; + + list = pango_attr_list_new (); + attr = pango_attr_foreground_new (font_color.red, + font_color.green, + font_color.blue); + attr->start_index = 0; + attr->end_index = G_MAXINT; + pango_attr_list_insert (list, attr); + + pango_layout_set_attributes (tree_view->priv->row_header_layout, + list); + + pango_attr_list_unref (list); + } +} + +void +hildon_tree_view_set_row_header_func (GtkTreeView *tree_view, + HildonTreeViewRowHeaderFunc func, + gpointer data, + GDestroyNotify destroy) +{ + g_return_if_fail (GTK_IS_TREE_VIEW (tree_view)); + + if (tree_view->priv->row_header_destroy) + (* tree_view->priv->row_header_destroy) (tree_view->priv->row_header_data); + + tree_view->priv->row_header_func = func; + tree_view->priv->row_header_data = data; + tree_view->priv->row_header_destroy = destroy; + + if (func && !tree_view->priv->row_header_layout) + { + tree_view->priv->row_header_layout = + gtk_widget_create_pango_layout (GTK_WIDGET (tree_view), ""); + + hildon_tree_view_setup_row_header_layout (tree_view); + } + else if (!func && tree_view->priv->row_header_layout) + { + g_object_unref (tree_view->priv->row_header_layout); + tree_view->priv->row_header_layout = NULL; + } + + /* Have the tree recalculate heights */ + _gtk_rbtree_mark_invalid (tree_view->priv->tree); + gtk_widget_queue_resize (GTK_WIDGET (tree_view)); +} + static void gtk_tree_view_grab_notify (GtkWidget *widget, @@ -15156,6 +16251,21 @@ if (tree_view->priv->rubber_band_status) gtk_tree_view_stop_rubber_band (tree_view); + + 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_tapped_row) + { + gtk_tree_row_reference_free (tree_view->priv->queued_tapped_row); + tree_view->priv->queued_tapped_row = NULL; + } + + free_queued_select_row (tree_view); + free_queued_activate_row (tree_view); } } @@ -15755,5 +16865,373 @@ return tree_view->priv->tooltip_column; } +static gboolean +gtk_tree_view_tap_and_hold_query (GtkWidget *widget, + GdkEvent *event) +{ + GtkTreeView *tree_view = GTK_TREE_VIEW (widget); + GtkTreePath *path; + GtkRBTree *tree = NULL; + GtkRBNode *node = NULL; + gdouble x, y; + gint new_y; + gboolean sensitive; + + if (!tree_view->priv->tree) + return FALSE; + + if (!gdk_event_get_coords (event, &x, &y)) + return FALSE; + + new_y = TREE_WINDOW_Y_TO_RBTREE_Y (tree_view, y); + if (new_y < 0) + new_y = 0; + _gtk_rbtree_find_offset (tree_view->priv->tree, new_y, &tree, &node); + if (node == NULL) + return TRUE; + + path = _gtk_tree_view_find_path (tree_view, tree, node); + + if (tree_view->priv->hildon_mode == HILDON_FREMANTLE + && tree_view->priv->hildon_ui_mode == HILDON_UI_MODE_NORMAL) + { + /* The normal mode has no notion of selection, so we special-case + * it here. + */ + sensitive = !row_is_separator (tree_view, NULL, NULL, path); + } + else + { + sensitive = _gtk_tree_selection_row_is_selectable (tree_view->priv->selection, + node, path); + } + + gtk_tree_path_free (path); + + return !sensitive; +} + +static void +free_queued_select_row (GtkTreeView *tree_view) +{ + /* We only clear the selected flag (used for highlight) if the node + * was previously *not* selected. + */ + if (tree_view->priv->hildon_mode == HILDON_FREMANTLE + && gtk_tree_row_reference_valid (tree_view->priv->queued_select_row)) + { + if (tree_view->priv->highlighted_node) + { + _gtk_tree_view_queue_draw_node (tree_view, + tree_view->priv->highlighted_tree, + tree_view->priv->highlighted_node, + NULL); + + tree_view->priv->highlighted_tree = NULL; + tree_view->priv->highlighted_node = NULL; + } + } + + gtk_tree_row_reference_free (tree_view->priv->queued_select_row); + tree_view->priv->queued_select_row = NULL; +} + +static void +free_queued_activate_row (GtkTreeView *tree_view) +{ + if (tree_view->priv->hildon_mode == HILDON_FREMANTLE + && tree_view->priv->hildon_ui_mode == HILDON_UI_MODE_NORMAL + && gtk_tree_row_reference_valid (tree_view->priv->queued_activate_row)) + { + if (tree_view->priv->highlighted_node) + { + _gtk_tree_view_queue_draw_node (tree_view, + tree_view->priv->highlighted_tree, + tree_view->priv->highlighted_node, + NULL); + + tree_view->priv->highlighted_tree = NULL; + tree_view->priv->highlighted_node = NULL; + } + } + + gtk_tree_row_reference_free (tree_view->priv->queued_activate_row); + tree_view->priv->queued_activate_row = NULL; +} + +static void +free_queued_actions (GtkTreeView *tree_view) +{ + free_queued_activate_row (tree_view); + free_queued_select_row (tree_view); + + 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_tapped_row); + tree_view->priv->queued_tapped_row = NULL; +} + +HildonUIMode +hildon_tree_view_get_hildon_ui_mode (GtkTreeView *tree_view) +{ + g_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), 0); + + return tree_view->priv->hildon_ui_mode; +} + +void +hildon_tree_view_set_hildon_ui_mode (GtkTreeView *tree_view, + HildonUIMode hildon_ui_mode) +{ + g_return_if_fail (GTK_IS_TREE_VIEW (tree_view)); + + /* Don't check if the new mode matches the old mode; always continue + * so that the selection corrections below always happen. + */ + tree_view->priv->hildon_ui_mode = hildon_ui_mode; + + if (tree_view->priv->hildon_mode == HILDON_DIABLO) + return; + + /* For both normal and edit mode a couple of things are disabled. */ + + /* Mode-specific settings */ + if (hildon_ui_mode == HILDON_UI_MODE_NORMAL) + { + gtk_tree_selection_set_mode (tree_view->priv->selection, + GTK_SELECTION_NONE); + } + else if (hildon_ui_mode == HILDON_UI_MODE_EDIT) + { + if (gtk_tree_selection_get_mode (tree_view->priv->selection) == GTK_SELECTION_NONE) + { + gtk_tree_selection_set_mode (tree_view->priv->selection, + GTK_SELECTION_SINGLE); + } + + if (tree_view->priv->selection->type != GTK_SELECTION_MULTIPLE + && gtk_tree_selection_count_selected_rows (tree_view->priv->selection) < 1) + { + GtkTreePath *path; + + /* Select the first item */ + path = gtk_tree_path_new_first (); + search_first_focusable_path (tree_view, &path, + TRUE, NULL, NULL); + gtk_tree_selection_select_path (tree_view->priv->selection, path); + gtk_tree_path_free (path); + } + } + else + g_assert_not_reached (); +} + +static void +hildon_tree_view_set_rows_offset (GtkTreeView *tree_view, + int rows_offset) +{ + if (tree_view->priv->rows_offset == rows_offset) + return; + + tree_view->priv->rows_offset = rows_offset; + if (tree_view->priv->tree) + _gtk_rbtree_set_base_offset (tree_view->priv->tree, rows_offset); + + gtk_widget_queue_resize (GTK_WIDGET (tree_view)); +} + +static void +hildon_tree_view_create_action_area (GtkTreeView *tree_view) +{ + /* gtk_tree_view_put() takes over ownership and so we do not + * have to unref these values in gtk_tree_view_destroy(). + */ + tree_view->priv->action_area_event_box = gtk_event_box_new (); + gtk_tree_view_put (tree_view, tree_view->priv->action_area_event_box, + 0, 0, 0, 0); + + if (tree_view->priv->action_area_orientation == GTK_ORIENTATION_HORIZONTAL) + tree_view->priv->action_area_box = gtk_hbox_new (TRUE, 0); + else if (tree_view->priv->action_area_orientation == GTK_ORIENTATION_VERTICAL) + tree_view->priv->action_area_box = gtk_vbox_new (TRUE, 0); + + gtk_container_add (GTK_CONTAINER (tree_view->priv->action_area_event_box), + tree_view->priv->action_area_box); +} + +static void +hildon_tree_view_set_action_area_height (GtkTreeView *tree_view) +{ + if (tree_view->priv->action_area_orientation == GTK_ORIENTATION_HORIZONTAL) + { + int row_height; + + gtk_widget_style_get (GTK_WIDGET (tree_view), + "row-height", &row_height, + NULL); + hildon_tree_view_set_rows_offset (tree_view, row_height); + } + else if (tree_view->priv->action_area_orientation == GTK_ORIENTATION_VERTICAL) + { + GList *children; + + /* The height in portrait mode is currently hardcoded to 90px per + * button. + */ + children = gtk_container_get_children (GTK_CONTAINER (tree_view->priv->action_area_box)); + hildon_tree_view_set_rows_offset (tree_view, + g_list_length (children) * 90); + g_list_free (children); + } +} + +void +hildon_tree_view_set_action_area_visible (GtkTreeView *tree_view, + gboolean visible) +{ + g_return_if_fail (GTK_IS_TREE_VIEW (tree_view)); + + if (tree_view->priv->action_area_visible == !!visible) + return; + + tree_view->priv->action_area_visible = visible; + + if (visible) + { + hildon_tree_view_set_action_area_height (tree_view); + + if (!tree_view->priv->action_area_event_box) + hildon_tree_view_create_action_area (tree_view); + + gtk_widget_show (tree_view->priv->action_area_box); + gtk_widget_show (tree_view->priv->action_area_event_box); + } + else + { + gtk_widget_hide (tree_view->priv->action_area_event_box); + + hildon_tree_view_set_rows_offset (tree_view, 0); + } +} + +gboolean +hildon_tree_view_get_action_area_visible (GtkTreeView *tree_view) +{ + g_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), FALSE); + + return tree_view->priv->action_area_visible; +} + +static void +hildon_tree_view_rotate_action_area_box (GtkBox *old_box, + GtkBox *new_box) +{ + int spacing, border_width; + GList *children, *child; + gboolean homogeneous; + + g_object_get (old_box, + "homogeneous", &homogeneous, + "spacing", &spacing, + "border-width", &border_width, + NULL); + + g_object_set (new_box, + "homogeneous", homogeneous, + "spacing", spacing, + "border-width", border_width, + NULL); + + children = gtk_container_get_children (GTK_CONTAINER (old_box)); + for (child = children; child; child = child->next) + { + guint padding; + gboolean expand, fill; + GtkPackType type; + GtkWidget *widget = child->data; + + g_object_ref (widget); + gtk_box_query_child_packing (old_box, widget, + &expand, &fill, &padding, &type); + gtk_container_remove (GTK_CONTAINER (old_box), widget); + gtk_container_add (GTK_CONTAINER (new_box), widget); + gtk_box_set_child_packing (new_box, widget, + expand, fill, padding, type); + g_object_unref (widget); + } + + g_list_free (children); + + if (gtk_widget_get_visible (GTK_WIDGET (old_box))) + gtk_widget_show (GTK_WIDGET (new_box)); +} + +void +hildon_tree_view_set_action_area_orientation (GtkTreeView *tree_view, + GtkOrientation orientation) +{ + GtkWidget *old_box; + GtkWidget *new_box; + + g_return_if_fail (GTK_IS_TREE_VIEW (tree_view)); + + if (tree_view->priv->action_area_orientation == orientation) + return; + + tree_view->priv->action_area_orientation = orientation; + + old_box = tree_view->priv->action_area_box; + if (orientation == GTK_ORIENTATION_HORIZONTAL) + new_box = gtk_hbox_new (TRUE, 0); + else if (orientation == GTK_ORIENTATION_VERTICAL) + new_box = gtk_vbox_new (TRUE, 0); + + if (tree_view->priv->action_area_visible) + hildon_tree_view_set_action_area_height (tree_view); + + hildon_tree_view_rotate_action_area_box (GTK_BOX (old_box), + GTK_BOX (new_box)); + + gtk_widget_destroy (old_box); + + tree_view->priv->action_area_box = new_box; + gtk_container_add (GTK_CONTAINER (tree_view->priv->action_area_event_box), + tree_view->priv->action_area_box); +} + +GtkOrientation +hildon_tree_view_get_action_area_orientation (GtkTreeView *tree_view) +{ + g_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), 0); + + return tree_view->priv->action_area_orientation; +} + +/** + * hildon_tree_view_get_action_area_box: + * @tree_view: a #GtkTreeView + * + * Returns the GtkBox is embedded in GtkTreeView's action area. Depending + * on the setting of the GtkTreeView:action-area-orientation property + * this is either a GtkHBox or GtkVBox. You do not own a reference to + * the returned widget and thus this value should not be unreferenced. + * + * Returns: a pointer to a GtkBox. This pointer should not be unreferenced. + * + * Since: maemo 5.0 + * Stability: unstable + */ +GtkWidget * +hildon_tree_view_get_action_area_box (GtkTreeView *tree_view) +{ + g_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), NULL); + + if (!tree_view->priv->action_area_event_box) + hildon_tree_view_create_action_area (tree_view); + + return tree_view->priv->action_area_box; +} + #define __GTK_TREE_VIEW_C__ #include "gtkaliasdef.c" --- a/gtk/gtktreeview.h +++ b/gtk/gtktreeview.h @@ -111,7 +111,8 @@ void (*_gtk_reserved1) (void); void (*_gtk_reserved2) (void); void (*_gtk_reserved3) (void); - void (*_gtk_reserved4) (void); + void (* row_insensitive) (GtkTreeView *tree_view, + GtkTreePath *path); }; @@ -134,7 +135,10 @@ typedef void (*GtkTreeViewSearchPositionFunc) (GtkTreeView *tree_view, GtkWidget *search_dialog, gpointer user_data); - +typedef gboolean (*HildonTreeViewRowHeaderFunc) (GtkTreeModel *model, + GtkTreeIter *iter, + gchar **header_text, + gpointer data); /* Creators */ GType gtk_tree_view_get_type (void) G_GNUC_CONST; @@ -392,6 +396,27 @@ gpointer data, GDestroyNotify destroy); +HildonTreeViewRowHeaderFunc hildon_tree_view_get_row_header_func (GtkTreeView *tree_view); +void hildon_tree_view_set_row_header_func (GtkTreeView *tree_view, + HildonTreeViewRowHeaderFunc func, + gpointer data, + GDestroyNotify destroy); + +HildonUIMode hildon_tree_view_get_hildon_ui_mode (GtkTreeView *tree_view); +void hildon_tree_view_set_hildon_ui_mode (GtkTreeView *tree_view, + HildonUIMode mode); + + +void hildon_tree_view_set_action_area_visible (GtkTreeView *tree_view, + gboolean visible); +gboolean hildon_tree_view_get_action_area_visible (GtkTreeView *tree_view); + +void hildon_tree_view_set_action_area_orientation (GtkTreeView *tree_view, + GtkOrientation orientation); +GtkOrientation hildon_tree_view_get_action_area_orientation (GtkTreeView *tree_view); + +GtkWidget *hildon_tree_view_get_action_area_box (GtkTreeView *tree_view); + GtkTreeViewGridLines gtk_tree_view_get_grid_lines (GtkTreeView *tree_view); void gtk_tree_view_set_grid_lines (GtkTreeView *tree_view, GtkTreeViewGridLines grid_lines); --- a/gtk/gtktreeprivate.h +++ b/gtk/gtktreeprivate.h @@ -28,7 +28,7 @@ #include #include -#define TREE_VIEW_DRAG_WIDTH 6 +#define TREE_VIEW_DRAG_WIDTH 28 typedef enum { @@ -300,6 +300,37 @@ /* Whether our key press handler is to avoid sending an unhandled binding to the search entry */ guint search_entry_avoid_unhandled_binding : 1; + + /* Fields for Maemo specific functionality */ + GtkTreeRowReference *queued_select_row; + GtkTreeRowReference *queued_expand_row; + GtkTreeRowReference *queued_activate_row; + GtkTreeRowReference *queued_tapped_row; + + GtkRBNode *highlighted_node; + GtkRBTree *highlighted_tree; + + GtkTreeCellDataHint cell_data_hint; + + HildonUIMode hildon_ui_mode; + HildonMode hildon_mode; + + GdkPixbuf *tickmark_icon; + + HildonTreeViewRowHeaderFunc row_header_func; + gpointer row_header_data; + GDestroyNotify row_header_destroy; + PangoLayout *row_header_layout; + + gint rows_offset; + GtkOrientation action_area_orientation; + GtkWidget *action_area_event_box; + GtkWidget *action_area_box; + + guint queued_shift_pressed : 1; + guint queued_ctrl_pressed : 1; + + guint action_area_visible : 1; }; #ifdef __GNUC__ --- a/gtk/gtktreeviewcolumn.c +++ b/gtk/gtktreeviewcolumn.c @@ -851,7 +851,7 @@ G_CALLBACK (gtk_tree_view_column_button_clicked), tree_column); - tree_column->alignment = gtk_alignment_new (tree_column->xalign, 0.5, 0.0, 0.0); + tree_column->alignment = gtk_alignment_new (tree_column->xalign, 0.5, 1.0, 1.0); hbox = gtk_hbox_new (FALSE, 2); tree_column->arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_IN); @@ -916,7 +916,7 @@ /* Set up the actual button */ gtk_alignment_set (GTK_ALIGNMENT (alignment), tree_column->xalign, - 0.5, 0.0, 0.0); + 0.5, 1.0, 1.0); if (tree_column->child) { @@ -926,6 +926,7 @@ current_child); gtk_container_add (GTK_CONTAINER (alignment), tree_column->child); + current_child = tree_column->child; } } else @@ -948,6 +949,14 @@ ""); } + if (GTK_IS_MISC (current_child)) + { + gfloat yalign; + + gtk_misc_get_alignment (GTK_MISC (current_child), NULL, &yalign); + gtk_misc_set_alignment (GTK_MISC (current_child), tree_column->xalign, yalign); + } + if (GTK_IS_TREE_SORTABLE (model)) gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (model), &sort_column_id, @@ -2536,6 +2545,100 @@ } /** + * gtk_tree_view_column_cell_set_cell_data_with_hint: + * @tree_column: A #GtkTreeViewColumn. + * @tree_model: The #GtkTreeModel to to get the cell renderers attributes from. + * @iter: The #GtkTreeIter to to get the cell renderer's attributes from. + * @is_expander: %TRUE, if the row has children + * @is_expanded: %TRUE, if the row has visible children + * @hint: A #GtkTreeCellDataHint for the #GtkTreeCellDataFunc. + * + * Sets the cell renderer based on the @tree_model and @iter. That is, for + * every attribute mapping in @tree_column, it will get a value from the set + * column on the @iter, and use that value to set the attribute on the cell + * renderer. The @hint is a hint for the #GtkTreeCellDataFunc so that it does not + * have to set all cell renderer properties, possible leading to some + * optimizations. This is used primarily by the #GtkTreeView. + * + * Since: maemo 4.0 + * Stability: Unstable + **/ +void +gtk_tree_view_column_cell_set_cell_data_with_hint (GtkTreeViewColumn *tree_column, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gboolean is_expander, + gboolean is_expanded, + GtkTreeCellDataHint hint) +{ + GSList *list; + GValue value = { 0, }; + GList *cell_list; + + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); + + if (tree_model == NULL) + return; + + GTK_TREE_VIEW (tree_column->tree_view)->priv->cell_data_hint = hint; + + for (cell_list = tree_column->cell_list; cell_list; cell_list = cell_list->next) + { + GtkTreeViewColumnCellInfo *info = (GtkTreeViewColumnCellInfo *) cell_list->data; + GObject *cell = (GObject *) info->cell; + + list = info->attributes; + + g_object_freeze_notify (cell); + + if (info->cell->is_expander != is_expander) + g_object_set (cell, "is-expander", is_expander, NULL); + + if (info->cell->is_expanded != is_expanded) + g_object_set (cell, "is-expanded", is_expanded, NULL); + + while (list && list->next) + { + gtk_tree_model_get_value (tree_model, iter, + GPOINTER_TO_INT (list->next->data), + &value); + g_object_set_property (cell, (gchar *) list->data, &value); + g_value_unset (&value); + list = list->next->next; + } + + if (info->func) + (* info->func) (tree_column, info->cell, tree_model, iter, info->func_data); + g_object_thaw_notify (G_OBJECT (info->cell)); + } +} + +/** + * gtk_tree_view_column_get_cell_data_hint: + * @tree_column: A #GtkTreeViewColumn. + * + * Returns the current value of the cell data hint as a + * #GtkTreeCellDataHint. Note that the value returned is only + * valid when called from a #GtkTreeCellDataFunc. The value of the hint + * tells you why the #GtkTreeView is calling the #GtkTreeCellDataFunc. + * Based on this hint, you can omit to generate the data and set certain + * cell renderer properties to improve performance. + * + * Return value: A #GtkTreeCellDataHint with the hint. + * + * Since: maemo 4.0 + * Stability: Unstable + */ +GtkTreeCellDataHint +gtk_tree_view_column_get_cell_data_hint (GtkTreeViewColumn *tree_column) +{ + g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), 0); + g_return_val_if_fail (GTK_IS_TREE_VIEW (tree_column->tree_view), 0); + + return GTK_TREE_VIEW (tree_column->tree_view)->priv->cell_data_hint; +} + +/** * gtk_tree_view_column_cell_set_cell_data: * @tree_column: A #GtkTreeViewColumn. * @tree_model: The #GtkTreeModel to to get the cell renderers attributes from. @@ -2564,6 +2667,8 @@ if (tree_model == NULL) return; + GTK_TREE_VIEW (tree_column->tree_view)->priv->cell_data_hint = GTK_TREE_CELL_DATA_HINT_ALL; + for (cell_list = tree_column->cell_list; cell_list; cell_list = cell_list->next) { GtkTreeViewColumnCellInfo *info = (GtkTreeViewColumnCellInfo *) cell_list->data; --- a/gtk/gtktreeviewcolumn.h +++ b/gtk/gtktreeviewcolumn.h @@ -238,6 +238,25 @@ void gtk_tree_view_column_queue_resize (GtkTreeViewColumn *tree_column); GtkWidget *gtk_tree_view_column_get_tree_view (GtkTreeViewColumn *tree_column); +/** + * GtkTreeCellDataHint: + * + * See gtk_tree_view_column_get_cell_data_hint(). + **/ +typedef enum +{ + GTK_TREE_CELL_DATA_HINT_ALL, + GTK_TREE_CELL_DATA_HINT_KEY_FOCUS, + GTK_TREE_CELL_DATA_HINT_SENSITIVITY +} GtkTreeCellDataHint; + +void gtk_tree_view_column_cell_set_cell_data_with_hint (GtkTreeViewColumn *tree_column, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gboolean is_expander, + gboolean is_expanded, + GtkTreeCellDataHint hint); +GtkTreeCellDataHint gtk_tree_view_column_get_cell_data_hint (GtkTreeViewColumn *tree_column); G_END_DECLS --- a/gtk/gtktreeselection.c +++ b/gtk/gtktreeselection.c @@ -25,6 +25,7 @@ #include "gtkmarshalers.h" #include "gtkintl.h" #include "gtkalias.h" +#include "gtkcelllayout.h" static void gtk_tree_selection_finalize (GObject *object); static gint gtk_tree_selection_real_select_all (GtkTreeSelection *selection); @@ -160,12 +161,24 @@ GtkSelectionMode type) { GtkTreeSelectionFunc tmp_func; + HildonMode mode; g_return_if_fail (GTK_IS_TREE_SELECTION (selection)); if (selection->type == type) return; - + gtk_widget_style_get (GTK_WIDGET (selection->tree_view), + "hildon-mode", &mode, + NULL); + + if (mode == HILDON_FREMANTLE + && selection->tree_view->priv->hildon_ui_mode == HILDON_UI_MODE_NORMAL + && type != GTK_SELECTION_NONE) + { + g_warning ("Cannot change the selection mode to anything other than GTK_SELECTION_NONE if hildon-ui-mode is GTK_HILDON_UI_MODE_NORMAL.\n"); + return; + } + if (type == GTK_SELECTION_NONE) { /* We do this so that we unconditionally unset all rows @@ -202,6 +215,47 @@ } } + if (!selected + && mode == HILDON_FREMANTLE + && selection->tree_view->priv->hildon_ui_mode == HILDON_UI_MODE_EDIT) + { + GList *rows; + + /* One item must stay selected; we look for the first selected + * item we can find, that one becomes the anchor. We silently + * assume here that there is at least *one* selected row, + * as mandated by any new-style edit mode. When switching + * from SELECTION_NONE there is no selected row, and the + * tree view will be responsible for selecting a node. + */ + + if (anchor_path) + gtk_tree_path_free (anchor_path); + + /* FIXME: this can be obviously optimized by walking + * the selection tree ourselves; or probably having a _real + * variant of _get_selected_rows() that we can tell to stop + * as soon as a selected node is found. + */ + rows = gtk_tree_selection_get_selected_rows (selection, NULL); + if (rows) + { + anchor_path = gtk_tree_path_copy (rows->data); + g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL); + g_list_free (rows); + + _gtk_tree_view_find_node (selection->tree_view, + anchor_path, + &tree, + &node); + + if (node && GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_IS_SELECTED)) + selected = TRUE; + + g_return_if_fail (selected == TRUE); + } + } + /* We do this so that we unconditionally unset all rows */ tmp_func = selection->user_func; @@ -221,6 +275,23 @@ } selection->type = type; + + if (type == GTK_SELECTION_SINGLE + || type == GTK_SELECTION_BROWSE) + { + GtkTreeIter iter; + + /* Make sure the cursor is on a selected node */ + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) + { + GtkTreePath *path; + + path = gtk_tree_model_get_path (selection->tree_view->priv->model, + &iter); + gtk_tree_view_set_cursor (selection->tree_view, path, NULL, FALSE); + gtk_tree_path_free (path); + } + } } /** @@ -1249,6 +1320,39 @@ g_signal_emit (selection, tree_selection_signals[CHANGED], 0); } +static gboolean +tree_column_is_sensitive (GtkTreeViewColumn *column, + GtkTreeModel *model, + GtkTreeIter *iter) +{ + GList *cells, *list; + gboolean sensitive = TRUE; + gboolean visible; + + gtk_tree_view_column_cell_set_cell_data_with_hint (column, model, + iter, FALSE, FALSE, + GTK_TREE_CELL_DATA_HINT_SENSITIVITY); + + cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column)); + + list = cells; + while (list) + { + g_object_get (list->data, + "sensitive", &sensitive, + "visible", &visible, + NULL); + + if (visible && sensitive) + break; + + list = list->next; + } + g_list_free (cells); + + return sensitive; +} + gboolean _gtk_tree_selection_row_is_selectable (GtkTreeSelection *selection, GtkRBNode *node, @@ -1256,6 +1360,17 @@ { GtkTreeIter iter; gboolean sensitive = FALSE; + GList *list; + HildonMode mode; + + gtk_widget_style_get (GTK_WIDGET (selection->tree_view), + "hildon-mode", &mode, + NULL); + + /* normal-mode does not support selections */ + if (mode == HILDON_FREMANTLE + && selection->tree_view->priv->hildon_ui_mode == HILDON_UI_MODE_NORMAL) + return FALSE; if (!gtk_tree_model_get_iter (selection->tree_view->priv->model, &iter, path)) sensitive = TRUE; @@ -1269,6 +1384,29 @@ return FALSE; } + if (!sensitive && selection->tree_view->priv->row_header_func) + { + /* never allow separators to be selected */ + if ((* selection->tree_view->priv->row_header_func) (selection->tree_view->priv->model, + &iter, + NULL, + selection->tree_view->priv->row_header_data)) + return FALSE; + } + + for (list = selection->tree_view->priv->columns; list && !sensitive; list = list->next) + { + GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN (list->data); + + if (!column->visible) + continue; + + sensitive = tree_column_is_sensitive (column, selection->tree_view->priv->model, &iter); + } + + if (!sensitive) + return FALSE; + if (selection->user_func) return (*selection->user_func) (selection, selection->tree_view->priv->model, path, GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_IS_SELECTED), @@ -1453,6 +1591,11 @@ path = _gtk_tree_view_find_path (selection->tree_view, tree, node); toggle = _gtk_tree_selection_row_is_selectable (selection, node, path); gtk_tree_path_free (path); + + /* Allow unselecting an insensitive row */ + if (!toggle && !select + && GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_IS_SELECTED)) + toggle = TRUE; } if (toggle)