blob: a4b8da62eb35e1bb9b10cdafd367d3938c35141b [file] [log] [blame]
// 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 <gtk/gtk.h>
#include <algorithm>
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<int*>(userdata));
}
void SumChildrenWidthRequisition(GtkWidget* child, gpointer userdata) {
if (gtk_widget_get_visible(child)) {
GtkRequisition req;
gtk_widget_get_child_requisition(child, &req);
(*reinterpret_cast<int*>(userdata)) += std::max(req.width, 0);
}
}
void ShowInvisibleChildren(GtkWidget* child, gpointer userdata) {
if (!gtk_widget_get_visible(child)) {
gtk_widget_show(child);
++(*reinterpret_cast<int*>(userdata));
}
}
void ChildSizeAllocate(GtkWidget* child, gpointer userdata) {
if (!gtk_widget_get_visible(child))
return;
SizeAllocateData* data = reinterpret_cast<SizeAllocateData*>(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, &current_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<GParamFlags>(
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<GtkCallback>(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