// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/gtk/gtk_chrome_shrinkable_hbox.h" #include #include namespace { enum { PROP_0, PROP_HIDE_CHILD_DIRECTLY }; struct SizeAllocateData { GtkChromeShrinkableHBox* box; GtkAllocation* allocation; GtkTextDirection direction; bool homogeneous; int border_width; // Maximum child width when |homogeneous| is TRUE. int homogeneous_child_width; }; void CountVisibleChildren(GtkWidget* child, gpointer userdata) { if (gtk_widget_get_visible(child)) ++(*reinterpret_cast(userdata)); } void SumChildrenWidthRequisition(GtkWidget* child, gpointer userdata) { if (gtk_widget_get_visible(child)) { GtkRequisition req; gtk_widget_get_child_requisition(child, &req); (*reinterpret_cast(userdata)) += std::max(req.width, 0); } } void ShowInvisibleChildren(GtkWidget* child, gpointer userdata) { if (!gtk_widget_get_visible(child)) { gtk_widget_show(child); ++(*reinterpret_cast(userdata)); } } void ChildSizeAllocate(GtkWidget* child, gpointer userdata) { if (!gtk_widget_get_visible(child)) return; SizeAllocateData* data = reinterpret_cast(userdata); GtkAllocation child_allocation; gtk_widget_get_allocation(child, &child_allocation); if (data->homogeneous) { // Make sure the child is not overlapped with others' boundary. if (child_allocation.width > data->homogeneous_child_width) { child_allocation.x += (child_allocation.width - data->homogeneous_child_width) / 2; child_allocation.width = data->homogeneous_child_width; } } else { guint padding; GtkPackType pack_type; gtk_box_query_child_packing(GTK_BOX(data->box), child, NULL, NULL, &padding, &pack_type); if ((data->direction == GTK_TEXT_DIR_RTL && pack_type == GTK_PACK_START) || (data->direction != GTK_TEXT_DIR_RTL && pack_type == GTK_PACK_END)) { // All children are right aligned, so make sure the child won't overflow // its parent's left edge. int overflow = (data->allocation->x + data->border_width + padding - child_allocation.x); if (overflow > 0) { child_allocation.width -= overflow; child_allocation.x += overflow; } } else { // All children are left aligned, so make sure the child won't overflow // its parent's right edge. int overflow = (child_allocation.x + child_allocation.width + padding - (data->allocation->x + data->allocation->width - data->border_width)); if (overflow > 0) child_allocation.width -= overflow; } } GtkAllocation current_allocation; gtk_widget_get_allocation(child, ¤t_allocation); if (child_allocation.width != current_allocation.width) { if (data->box->hide_child_directly || child_allocation.width <= 1) gtk_widget_hide(child); else gtk_widget_size_allocate(child, &child_allocation); } } } // namespace G_BEGIN_DECLS static void gtk_chrome_shrinkable_hbox_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec); static void gtk_chrome_shrinkable_hbox_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec); static void gtk_chrome_shrinkable_hbox_size_allocate(GtkWidget* widget, GtkAllocation* allocation); G_DEFINE_TYPE(GtkChromeShrinkableHBox, gtk_chrome_shrinkable_hbox, GTK_TYPE_HBOX) static void gtk_chrome_shrinkable_hbox_class_init( GtkChromeShrinkableHBoxClass *klass) { GObjectClass* object_class = G_OBJECT_CLASS(klass); GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass); object_class->set_property = gtk_chrome_shrinkable_hbox_set_property; object_class->get_property = gtk_chrome_shrinkable_hbox_get_property; widget_class->size_allocate = gtk_chrome_shrinkable_hbox_size_allocate; g_object_class_install_property(object_class, PROP_HIDE_CHILD_DIRECTLY, g_param_spec_boolean("hide-child-directly", "Hide child directly", "Whether the children should be hid directly, " "if there is no enough space in its parent", FALSE, static_cast( G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); } static void gtk_chrome_shrinkable_hbox_init(GtkChromeShrinkableHBox* box) { box->hide_child_directly = FALSE; box->children_width_requisition = 0; } static void gtk_chrome_shrinkable_hbox_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec) { GtkChromeShrinkableHBox* box = GTK_CHROME_SHRINKABLE_HBOX(object); switch (prop_id) { case PROP_HIDE_CHILD_DIRECTLY: gtk_chrome_shrinkable_hbox_set_hide_child_directly( box, g_value_get_boolean(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gtk_chrome_shrinkable_hbox_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec) { GtkChromeShrinkableHBox* box = GTK_CHROME_SHRINKABLE_HBOX(object); switch (prop_id) { case PROP_HIDE_CHILD_DIRECTLY: g_value_set_boolean(value, box->hide_child_directly); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gtk_chrome_shrinkable_hbox_size_allocate( GtkWidget* widget, GtkAllocation* allocation) { GtkChromeShrinkableHBox* box = GTK_CHROME_SHRINKABLE_HBOX(widget); gint children_width_requisition = 0; gtk_container_foreach(GTK_CONTAINER(widget), SumChildrenWidthRequisition, &children_width_requisition); GtkAllocation widget_allocation; gtk_widget_get_allocation(widget, &widget_allocation); // If we are allocated to more width or some children are removed or shrunk, // then we need to show all invisible children before calling parent class's // size_allocate method, because the new width may be enough to show those // hidden children. if (widget_allocation.width < allocation->width || box->children_width_requisition > children_width_requisition) { gtk_container_foreach(GTK_CONTAINER(widget), reinterpret_cast(gtk_widget_show), NULL); // If there were any invisible children, showing them will trigger another // allocate. But we still need to go through the size allocate process // in this iteration, otherwise before the next allocate iteration, the // children may be redrawn on the screen with incorrect size allocation. } // Let the parent class do size allocation first. After that all children will // be allocated with reasonable position and size according to their size // request. (GTK_WIDGET_CLASS(gtk_chrome_shrinkable_hbox_parent_class)->size_allocate) (widget, allocation); gint visible_children_count = gtk_chrome_shrinkable_hbox_get_visible_child_count( GTK_CHROME_SHRINKABLE_HBOX(widget)); box->children_width_requisition = 0; if (visible_children_count == 0) return; SizeAllocateData data; data.box = GTK_CHROME_SHRINKABLE_HBOX(widget); data.allocation = allocation; data.direction = gtk_widget_get_direction(widget); data.homogeneous = gtk_box_get_homogeneous(GTK_BOX(widget)); data.border_width = gtk_container_get_border_width(GTK_CONTAINER(widget)); data.homogeneous_child_width = (allocation->width - data.border_width * 2 - (visible_children_count - 1) * gtk_box_get_spacing(GTK_BOX(widget))) / visible_children_count; // Shrink or hide children if necessary. gtk_container_foreach(GTK_CONTAINER(widget), ChildSizeAllocate, &data); // Record current width requisition of visible children, so we can know if // it's necessary to show invisible children next time. gtk_container_foreach(GTK_CONTAINER(widget), SumChildrenWidthRequisition, &box->children_width_requisition); } GtkWidget* gtk_chrome_shrinkable_hbox_new(gboolean hide_child_directly, gboolean homogeneous, gint spacing) { return GTK_WIDGET(g_object_new(GTK_TYPE_CHROME_SHRINKABLE_HBOX, "hide-child-directly", hide_child_directly, "homogeneous", homogeneous, "spacing", spacing, NULL)); } void gtk_chrome_shrinkable_hbox_set_hide_child_directly( GtkChromeShrinkableHBox* box, gboolean hide_child_directly) { g_return_if_fail(GTK_IS_CHROME_SHRINKABLE_HBOX(box)); if (hide_child_directly != box->hide_child_directly) { box->hide_child_directly = hide_child_directly; g_object_notify(G_OBJECT(box), "hide-child-directly"); gtk_widget_queue_resize(GTK_WIDGET(box)); } } gboolean gtk_chrome_shrinkable_hbox_get_hide_child_directly( GtkChromeShrinkableHBox* box) { g_return_val_if_fail(GTK_IS_CHROME_SHRINKABLE_HBOX(box), FALSE); return box->hide_child_directly; } void gtk_chrome_shrinkable_hbox_pack_start(GtkChromeShrinkableHBox* box, GtkWidget* child, guint padding) { g_return_if_fail(GTK_IS_CHROME_SHRINKABLE_HBOX(box)); g_return_if_fail(GTK_IS_WIDGET(child)); gtk_box_pack_start(GTK_BOX(box), child, FALSE, FALSE, 0); } void gtk_chrome_shrinkable_hbox_pack_end(GtkChromeShrinkableHBox* box, GtkWidget* child, guint padding) { g_return_if_fail(GTK_IS_CHROME_SHRINKABLE_HBOX(box)); g_return_if_fail(GTK_IS_WIDGET(child)); gtk_box_pack_end(GTK_BOX(box), child, FALSE, FALSE, 0); } gint gtk_chrome_shrinkable_hbox_get_visible_child_count( GtkChromeShrinkableHBox* box) { gint visible_children_count = 0; gtk_container_foreach(GTK_CONTAINER(box), CountVisibleChildren, &visible_children_count); return visible_children_count; } G_END_DECLS