/* * Copyright (C) 2012 Alexander Larsson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include #include #include #include #include #include #include #include #include #include "egg-list-box.h" #include "egg-list-box-accessible.h" /* This already exists in gtk as _gtk_marshal_VOID__ENUM_INT, inline it here for now to avoid separate marshallers file */ static void _egg_marshal_VOID__ENUM_INT (GClosure * closure, GValue * return_value, guint n_param_values, const GValue * param_values, gpointer invocation_hint, gpointer marshal_data) { typedef void (*GMarshalFunc_VOID__ENUM_INT) (gpointer data1, gint arg_1, gint arg_2, gpointer data2); register GMarshalFunc_VOID__ENUM_INT callback; register GCClosure * cc; register gpointer data1; register gpointer data2; cc = (GCClosure *) closure; g_return_if_fail (n_param_values == 3); if (G_CCLOSURE_SWAP_DATA (closure)) { data1 = closure->data; data2 = param_values->data[0].v_pointer; } else { data1 = param_values->data[0].v_pointer; data2 = closure->data; } callback = (GMarshalFunc_VOID__ENUM_INT) (marshal_data ? marshal_data : cc->callback); callback (data1, g_value_get_enum (param_values + 1), g_value_get_int (param_values + 2), data2); } typedef struct _EggListBoxChildInfo EggListBoxChildInfo; struct _EggListBoxPrivate { GSequence *children; GHashTable *child_hash; GHashTable *separator_hash; GCompareDataFunc sort_func; gpointer sort_func_target; GDestroyNotify sort_func_target_destroy_notify; EggListBoxFilterFunc filter_func; gpointer filter_func_target; GDestroyNotify filter_func_target_destroy_notify; EggListBoxUpdateSeparatorFunc update_separator_func; gpointer update_separator_func_target; GDestroyNotify update_separator_func_target_destroy_notify; EggListBoxChildInfo *selected_child; EggListBoxChildInfo *prelight_child; EggListBoxChildInfo *cursor_child; gboolean active_child_active; EggListBoxChildInfo *active_child; GtkSelectionMode selection_mode; GtkAdjustment *adjustment; gboolean activate_single_click; /* DnD */ GtkWidget *drag_highlighted_widget; guint auto_scroll_timeout_id; }; struct _EggListBoxChildInfo { GSequenceIter *iter; GtkWidget *widget; GtkWidget *separator; gint y; gint height; }; enum { CHILD_SELECTED, CHILD_ACTIVATED, ACTIVATE_CURSOR_CHILD, TOGGLE_CURSOR_CHILD, MOVE_CURSOR, REFILTER, LAST_SIGNAL }; enum { PROP_0 }; G_DEFINE_TYPE (EggListBox, egg_list_box, GTK_TYPE_CONTAINER) static EggListBoxChildInfo *egg_list_box_find_child_at_y (EggListBox *list_box, gint y); static EggListBoxChildInfo *egg_list_box_lookup_info (EggListBox *list_box, GtkWidget *widget); static void egg_list_box_update_selected (EggListBox *list_box, EggListBoxChildInfo *child); static void egg_list_box_apply_filter_all (EggListBox *list_box); static void egg_list_box_update_separator (EggListBox *list_box, GSequenceIter *iter); static GSequenceIter * egg_list_box_get_next_visible (EggListBox *list_box, GSequenceIter *_iter); static void egg_list_box_apply_filter (EggListBox *list_box, GtkWidget *child); static void egg_list_box_add_move_binding (GtkBindingSet *binding_set, guint keyval, GdkModifierType modmask, GtkMovementStep step, gint count); static void egg_list_box_update_cursor (EggListBox *list_box, EggListBoxChildInfo *child); static void egg_list_box_select_and_activate (EggListBox *list_box, EggListBoxChildInfo *child); static void egg_list_box_update_prelight (EggListBox *list_box, EggListBoxChildInfo *child); static void egg_list_box_update_active (EggListBox *list_box, EggListBoxChildInfo *child); static gboolean egg_list_box_real_enter_notify_event (GtkWidget *widget, GdkEventCrossing *event); static gboolean egg_list_box_real_leave_notify_event (GtkWidget *widget, GdkEventCrossing *event); static gboolean egg_list_box_real_motion_notify_event (GtkWidget *widget, GdkEventMotion *event); static gboolean egg_list_box_real_button_press_event (GtkWidget *widget, GdkEventButton *event); static gboolean egg_list_box_real_button_release_event (GtkWidget *widget, GdkEventButton *event); static void egg_list_box_real_show (GtkWidget *widget); static gboolean egg_list_box_real_focus (GtkWidget *widget, GtkDirectionType direction); static GSequenceIter* egg_list_box_get_previous_visible (EggListBox *list_box, GSequenceIter *_iter); static EggListBoxChildInfo *egg_list_box_get_first_visible (EggListBox *list_box); static EggListBoxChildInfo *egg_list_box_get_last_visible (EggListBox *list_box); static gboolean egg_list_box_real_draw (GtkWidget *widget, cairo_t *cr); static void egg_list_box_real_realize (GtkWidget *widget); static void egg_list_box_real_add (GtkContainer *container, GtkWidget *widget); static void egg_list_box_real_remove (GtkContainer *container, GtkWidget *widget); static void egg_list_box_real_forall_internal (GtkContainer *container, gboolean include_internals, GtkCallback callback, void *callback_target); static void egg_list_box_real_compute_expand_internal (GtkWidget *widget, gboolean *hexpand, gboolean *vexpand); static GType egg_list_box_real_child_type (GtkContainer *container); static GtkSizeRequestMode egg_list_box_real_get_request_mode (GtkWidget *widget); static void egg_list_box_real_size_allocate (GtkWidget *widget, GtkAllocation *allocation); static void egg_list_box_real_drag_leave (GtkWidget *widget, GdkDragContext *context, guint time_); static gboolean egg_list_box_real_drag_motion (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time_); static void egg_list_box_real_activate_cursor_child (EggListBox *list_box); static void egg_list_box_real_toggle_cursor_child (EggListBox *list_box); static void egg_list_box_real_move_cursor (EggListBox *list_box, GtkMovementStep step, gint count); static void egg_list_box_real_refilter (EggListBox *list_box); static void egg_list_box_finalize (GObject *obj); static void egg_list_box_real_get_preferred_height (GtkWidget *widget, gint *minimum_height, gint *natural_height); static void egg_list_box_real_get_preferred_height_for_width (GtkWidget *widget, gint width, gint *minimum_height, gint *natural_height); static void egg_list_box_real_get_preferred_width (GtkWidget *widget, gint *minimum_width, gint *natural_width); static void egg_list_box_real_get_preferred_width_for_height (GtkWidget *widget, gint height, gint *minimum_width, gint *natural_width); static guint signals[LAST_SIGNAL] = { 0 }; static EggListBoxChildInfo* egg_list_box_child_info_new (GtkWidget *widget) { EggListBoxChildInfo *info; info = g_new0 (EggListBoxChildInfo, 1); info->widget = g_object_ref (widget); return info; } static void egg_list_box_child_info_free (EggListBoxChildInfo *info) { g_clear_object (&info->widget); g_clear_object (&info->separator); g_free (info); } EggListBox* egg_list_box_new (void) { return g_object_new (EGG_TYPE_LIST_BOX, NULL); } static void egg_list_box_init (EggListBox *list_box) { EggListBoxPrivate *priv; list_box->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (list_box, EGG_TYPE_LIST_BOX, EggListBoxPrivate); gtk_widget_set_can_focus (GTK_WIDGET (list_box), TRUE); gtk_widget_set_has_window (GTK_WIDGET (list_box), TRUE); gtk_widget_set_redraw_on_allocate (GTK_WIDGET (list_box), TRUE); priv->selection_mode = GTK_SELECTION_SINGLE; priv->activate_single_click = TRUE; priv->children = g_sequence_new ((GDestroyNotify)egg_list_box_child_info_free); priv->child_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL); priv->separator_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL); } static void egg_list_box_finalize (GObject *obj) { EggListBox *list_box = EGG_LIST_BOX (obj); EggListBoxPrivate *priv = list_box->priv; if (priv->auto_scroll_timeout_id != ((guint) 0)) g_source_remove (priv->auto_scroll_timeout_id); if (priv->sort_func_target_destroy_notify != NULL) priv->sort_func_target_destroy_notify (priv->sort_func_target); if (priv->filter_func_target_destroy_notify != NULL) priv->filter_func_target_destroy_notify (priv->filter_func_target); if (priv->update_separator_func_target_destroy_notify != NULL) priv->update_separator_func_target_destroy_notify (priv->update_separator_func_target); g_clear_object (&priv->adjustment); g_clear_object (&priv->drag_highlighted_widget); g_sequence_free (priv->children); g_hash_table_unref (priv->child_hash); g_hash_table_unref (priv->separator_hash); G_OBJECT_CLASS (egg_list_box_parent_class)->finalize (obj); } static void egg_list_box_class_init (EggListBoxClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); GtkBindingSet *binding_set; egg_list_box_parent_class = g_type_class_peek_parent (klass); g_type_class_add_private (klass, sizeof (EggListBoxPrivate)); gtk_widget_class_set_accessible_type (widget_class, EGG_TYPE_LIST_BOX_ACCESSIBLE); object_class->finalize = egg_list_box_finalize; widget_class->enter_notify_event = egg_list_box_real_enter_notify_event; widget_class->leave_notify_event = egg_list_box_real_leave_notify_event; widget_class->motion_notify_event = egg_list_box_real_motion_notify_event; widget_class->button_press_event = egg_list_box_real_button_press_event; widget_class->button_release_event = egg_list_box_real_button_release_event; widget_class->show = egg_list_box_real_show; widget_class->focus = egg_list_box_real_focus; widget_class->draw = egg_list_box_real_draw; widget_class->realize = egg_list_box_real_realize; widget_class->compute_expand = egg_list_box_real_compute_expand_internal; widget_class->get_request_mode = egg_list_box_real_get_request_mode; widget_class->get_preferred_height = egg_list_box_real_get_preferred_height; widget_class->get_preferred_height_for_width = egg_list_box_real_get_preferred_height_for_width; widget_class->get_preferred_width = egg_list_box_real_get_preferred_width; widget_class->get_preferred_width_for_height = egg_list_box_real_get_preferred_width_for_height; widget_class->size_allocate = egg_list_box_real_size_allocate; widget_class->drag_leave = egg_list_box_real_drag_leave; widget_class->drag_motion = egg_list_box_real_drag_motion; container_class->add = egg_list_box_real_add; container_class->remove = egg_list_box_real_remove; container_class->forall = egg_list_box_real_forall_internal; container_class->child_type = egg_list_box_real_child_type; klass->activate_cursor_child = egg_list_box_real_activate_cursor_child; klass->toggle_cursor_child = egg_list_box_real_toggle_cursor_child; klass->move_cursor = egg_list_box_real_move_cursor; klass->refilter = egg_list_box_real_refilter; signals[CHILD_SELECTED] = g_signal_new ("child-selected", EGG_TYPE_LIST_BOX, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EggListBoxClass, child_selected), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GTK_TYPE_WIDGET); signals[CHILD_ACTIVATED] = g_signal_new ("child-activated", EGG_TYPE_LIST_BOX, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EggListBoxClass, child_activated), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GTK_TYPE_WIDGET); signals[ACTIVATE_CURSOR_CHILD] = g_signal_new ("activate-cursor-child", EGG_TYPE_LIST_BOX, G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (EggListBoxClass, activate_cursor_child), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[TOGGLE_CURSOR_CHILD] = g_signal_new ("toggle-cursor-child", EGG_TYPE_LIST_BOX, G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (EggListBoxClass, toggle_cursor_child), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[MOVE_CURSOR] = g_signal_new ("move-cursor", EGG_TYPE_LIST_BOX, G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (EggListBoxClass, move_cursor), NULL, NULL, _egg_marshal_VOID__ENUM_INT, G_TYPE_NONE, 2, GTK_TYPE_MOVEMENT_STEP, G_TYPE_INT); signals[REFILTER] = g_signal_new ("refilter", EGG_TYPE_LIST_BOX, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EggListBoxClass, refilter), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); widget_class->activate_signal = signals[ACTIVATE_CURSOR_CHILD]; binding_set = gtk_binding_set_by_class (klass); egg_list_box_add_move_binding (binding_set, GDK_KEY_Home, 0, GTK_MOVEMENT_BUFFER_ENDS, -1); egg_list_box_add_move_binding (binding_set, GDK_KEY_KP_Home, 0, GTK_MOVEMENT_BUFFER_ENDS, -1); egg_list_box_add_move_binding (binding_set, GDK_KEY_End, 0, GTK_MOVEMENT_BUFFER_ENDS, 1); egg_list_box_add_move_binding (binding_set, GDK_KEY_KP_End, 0, GTK_MOVEMENT_BUFFER_ENDS, 1); egg_list_box_add_move_binding (binding_set, GDK_KEY_Up, GDK_CONTROL_MASK, GTK_MOVEMENT_DISPLAY_LINES, -1); egg_list_box_add_move_binding (binding_set, GDK_KEY_KP_Up, GDK_CONTROL_MASK, GTK_MOVEMENT_DISPLAY_LINES, -1); egg_list_box_add_move_binding (binding_set, GDK_KEY_Down, GDK_CONTROL_MASK, GTK_MOVEMENT_DISPLAY_LINES, 1); egg_list_box_add_move_binding (binding_set, GDK_KEY_KP_Down, GDK_CONTROL_MASK, GTK_MOVEMENT_DISPLAY_LINES, 1); egg_list_box_add_move_binding (binding_set, GDK_KEY_Page_Up, 0, GTK_MOVEMENT_PAGES, -1); egg_list_box_add_move_binding (binding_set, GDK_KEY_KP_Page_Up, 0, GTK_MOVEMENT_PAGES, -1); egg_list_box_add_move_binding (binding_set, GDK_KEY_Page_Down, 0, GTK_MOVEMENT_PAGES, 1); egg_list_box_add_move_binding (binding_set, GDK_KEY_KP_Page_Down, 0, GTK_MOVEMENT_PAGES, 1); gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, GDK_CONTROL_MASK, "toggle-cursor-child", 0, NULL); } GtkWidget * egg_list_box_get_selected_child (EggListBox *list_box) { EggListBoxPrivate *priv = list_box->priv; g_return_val_if_fail (list_box != NULL, NULL); if (priv->selected_child != NULL) return priv->selected_child->widget; return NULL; } GtkWidget * egg_list_box_get_child_at_y (EggListBox *list_box, gint y) { EggListBoxChildInfo *child; g_return_val_if_fail (list_box != NULL, NULL); child = egg_list_box_find_child_at_y (list_box, y); if (child == NULL) return NULL; return child->widget; } void egg_list_box_select_child (EggListBox *list_box, GtkWidget *child) { EggListBoxChildInfo *info = NULL; g_return_if_fail (list_box != NULL); if (child != NULL) info = egg_list_box_lookup_info (list_box, child); egg_list_box_update_selected (list_box, info); } void egg_list_box_set_adjustment (EggListBox *list_box, GtkAdjustment *adjustment) { EggListBoxPrivate *priv = list_box->priv; g_return_if_fail (list_box != NULL); g_object_ref (adjustment); if (priv->adjustment) g_object_unref (priv->adjustment); priv->adjustment = adjustment; gtk_container_set_focus_vadjustment (GTK_CONTAINER (list_box), adjustment); } void egg_list_box_add_to_scrolled (EggListBox *list_box, GtkScrolledWindow *scrolled) { g_return_if_fail (list_box != NULL); g_return_if_fail (scrolled != NULL); gtk_scrolled_window_add_with_viewport (scrolled, GTK_WIDGET (list_box)); egg_list_box_set_adjustment (list_box, gtk_scrolled_window_get_vadjustment (scrolled)); } void egg_list_box_set_selection_mode (EggListBox *list_box, GtkSelectionMode mode) { EggListBoxPrivate *priv = list_box->priv; g_return_if_fail (list_box != NULL); if (mode == GTK_SELECTION_MULTIPLE) { g_warning ("egg-list-box.vala:115: Multiple selections not supported"); return; } priv->selection_mode = mode; if (mode == GTK_SELECTION_NONE) egg_list_box_update_selected (list_box, NULL); } void egg_list_box_set_filter_func (EggListBox *list_box, EggListBoxFilterFunc f, void *f_target, GDestroyNotify f_target_destroy_notify) { EggListBoxPrivate *priv = list_box->priv; g_return_if_fail (list_box != NULL); if (priv->filter_func_target_destroy_notify != NULL) priv->filter_func_target_destroy_notify (priv->filter_func_target); priv->filter_func = f; priv->filter_func_target = f_target; priv->filter_func_target_destroy_notify = f_target_destroy_notify; egg_list_box_refilter (list_box); } void egg_list_box_set_separator_funcs (EggListBox *list_box, EggListBoxUpdateSeparatorFunc update_separator, void *update_separator_target, GDestroyNotify update_separator_target_destroy_notify) { EggListBoxPrivate *priv = list_box->priv; g_return_if_fail (list_box != NULL); if (priv->update_separator_func_target_destroy_notify != NULL) priv->update_separator_func_target_destroy_notify (priv->update_separator_func_target); priv->update_separator_func = update_separator; priv->update_separator_func_target = update_separator_target; priv->update_separator_func_target_destroy_notify = update_separator_target_destroy_notify; egg_list_box_reseparate (list_box); } static void egg_list_box_real_refilter (EggListBox *list_box) { egg_list_box_apply_filter_all (list_box); egg_list_box_reseparate (list_box); gtk_widget_queue_resize (GTK_WIDGET (list_box)); } void egg_list_box_refilter (EggListBox *list_box) { g_return_if_fail (list_box != NULL); g_signal_emit (list_box, signals[REFILTER], 0); } static gint do_sort (EggListBoxChildInfo *a, EggListBoxChildInfo *b, EggListBox *list_box) { EggListBoxPrivate *priv = list_box->priv; return priv->sort_func (a->widget, b->widget, priv->sort_func_target); } void egg_list_box_resort (EggListBox *list_box) { EggListBoxPrivate *priv = list_box->priv; g_return_if_fail (list_box != NULL); g_sequence_sort (priv->children, (GCompareDataFunc)do_sort, list_box); egg_list_box_reseparate (list_box); gtk_widget_queue_resize (GTK_WIDGET (list_box)); } void egg_list_box_reseparate (EggListBox *list_box) { EggListBoxPrivate *priv = list_box->priv; GSequenceIter *iter; g_return_if_fail (list_box != NULL); for (iter = g_sequence_get_begin_iter (priv->children); !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) egg_list_box_update_separator (list_box, iter); gtk_widget_queue_resize (GTK_WIDGET (list_box)); } void egg_list_box_set_sort_func (EggListBox *list_box, GCompareDataFunc f, void *f_target, GDestroyNotify f_target_destroy_notify) { EggListBoxPrivate *priv = list_box->priv; g_return_if_fail (list_box != NULL); if (priv->sort_func_target_destroy_notify != NULL) priv->sort_func_target_destroy_notify (priv->sort_func_target); priv->sort_func = f; priv->sort_func_target = f_target; priv->sort_func_target_destroy_notify = f_target_destroy_notify; egg_list_box_resort (list_box); } void egg_list_box_child_changed (EggListBox *list_box, GtkWidget *widget) { EggListBoxPrivate *priv = list_box->priv; EggListBoxChildInfo *info; GSequenceIter *prev_next, *next; g_return_if_fail (list_box != NULL); g_return_if_fail (widget != NULL); info = egg_list_box_lookup_info (list_box, widget); if (info == NULL) return; prev_next = egg_list_box_get_next_visible (list_box, info->iter); if (priv->sort_func != NULL) { g_sequence_sort_changed (info->iter, (GCompareDataFunc)do_sort, list_box); gtk_widget_queue_resize (GTK_WIDGET (list_box)); } egg_list_box_apply_filter (list_box, info->widget); if (gtk_widget_get_visible (GTK_WIDGET (list_box))) { next = egg_list_box_get_next_visible (list_box, info->iter); egg_list_box_update_separator (list_box, info->iter); egg_list_box_update_separator (list_box, next); egg_list_box_update_separator (list_box, prev_next); } } void egg_list_box_set_activate_on_single_click (EggListBox *list_box, gboolean single) { EggListBoxPrivate *priv = list_box->priv; g_return_if_fail (list_box != NULL); priv->activate_single_click = single; } static void egg_list_box_add_move_binding (GtkBindingSet *binding_set, guint keyval, GdkModifierType modmask, GtkMovementStep step, gint count) { gtk_binding_entry_add_signal (binding_set, keyval, modmask, "move-cursor", (guint) 2, GTK_TYPE_MOVEMENT_STEP, step, G_TYPE_INT, count, NULL); if ((modmask & GDK_CONTROL_MASK) == GDK_CONTROL_MASK) return; gtk_binding_entry_add_signal (binding_set, keyval, GDK_CONTROL_MASK, "move-cursor", (guint) 2, GTK_TYPE_MOVEMENT_STEP, step, G_TYPE_INT, count, NULL); } static EggListBoxChildInfo* egg_list_box_find_child_at_y (EggListBox *list_box, gint y) { EggListBoxPrivate *priv = list_box->priv; EggListBoxChildInfo *child_info; GSequenceIter *iter; EggListBoxChildInfo *info; child_info = NULL; for (iter = g_sequence_get_begin_iter (priv->children); !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) { info = (EggListBoxChildInfo*) g_sequence_get (iter); if (y >= info->y && y < (info->y + info->height)) { child_info = info; break; } } return child_info; } static void egg_list_box_update_cursor (EggListBox *list_box, EggListBoxChildInfo *child) { EggListBoxPrivate *priv = list_box->priv; priv->cursor_child = child; gtk_widget_grab_focus (GTK_WIDGET (list_box)); gtk_widget_queue_draw (GTK_WIDGET (list_box)); if (child != NULL && priv->adjustment != NULL) { GtkAllocation allocation; gtk_widget_get_allocation (GTK_WIDGET (list_box), &allocation); gtk_adjustment_clamp_page (priv->adjustment, priv->cursor_child->y + allocation.y, priv->cursor_child->y + allocation.y + priv->cursor_child->height); } _egg_list_box_accessible_update_cursor (list_box, child ? child->widget : NULL); } static void egg_list_box_update_selected (EggListBox *list_box, EggListBoxChildInfo *child) { EggListBoxPrivate *priv = list_box->priv; if (child != priv->selected_child && (child == NULL || priv->selection_mode != GTK_SELECTION_NONE)) { priv->selected_child = child; g_signal_emit (list_box, signals[CHILD_SELECTED], 0, (priv->selected_child != NULL) ? priv->selected_child->widget : NULL); gtk_widget_queue_draw (GTK_WIDGET (list_box)); } _egg_list_box_accessible_update_selected (list_box, child ? child->widget : NULL); if (child != NULL) egg_list_box_update_cursor (list_box, child); } static void egg_list_box_select_and_activate (EggListBox *list_box, EggListBoxChildInfo *child) { GtkWidget *w = NULL; if (child != NULL) w = child->widget; egg_list_box_update_selected (list_box, child); if (w != NULL) g_signal_emit (list_box, signals[CHILD_ACTIVATED], 0, w); } static void egg_list_box_update_prelight (EggListBox *list_box, EggListBoxChildInfo *child) { EggListBoxPrivate *priv = list_box->priv; if (child != priv->prelight_child) { priv->prelight_child = child; gtk_widget_queue_draw (GTK_WIDGET (list_box)); } } static void egg_list_box_update_active (EggListBox *list_box, EggListBoxChildInfo *child) { EggListBoxPrivate *priv = list_box->priv; gboolean val; val = priv->active_child == child; if (priv->active_child != NULL && val != priv->active_child_active) { priv->active_child_active = val; gtk_widget_queue_draw (GTK_WIDGET (list_box)); } } static gboolean egg_list_box_real_enter_notify_event (GtkWidget *widget, GdkEventCrossing *event) { EggListBox *list_box = EGG_LIST_BOX (widget); EggListBoxChildInfo *child; if (event->window != gtk_widget_get_window (GTK_WIDGET (list_box))) return FALSE; child = egg_list_box_find_child_at_y (list_box, event->y); egg_list_box_update_prelight (list_box, child); egg_list_box_update_active (list_box, child); return FALSE; } static gboolean egg_list_box_real_leave_notify_event (GtkWidget *widget, GdkEventCrossing *event) { EggListBox *list_box = EGG_LIST_BOX (widget); EggListBoxChildInfo *child = NULL; if (event->window != gtk_widget_get_window (GTK_WIDGET (list_box))) return FALSE; if (event->detail != GDK_NOTIFY_INFERIOR) child = NULL; else child = egg_list_box_find_child_at_y (list_box, event->y); egg_list_box_update_prelight (list_box, child); egg_list_box_update_active (list_box, child); return FALSE; } static gboolean egg_list_box_real_motion_notify_event (GtkWidget *widget, GdkEventMotion *event) { EggListBox *list_box = EGG_LIST_BOX (widget); EggListBoxChildInfo *child; GdkWindow *window, *event_window; gint relative_y; gdouble parent_y; window = gtk_widget_get_window (GTK_WIDGET (list_box)); event_window = event->window; relative_y = event->y; while ((event_window != NULL) && (event_window != window)) { gdk_window_coords_to_parent (event_window, 0, relative_y, NULL, &parent_y); relative_y = parent_y; event_window = gdk_window_get_effective_parent (event_window); } child = egg_list_box_find_child_at_y (list_box, relative_y); egg_list_box_update_prelight (list_box, child); egg_list_box_update_active (list_box, child); return FALSE; } static gboolean egg_list_box_real_button_press_event (GtkWidget *widget, GdkEventButton *event) { EggListBox *list_box = EGG_LIST_BOX (widget); EggListBoxPrivate *priv = list_box->priv; if (event->button == 1) { EggListBoxChildInfo *child; child = egg_list_box_find_child_at_y (list_box, event->y); if (child != NULL) { priv->active_child = child; priv->active_child_active = TRUE; gtk_widget_queue_draw (GTK_WIDGET (list_box)); if (event->type == GDK_2BUTTON_PRESS && !priv->activate_single_click) g_signal_emit (list_box, signals[CHILD_ACTIVATED], 0, child->widget); } /* TODO: Should mark as active while down, and handle grab breaks */ } return FALSE; } static gboolean egg_list_box_real_button_release_event (GtkWidget *widget, GdkEventButton *event) { EggListBox *list_box = EGG_LIST_BOX (widget); EggListBoxPrivate *priv = list_box->priv; if (event->button == 1) { if (priv->active_child != NULL && priv->active_child_active) { if (priv->activate_single_click) egg_list_box_select_and_activate (list_box, priv->active_child); else egg_list_box_update_selected (list_box, priv->active_child); } priv->active_child = NULL; priv->active_child_active = FALSE; gtk_widget_queue_draw (GTK_WIDGET (list_box)); } return FALSE; } static void egg_list_box_real_show (GtkWidget *widget) { EggListBox * list_box = EGG_LIST_BOX (widget); egg_list_box_reseparate (list_box); GTK_WIDGET_CLASS (egg_list_box_parent_class)->show ((GtkWidget*) G_TYPE_CHECK_INSTANCE_CAST (list_box, GTK_TYPE_CONTAINER, GtkContainer)); } static gboolean egg_list_box_real_focus (GtkWidget* widget, GtkDirectionType direction) { EggListBox *list_box = EGG_LIST_BOX (widget); EggListBoxPrivate *priv = list_box->priv; gboolean had_focus = FALSE; gboolean focus_into = FALSE; GtkWidget* recurse_into; EggListBoxChildInfo *current_focus_child; EggListBoxChildInfo *next_focus_child; gboolean modify_selection_pressed; GdkModifierType state = 0; recurse_into = NULL; focus_into = TRUE; g_object_get (GTK_WIDGET (list_box), "has-focus", &had_focus, NULL); current_focus_child = NULL; next_focus_child = NULL; if (had_focus) { /* If on row, going right, enter into possible container */ if (direction == GTK_DIR_RIGHT || direction == GTK_DIR_TAB_FORWARD) { if (priv->cursor_child != NULL) recurse_into = priv->cursor_child->widget; } current_focus_child = priv->cursor_child; /* Unless we're going up/down we're always leaving the container */ if (direction != GTK_DIR_UP && direction != GTK_DIR_DOWN) focus_into = FALSE; } else if (gtk_container_get_focus_child ((GtkContainer*) list_box) != NULL) { /* There is a focus child, always navigat inside it first */ recurse_into = gtk_container_get_focus_child ((GtkContainer*) list_box); current_focus_child = egg_list_box_lookup_info (list_box, recurse_into); /* If exiting child container to the right, exit row */ if (direction == GTK_DIR_RIGHT || direction == GTK_DIR_TAB_FORWARD) focus_into = FALSE; /* If exiting child container to the left, select row or out */ if (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD) next_focus_child = current_focus_child; } else { /* If coming from the left, enter into possible container */ if (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD) { if (priv->selected_child != NULL) recurse_into = priv->selected_child->widget; } } if (recurse_into != NULL) { if (gtk_widget_child_focus (recurse_into, direction)) return TRUE; } if (!focus_into) return FALSE; /* Focus is leaving us */ /* TODO: This doesn't handle up/down going into a focusable separator */ if (next_focus_child == NULL) { if (current_focus_child != NULL) { GSequenceIter* i; if (direction == GTK_DIR_UP) { i = egg_list_box_get_previous_visible (list_box, current_focus_child->iter); if (i != NULL) next_focus_child = g_sequence_get (i); } else { i = egg_list_box_get_next_visible (list_box, current_focus_child->iter); if (!g_sequence_iter_is_end (i)) next_focus_child = g_sequence_get (i); } } else { switch (direction) { case GTK_DIR_UP: case GTK_DIR_TAB_BACKWARD: next_focus_child = priv->selected_child; if (next_focus_child == NULL) next_focus_child = egg_list_box_get_last_visible (list_box); break; default: next_focus_child = priv->selected_child; if (next_focus_child == NULL) next_focus_child = egg_list_box_get_first_visible (list_box); break; } } } if (next_focus_child == NULL) { if (direction == GTK_DIR_UP || direction == GTK_DIR_DOWN) { if (gtk_widget_keynav_failed (GTK_WIDGET (list_box), direction)) return TRUE; } return FALSE; } modify_selection_pressed = FALSE; if (gtk_get_current_event_state (&state)) { GdkModifierType modify_mod_mask; modify_mod_mask = gtk_widget_get_modifier_mask (GTK_WIDGET (list_box), GDK_MODIFIER_INTENT_MODIFY_SELECTION); if ((state & modify_mod_mask) == modify_mod_mask) modify_selection_pressed = TRUE; } egg_list_box_update_cursor (list_box, next_focus_child); if (!modify_selection_pressed) egg_list_box_update_selected (list_box, next_focus_child); return TRUE; } typedef struct { EggListBoxChildInfo *child; GtkStateFlags state; } ChildFlags; static ChildFlags* child_flags_find_or_add (ChildFlags *array, int *array_length, EggListBoxChildInfo *to_find) { gint i; for (i = 0; i < *array_length; i++) { if (array[i].child == to_find) return &array[i]; } *array_length = *array_length + 1; array[*array_length - 1].child = to_find; array[*array_length - 1].state = 0; return &array[*array_length - 1]; } static gboolean egg_list_box_real_draw (GtkWidget* widget, cairo_t* cr) { EggListBox * list_box = EGG_LIST_BOX (widget); EggListBoxPrivate *priv = list_box->priv; GtkAllocation allocation = {0}; GtkStyleContext* context; GtkStateFlags state; ChildFlags flags[3], *found; gint flags_length; gint focus_pad; int i; gtk_widget_get_allocation (GTK_WIDGET (list_box), &allocation); context = gtk_widget_get_style_context (GTK_WIDGET (list_box)); state = gtk_widget_get_state_flags (widget); gtk_render_background (context, cr, (gdouble) 0, (gdouble) 0, (gdouble) allocation.width, (gdouble) allocation.height); flags_length = 0; if (priv->selected_child != NULL) { found = child_flags_find_or_add (flags, &flags_length, priv->selected_child); found->state |= (state | GTK_STATE_FLAG_SELECTED); } if (priv->prelight_child != NULL) { found = child_flags_find_or_add (flags, &flags_length, priv->prelight_child); found->state |= (state | GTK_STATE_FLAG_PRELIGHT); } if (priv->active_child != NULL && priv->active_child_active) { found = child_flags_find_or_add (flags, &flags_length, priv->active_child); found->state |= (state | GTK_STATE_FLAG_ACTIVE); } for (i = 0; i < flags_length; i++) { ChildFlags *flag = &flags[i]; gtk_style_context_save (context); gtk_style_context_set_state (context, flag->state); gtk_render_background (context, cr, 0, flag->child->y, allocation.width, flag->child->height); gtk_style_context_restore (context); } if (gtk_widget_has_visible_focus (GTK_WIDGET (list_box)) && priv->cursor_child != NULL) { gtk_style_context_get_style (context, "focus-padding", &focus_pad, NULL); gtk_render_focus (context, cr, focus_pad, priv->cursor_child->y + focus_pad, allocation.width - 2 * focus_pad, priv->cursor_child->height - 2 * focus_pad); } GTK_WIDGET_CLASS (egg_list_box_parent_class)->draw ((GtkWidget*) G_TYPE_CHECK_INSTANCE_CAST (list_box, GTK_TYPE_CONTAINER, GtkContainer), cr); return TRUE; } static void egg_list_box_real_realize (GtkWidget* widget) { EggListBox *list_box = EGG_LIST_BOX (widget); GtkAllocation allocation; GdkWindowAttr attributes = {0}; GdkWindow *window; gtk_widget_get_allocation (GTK_WIDGET (list_box), &allocation); gtk_widget_set_realized (GTK_WIDGET (list_box), TRUE); attributes.x = allocation.x; attributes.y = allocation.y; attributes.width = allocation.width; attributes.height = allocation.height; attributes.window_type = GDK_WINDOW_CHILD; attributes.event_mask = gtk_widget_get_events (GTK_WIDGET (list_box)) | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_POINTER_MOTION_MASK | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK; attributes.wclass = GDK_INPUT_OUTPUT; window = gdk_window_new (gtk_widget_get_parent_window (GTK_WIDGET (list_box)), &attributes, GDK_WA_X | GDK_WA_Y); gtk_style_context_set_background (gtk_widget_get_style_context (GTK_WIDGET (list_box)), window); gdk_window_set_user_data (window, (GObject*) list_box); gtk_widget_set_window (GTK_WIDGET (list_box), window); /* Passes ownership */ } static void egg_list_box_apply_filter (EggListBox *list_box, GtkWidget *child) { EggListBoxPrivate *priv = list_box->priv; gboolean do_show; do_show = TRUE; if (priv->filter_func != NULL) do_show = priv->filter_func (child, priv->filter_func_target); gtk_widget_set_child_visible (child, do_show); } static void egg_list_box_apply_filter_all (EggListBox *list_box) { EggListBoxPrivate *priv = list_box->priv; EggListBoxChildInfo *child_info; GSequenceIter *iter; for (iter = g_sequence_get_begin_iter (priv->children); !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) { child_info = g_sequence_get (iter); egg_list_box_apply_filter (list_box, child_info->widget); } } /* Children are visible if they are shown by the app (visible) and not filtered out (child_visible) by the listbox */ static gboolean child_is_visible (GtkWidget *child) { return gtk_widget_get_visible (child) && gtk_widget_get_child_visible (child); } static EggListBoxChildInfo* egg_list_box_get_first_visible (EggListBox *list_box) { EggListBoxPrivate *priv = list_box->priv; EggListBoxChildInfo *child_info; GSequenceIter *iter; for (iter = g_sequence_get_begin_iter (priv->children); !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) { child_info = g_sequence_get (iter); if (child_is_visible (child_info->widget)) return child_info; } return NULL; } static EggListBoxChildInfo* egg_list_box_get_last_visible (EggListBox *list_box) { EggListBoxPrivate *priv = list_box->priv; EggListBoxChildInfo *child_info; GSequenceIter *iter; iter = g_sequence_get_end_iter (priv->children); while (!g_sequence_iter_is_begin (iter)) { iter = g_sequence_iter_prev (iter); child_info = g_sequence_get (iter); if (child_is_visible (child_info->widget)) return child_info; } return NULL; } static GSequenceIter* egg_list_box_get_previous_visible (EggListBox *list_box, GSequenceIter* iter) { EggListBoxChildInfo *child_info; if (g_sequence_iter_is_begin (iter)) return NULL; do { iter = g_sequence_iter_prev (iter); child_info = g_sequence_get (iter); if (child_is_visible (child_info->widget)) return iter; } while (!g_sequence_iter_is_begin (iter)); return NULL; } static GSequenceIter* egg_list_box_get_next_visible (EggListBox *list_box, GSequenceIter* iter) { EggListBoxChildInfo *child_info; if (g_sequence_iter_is_end (iter)) return iter; do { iter = g_sequence_iter_next (iter); if (!g_sequence_iter_is_end (iter)) { child_info = g_sequence_get (iter); if (child_is_visible (child_info->widget)) return iter; } } while (!g_sequence_iter_is_end (iter)); return iter; } static void egg_list_box_update_separator (EggListBox *list_box, GSequenceIter* iter) { EggListBoxPrivate *priv = list_box->priv; EggListBoxChildInfo *info; GSequenceIter *before_iter; GtkWidget *child; GtkWidget *before_child; EggListBoxChildInfo *before_info; GtkWidget *old_separator; if (iter == NULL || g_sequence_iter_is_end (iter)) return; info = g_sequence_get (iter); before_iter = egg_list_box_get_previous_visible (list_box, iter); child = info->widget; if (child) g_object_ref (child); before_child = NULL; if (before_iter != NULL) { before_info = g_sequence_get (before_iter); before_child = before_info->widget; if (before_child) g_object_ref (before_child); } if (priv->update_separator_func != NULL && child_is_visible (child)) { old_separator = info->separator; if (old_separator) g_object_ref (old_separator); priv->update_separator_func (&info->separator, child, before_child, priv->update_separator_func_target); if (old_separator != info->separator) { if (old_separator != NULL) { gtk_widget_unparent (old_separator); g_hash_table_remove (priv->separator_hash, old_separator); } if (info->separator != NULL) { g_hash_table_insert (priv->separator_hash, info->separator, info); gtk_widget_set_parent (info->separator, GTK_WIDGET (list_box)); gtk_widget_show (info->separator); } gtk_widget_queue_resize (GTK_WIDGET (list_box)); } if (old_separator) g_object_unref (old_separator); } else { if (info->separator != NULL) { g_hash_table_remove (priv->separator_hash, info->separator); gtk_widget_unparent (info->separator); g_clear_object (&info->separator); gtk_widget_queue_resize (GTK_WIDGET (list_box)); } } if (before_child) g_object_unref (before_child); if (child) g_object_unref (child); } static EggListBoxChildInfo* egg_list_box_lookup_info (EggListBox *list_box, GtkWidget* child) { EggListBoxPrivate *priv = list_box->priv; return g_hash_table_lookup (priv->child_hash, child); } static void child_visibility_changed (GObject* object, GParamSpec* pspec, EggListBox *list_box) { EggListBoxChildInfo *info; if (gtk_widget_get_visible (GTK_WIDGET (list_box))) { info = egg_list_box_lookup_info (list_box, GTK_WIDGET (object)); if (info != NULL) { egg_list_box_update_separator (list_box, info->iter); egg_list_box_update_separator (list_box, egg_list_box_get_next_visible (list_box, info->iter)); } } } static void egg_list_box_real_add (GtkContainer* container, GtkWidget* child) { EggListBox *list_box = EGG_LIST_BOX (container); EggListBoxPrivate *priv = list_box->priv; EggListBoxChildInfo *info; GSequenceIter* iter = NULL; info = egg_list_box_child_info_new (child); g_hash_table_insert (priv->child_hash, child, info); if (priv->sort_func != NULL) iter = g_sequence_insert_sorted (priv->children, info, (GCompareDataFunc)do_sort, list_box); else iter = g_sequence_append (priv->children, info); info->iter = iter; gtk_widget_set_parent (child, GTK_WIDGET (list_box)); egg_list_box_apply_filter (list_box, child); if (gtk_widget_get_visible (GTK_WIDGET (list_box))) { egg_list_box_update_separator (list_box, iter); egg_list_box_update_separator (list_box, egg_list_box_get_next_visible (list_box, iter)); } g_signal_connect_object (child, "notify::visible", (GCallback) child_visibility_changed, list_box, 0); } static void egg_list_box_real_remove (GtkContainer* container, GtkWidget* child) { EggListBox *list_box = EGG_LIST_BOX (container); EggListBoxPrivate *priv = list_box->priv; gboolean was_visible; EggListBoxChildInfo *info; GSequenceIter *next; g_return_if_fail (child != NULL); was_visible = gtk_widget_get_visible (child); g_signal_handlers_disconnect_by_func (child, (GCallback) child_visibility_changed, list_box); info = egg_list_box_lookup_info (list_box, child); if (info == NULL) { info = g_hash_table_lookup (priv->separator_hash, child); if (info != NULL) { g_hash_table_remove (priv->separator_hash, child); g_clear_object (&info->separator); gtk_widget_unparent (child); if (was_visible && gtk_widget_get_visible (GTK_WIDGET (list_box))) gtk_widget_queue_resize (GTK_WIDGET (list_box)); } else { g_warning ("egg-list-box.vala:846: Tried to remove non-child %p\n", child); } return; } if (info->separator != NULL) { g_hash_table_remove (priv->separator_hash, info->separator); gtk_widget_unparent (info->separator); g_clear_object (&info->separator); } if (info == priv->selected_child) egg_list_box_update_selected (list_box, NULL); if (info == priv->prelight_child) priv->prelight_child = NULL; if (info == priv->cursor_child) priv->cursor_child = NULL; if (info == priv->active_child) priv->active_child = NULL; next = egg_list_box_get_next_visible (list_box, info->iter); gtk_widget_unparent (child); g_hash_table_remove (priv->child_hash, child); g_sequence_remove (info->iter); if (gtk_widget_get_visible (GTK_WIDGET (list_box))) egg_list_box_update_separator (list_box, next); if (was_visible && gtk_widget_get_visible (GTK_WIDGET (list_box))) gtk_widget_queue_resize (GTK_WIDGET (list_box)); } static void egg_list_box_real_forall_internal (GtkContainer* container, gboolean include_internals, GtkCallback callback, void* callback_target) { EggListBox *list_box = EGG_LIST_BOX (container); EggListBoxPrivate *priv = list_box->priv; GSequenceIter *iter; EggListBoxChildInfo *child_info; iter = g_sequence_get_begin_iter (priv->children); while (!g_sequence_iter_is_end (iter)) { child_info = g_sequence_get (iter); iter = g_sequence_iter_next (iter); if (child_info->separator != NULL && include_internals) callback (child_info->separator, callback_target); callback (child_info->widget, callback_target); } } static void egg_list_box_real_compute_expand_internal (GtkWidget* widget, gboolean* hexpand, gboolean* vexpand) { GTK_WIDGET_CLASS (egg_list_box_parent_class)->compute_expand (widget, hexpand, vexpand); /* We don't expand vertically beyound the minimum size */ if (vexpand) *vexpand = FALSE; } static GType egg_list_box_real_child_type (GtkContainer* container) { return GTK_TYPE_WIDGET; } static GtkSizeRequestMode egg_list_box_real_get_request_mode (GtkWidget* widget) { return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; } static void egg_list_box_real_get_preferred_height (GtkWidget* widget, gint* minimum_height, gint* natural_height) { gint natural_width; egg_list_box_real_get_preferred_width (widget, NULL, &natural_width); egg_list_box_real_get_preferred_height_for_width (widget, natural_width, minimum_height, natural_height); } static void egg_list_box_real_get_preferred_height_for_width (GtkWidget* widget, gint width, gint* minimum_height_out, gint* natural_height_out) { EggListBox *list_box = EGG_LIST_BOX (widget); EggListBoxPrivate *priv = list_box->priv; GSequenceIter *iter; gint minimum_height; gint natural_height; GtkStyleContext *context; gint focus_width; gint focus_pad; minimum_height = 0; context = gtk_widget_get_style_context (GTK_WIDGET (list_box)); gtk_style_context_get_style (context, "focus-line-width", &focus_width, "focus-padding", &focus_pad, NULL); for (iter = g_sequence_get_begin_iter (priv->children); !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) { EggListBoxChildInfo *child_info; GtkWidget *child; gint child_min = 0; child_info = g_sequence_get (iter); child = child_info->widget; if (!child_is_visible (child)) continue; if (child_info->separator != NULL) { gtk_widget_get_preferred_height_for_width (child_info->separator, width, &child_min, NULL); minimum_height += child_min; } gtk_widget_get_preferred_height_for_width (child, width - 2 * (focus_width + focus_pad), &child_min, NULL); minimum_height += child_min + 2 * (focus_width + focus_pad); } /* We always allocate the minimum height, since handling expanding rows is way too costly, and unlikely to be used, as lists are generally put inside a scrolling window anyway. */ natural_height = minimum_height; if (minimum_height_out) *minimum_height_out = minimum_height; if (natural_height_out) *natural_height_out = natural_height; } static void egg_list_box_real_get_preferred_width (GtkWidget* widget, gint* minimum_width_out, gint* natural_width_out) { EggListBox *list_box = EGG_LIST_BOX (widget); EggListBoxPrivate *priv = list_box->priv; gint minimum_width; gint natural_width; GtkStyleContext *context; gint focus_width; gint focus_pad; GSequenceIter *iter; EggListBoxChildInfo *child_info; GtkWidget *child; gint child_min; gint child_nat; context = gtk_widget_get_style_context (GTK_WIDGET (list_box)); gtk_style_context_get_style (context, "focus-line-width", &focus_width, "focus-padding", &focus_pad, NULL); minimum_width = 0; natural_width = 0; for (iter = g_sequence_get_begin_iter (priv->children); !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) { child_info = g_sequence_get (iter); child = child_info->widget; if (!child_is_visible (child)) continue; gtk_widget_get_preferred_width (child, &child_min, &child_nat); minimum_width = MAX (minimum_width, child_min + 2 * (focus_width + focus_pad)); natural_width = MAX (natural_width, child_nat + 2 * (focus_width + focus_pad)); if (child_info->separator != NULL) { gtk_widget_get_preferred_width (child_info->separator, &child_min, &child_nat); minimum_width = MAX (minimum_width, child_min); natural_width = MAX (natural_width, child_nat); } } if (minimum_width_out) *minimum_width_out = minimum_width; if (natural_width_out) *natural_width_out = natural_width; } static void egg_list_box_real_get_preferred_width_for_height (GtkWidget *widget, gint height, gint *minimum_width, gint *natural_width) { EggListBox *list_box = EGG_LIST_BOX (widget); egg_list_box_real_get_preferred_width (GTK_WIDGET (list_box), minimum_width, natural_width); } static void egg_list_box_real_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { EggListBox *list_box = EGG_LIST_BOX (widget); EggListBoxPrivate *priv = list_box->priv; GtkAllocation child_allocation; GtkAllocation separator_allocation; EggListBoxChildInfo *child_info; GdkWindow *window; GtkWidget *child; GSequenceIter *iter; GtkStyleContext *context; gint focus_width; gint focus_pad; int child_min; child_allocation.x = 0; child_allocation.y = 0; child_allocation.width = 0; child_allocation.height = 0; separator_allocation.x = 0; separator_allocation.y = 0; separator_allocation.width = 0; separator_allocation.height = 0; gtk_widget_set_allocation (GTK_WIDGET (list_box), allocation); window = gtk_widget_get_window (GTK_WIDGET (list_box)); if (window != NULL) gdk_window_move_resize (window, allocation->x, allocation->y, allocation->width, allocation->height); context = gtk_widget_get_style_context (GTK_WIDGET (list_box)); gtk_style_context_get_style (context, "focus-line-width", &focus_width, "focus-padding", &focus_pad, NULL); child_allocation.x = 0 + focus_width + focus_pad; child_allocation.y = 0; child_allocation.width = allocation->width - 2 * (focus_width + focus_pad); separator_allocation.x = 0; separator_allocation.width = allocation->width; for (iter = g_sequence_get_begin_iter (priv->children); !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) { child_info = g_sequence_get (iter); child = child_info->widget; if (!child_is_visible (child)) { child_info->y = child_allocation.y; child_info->height = 0; continue; } if (child_info->separator != NULL) { gtk_widget_get_preferred_height_for_width (child_info->separator, allocation->width, &child_min, NULL); separator_allocation.height = child_min; separator_allocation.y = child_allocation.y; gtk_widget_size_allocate (child_info->separator, &separator_allocation); child_allocation.y += child_min; } child_info->y = child_allocation.y; child_allocation.y += focus_width + focus_pad; gtk_widget_get_preferred_height_for_width (child, child_allocation.width, &child_min, NULL); child_allocation.height = child_min; child_info->height = child_allocation.height + 2 * (focus_width + focus_pad); gtk_widget_size_allocate (child, &child_allocation); child_allocation.y += child_min + focus_width + focus_pad; } } void egg_list_box_drag_unhighlight_widget (EggListBox *list_box) { EggListBoxPrivate *priv = list_box->priv; g_return_if_fail (list_box != NULL); if (priv->drag_highlighted_widget == NULL) return; gtk_drag_unhighlight (priv->drag_highlighted_widget); g_clear_object (&priv->drag_highlighted_widget); } void egg_list_box_drag_highlight_widget (EggListBox *list_box, GtkWidget *child) { EggListBoxPrivate *priv = list_box->priv; GtkWidget *old_highlight; g_return_if_fail (list_box != NULL); g_return_if_fail (child != NULL); if (priv->drag_highlighted_widget == child) return; egg_list_box_drag_unhighlight_widget (list_box); gtk_drag_highlight (child); old_highlight = priv->drag_highlighted_widget; priv->drag_highlighted_widget = g_object_ref (child); if (old_highlight) g_object_unref (old_highlight); } static void egg_list_box_real_drag_leave (GtkWidget *widget, GdkDragContext *context, guint time_) { EggListBox *list_box = EGG_LIST_BOX (widget); EggListBoxPrivate *priv = list_box->priv; egg_list_box_drag_unhighlight_widget (list_box); if (priv->auto_scroll_timeout_id != 0) { g_source_remove (priv->auto_scroll_timeout_id); priv->auto_scroll_timeout_id = 0; } } typedef struct { EggListBox *list_box; gint move; } MoveData; static void move_data_free (MoveData *data) { g_slice_free (MoveData, data); } static gboolean drag_motion_timeout (MoveData *data) { EggListBox *list_box = data->list_box; EggListBoxPrivate *priv = list_box->priv; gtk_adjustment_set_value (priv->adjustment, gtk_adjustment_get_value (priv->adjustment) + gtk_adjustment_get_step_increment (priv->adjustment) * data->move); return TRUE; } static gboolean egg_list_box_real_drag_motion (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time_) { EggListBox *list_box = EGG_LIST_BOX (widget); EggListBoxPrivate *priv = list_box->priv; int move; MoveData *data; gdouble size; /* Auto-scroll during Dnd if cursor is moving into the top/bottom portion of the * box. */ if (priv->auto_scroll_timeout_id != 0) { g_source_remove (priv->auto_scroll_timeout_id); priv->auto_scroll_timeout_id = 0; } if (priv->adjustment == NULL) return FALSE; /* Part of the view triggering auto-scroll */ size = 30; move = 0; if (y < (gtk_adjustment_get_value (priv->adjustment) + size)) { /* Scroll up */ move = -1; } else if (y > ((gtk_adjustment_get_value (priv->adjustment) + gtk_adjustment_get_page_size (priv->adjustment)) - size)) { /* Scroll down */ move = 1; } if (move == 0) return FALSE; data = g_slice_new0 (MoveData); data->list_box = list_box; priv->auto_scroll_timeout_id = g_timeout_add_full (G_PRIORITY_DEFAULT, 150, (GSourceFunc)drag_motion_timeout, data, (GDestroyNotify) move_data_free); return FALSE; } static void egg_list_box_real_activate_cursor_child (EggListBox *list_box) { EggListBoxPrivate *priv = list_box->priv; egg_list_box_select_and_activate (list_box, priv->cursor_child); } static void egg_list_box_real_toggle_cursor_child (EggListBox *list_box) { EggListBoxPrivate *priv = list_box->priv; if (priv->cursor_child == NULL) return; if (priv->selection_mode == GTK_SELECTION_SINGLE && priv->selected_child == priv->cursor_child) egg_list_box_update_selected (list_box, NULL); else egg_list_box_select_and_activate (list_box, priv->cursor_child); } static void egg_list_box_real_move_cursor (EggListBox *list_box, GtkMovementStep step, gint count) { EggListBoxPrivate *priv = list_box->priv; GdkModifierType state; gboolean modify_selection_pressed; EggListBoxChildInfo *child; GdkModifierType modify_mod_mask; EggListBoxChildInfo *prev; EggListBoxChildInfo *next; gint page_size; GSequenceIter *iter; gint start_y; gint end_y; modify_selection_pressed = FALSE; if (gtk_get_current_event_state (&state)) { modify_mod_mask = gtk_widget_get_modifier_mask (GTK_WIDGET (list_box), GDK_MODIFIER_INTENT_MODIFY_SELECTION); if ((state & modify_mod_mask) == modify_mod_mask) modify_selection_pressed = TRUE; } child = NULL; switch (step) { case GTK_MOVEMENT_BUFFER_ENDS: if (count < 0) child = egg_list_box_get_first_visible (list_box); else child = egg_list_box_get_last_visible (list_box); break; case GTK_MOVEMENT_DISPLAY_LINES: if (priv->cursor_child != NULL) { iter = priv->cursor_child->iter; while (count < 0 && iter != NULL) { iter = egg_list_box_get_previous_visible (list_box, iter); count = count + 1; } while (count > 0 && iter != NULL) { iter = egg_list_box_get_next_visible (list_box, iter); count = count - 1; } if (iter != NULL && !g_sequence_iter_is_end (iter)) child = g_sequence_get (iter); } break; case GTK_MOVEMENT_PAGES: page_size = 100; if (priv->adjustment != NULL) page_size = gtk_adjustment_get_page_increment (priv->adjustment); if (priv->cursor_child != NULL) { start_y = priv->cursor_child->y; end_y = start_y; iter = priv->cursor_child->iter; child = priv->cursor_child; if (count < 0) { /* Up */ while (iter != NULL && !g_sequence_iter_is_begin (iter)) { iter = egg_list_box_get_previous_visible (list_box, iter); if (iter == NULL) break; prev = g_sequence_get (iter); if (prev->y < start_y - page_size) break; child = prev; } } else { /* Down */ while (iter != NULL && !g_sequence_iter_is_end (iter)) { iter = egg_list_box_get_next_visible (list_box, iter); if (g_sequence_iter_is_end (iter)) break; next = g_sequence_get (iter); if (next->y > start_y + page_size) break; child = next; } } end_y = child->y; if (end_y != start_y && priv->adjustment != NULL) gtk_adjustment_set_value (priv->adjustment, gtk_adjustment_get_value (priv->adjustment) + end_y - start_y); } break; default: return; } if (child == NULL || child == priv->cursor_child) { GtkDirectionType direction = count < 0 ? GTK_DIR_UP : GTK_DIR_DOWN; if (!gtk_widget_keynav_failed (GTK_WIDGET (list_box), direction)) { GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (list_box)); if (toplevel) gtk_widget_child_focus (toplevel, direction == GTK_DIR_UP ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD); } return; } egg_list_box_update_cursor (list_box, child); if (!modify_selection_pressed) egg_list_box_update_selected (list_box, child); }