1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/ui/gtk/gtk_chrome_shrinkable_hbox.h"
6
7#include <gtk/gtk.h>
8
9#include <algorithm>
10
11namespace {
12
13enum {
14  PROP_0,
15  PROP_HIDE_CHILD_DIRECTLY
16};
17
18struct SizeAllocateData {
19  GtkChromeShrinkableHBox* box;
20  GtkAllocation* allocation;
21  GtkTextDirection direction;
22  bool homogeneous;
23  int border_width;
24
25  // Maximum child width when |homogeneous| is TRUE.
26  int homogeneous_child_width;
27};
28
29void CountVisibleChildren(GtkWidget* child, gpointer userdata) {
30  if (GTK_WIDGET_VISIBLE(child))
31    ++(*reinterpret_cast<int*>(userdata));
32}
33
34void SumChildrenWidthRequisition(GtkWidget* child, gpointer userdata) {
35  if (GTK_WIDGET_VISIBLE(child)) {
36    GtkRequisition req;
37    gtk_widget_get_child_requisition(child, &req);
38    (*reinterpret_cast<int*>(userdata)) += std::max(req.width, 0);
39  }
40}
41
42void ShowInvisibleChildren(GtkWidget* child, gpointer userdata) {
43  if (!GTK_WIDGET_VISIBLE(child)) {
44    gtk_widget_show(child);
45    ++(*reinterpret_cast<int*>(userdata));
46  }
47}
48
49void ChildSizeAllocate(GtkWidget* child, gpointer userdata) {
50  if (!GTK_WIDGET_VISIBLE(child))
51    return;
52
53  SizeAllocateData* data = reinterpret_cast<SizeAllocateData*>(userdata);
54  GtkAllocation child_allocation = child->allocation;
55
56  if (data->homogeneous) {
57    // Make sure the child is not overlapped with others' boundary.
58    if (child_allocation.width > data->homogeneous_child_width) {
59      child_allocation.x +=
60          (child_allocation.width - data->homogeneous_child_width) / 2;
61      child_allocation.width = data->homogeneous_child_width;
62    }
63  } else {
64    guint padding;
65    GtkPackType pack_type;
66    gtk_box_query_child_packing(GTK_BOX(data->box), child, NULL, NULL,
67                                &padding, &pack_type);
68
69    if ((data->direction == GTK_TEXT_DIR_RTL && pack_type == GTK_PACK_START) ||
70        (data->direction != GTK_TEXT_DIR_RTL && pack_type == GTK_PACK_END)) {
71      // All children are right aligned, so make sure the child won't overflow
72      // its parent's left edge.
73      int overflow = (data->allocation->x + data->border_width + padding -
74                      child_allocation.x);
75      if (overflow > 0) {
76        child_allocation.width -= overflow;
77        child_allocation.x += overflow;
78      }
79    } else {
80      // All children are left aligned, so make sure the child won't overflow
81      // its parent's right edge.
82      int overflow = (child_allocation.x + child_allocation.width + padding -
83          (data->allocation->x + data->allocation->width - data->border_width));
84      if (overflow > 0)
85        child_allocation.width -= overflow;
86    }
87  }
88
89  if (child_allocation.width != child->allocation.width) {
90    if (data->box->hide_child_directly || child_allocation.width <= 1)
91      gtk_widget_hide(child);
92    else
93      gtk_widget_size_allocate(child, &child_allocation);
94  }
95}
96
97}  // namespace
98
99G_BEGIN_DECLS
100
101static void gtk_chrome_shrinkable_hbox_set_property(GObject* object,
102                                             guint prop_id,
103                                             const GValue* value,
104                                             GParamSpec* pspec);
105static void gtk_chrome_shrinkable_hbox_get_property(GObject* object,
106                                             guint prop_id,
107                                             GValue* value,
108                                             GParamSpec* pspec);
109static void gtk_chrome_shrinkable_hbox_size_allocate(GtkWidget* widget,
110                                              GtkAllocation* allocation);
111
112G_DEFINE_TYPE(GtkChromeShrinkableHBox, gtk_chrome_shrinkable_hbox,
113              GTK_TYPE_HBOX)
114
115static void gtk_chrome_shrinkable_hbox_class_init(
116    GtkChromeShrinkableHBoxClass *klass) {
117  GObjectClass* object_class = G_OBJECT_CLASS(klass);
118  GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
119
120  object_class->set_property = gtk_chrome_shrinkable_hbox_set_property;
121  object_class->get_property = gtk_chrome_shrinkable_hbox_get_property;
122
123  widget_class->size_allocate = gtk_chrome_shrinkable_hbox_size_allocate;
124
125  g_object_class_install_property(object_class, PROP_HIDE_CHILD_DIRECTLY,
126      g_param_spec_boolean("hide-child-directly",
127                           "Hide child directly",
128                           "Whether the children should be hid directly, "
129                           "if there is no enough space in its parent",
130                           FALSE,
131                           static_cast<GParamFlags>(
132                               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
133}
134
135static void gtk_chrome_shrinkable_hbox_init(GtkChromeShrinkableHBox* box) {
136  box->hide_child_directly = FALSE;
137  box->children_width_requisition = 0;
138}
139
140static void gtk_chrome_shrinkable_hbox_set_property(GObject* object,
141                                                    guint prop_id,
142                                                    const GValue* value,
143                                                    GParamSpec* pspec) {
144  GtkChromeShrinkableHBox* box = GTK_CHROME_SHRINKABLE_HBOX(object);
145
146  switch (prop_id) {
147    case PROP_HIDE_CHILD_DIRECTLY:
148      gtk_chrome_shrinkable_hbox_set_hide_child_directly(
149          box, g_value_get_boolean(value));
150      break;
151    default:
152      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
153      break;
154  }
155}
156
157static void gtk_chrome_shrinkable_hbox_get_property(GObject* object,
158                                                    guint prop_id,
159                                                    GValue* value,
160                                                    GParamSpec* pspec) {
161  GtkChromeShrinkableHBox* box = GTK_CHROME_SHRINKABLE_HBOX(object);
162
163  switch (prop_id) {
164    case PROP_HIDE_CHILD_DIRECTLY:
165      g_value_set_boolean(value, box->hide_child_directly);
166      break;
167    default:
168      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
169      break;
170  }
171}
172
173static void gtk_chrome_shrinkable_hbox_size_allocate(
174    GtkWidget* widget, GtkAllocation* allocation) {
175  GtkChromeShrinkableHBox* box = GTK_CHROME_SHRINKABLE_HBOX(widget);
176  gint children_width_requisition = 0;
177  gtk_container_foreach(GTK_CONTAINER(widget), SumChildrenWidthRequisition,
178                        &children_width_requisition);
179
180  // If we are allocated to more width or some children are removed or shrunk,
181  // then we need to show all invisible children before calling parent class's
182  // size_allocate method, because the new width may be enough to show those
183  // hidden children.
184  if (widget->allocation.width < allocation->width ||
185      box->children_width_requisition > children_width_requisition) {
186    gtk_container_foreach(GTK_CONTAINER(widget),
187                          reinterpret_cast<GtkCallback>(gtk_widget_show), NULL);
188
189    // If there were any invisible children, showing them will trigger another
190    // allocate. But we still need to go through the size allocate process
191    // in this iteration, otherwise before the next allocate iteration, the
192    // children may be redrawn on the screen with incorrect size allocation.
193  }
194
195  // Let the parent class do size allocation first. After that all children will
196  // be allocated with reasonable position and size according to their size
197  // request.
198  (GTK_WIDGET_CLASS(gtk_chrome_shrinkable_hbox_parent_class)->size_allocate)
199      (widget, allocation);
200
201  gint visible_children_count =
202      gtk_chrome_shrinkable_hbox_get_visible_child_count(
203          GTK_CHROME_SHRINKABLE_HBOX(widget));
204
205  box->children_width_requisition = 0;
206  if (visible_children_count == 0)
207    return;
208
209  SizeAllocateData data;
210  data.box = GTK_CHROME_SHRINKABLE_HBOX(widget);
211  data.allocation = allocation;
212  data.direction = gtk_widget_get_direction(widget);
213  data.homogeneous = gtk_box_get_homogeneous(GTK_BOX(widget));
214  data.border_width = gtk_container_get_border_width(GTK_CONTAINER(widget));
215  data.homogeneous_child_width =
216      (allocation->width - data.border_width * 2 -
217       (visible_children_count - 1) * gtk_box_get_spacing(GTK_BOX(widget))) /
218      visible_children_count;
219
220  // Shrink or hide children if necessary.
221  gtk_container_foreach(GTK_CONTAINER(widget), ChildSizeAllocate, &data);
222
223  // Record current width requisition of visible children, so we can know if
224  // it's necessary to show invisible children next time.
225  gtk_container_foreach(GTK_CONTAINER(widget), SumChildrenWidthRequisition,
226                        &box->children_width_requisition);
227}
228
229GtkWidget* gtk_chrome_shrinkable_hbox_new(gboolean hide_child_directly,
230                                          gboolean homogeneous,
231                                          gint spacing) {
232  return GTK_WIDGET(g_object_new(GTK_TYPE_CHROME_SHRINKABLE_HBOX,
233                                 "hide-child-directly", hide_child_directly,
234                                 "homogeneous", homogeneous,
235                                 "spacing", spacing,
236                                 NULL));
237}
238
239void gtk_chrome_shrinkable_hbox_set_hide_child_directly(
240    GtkChromeShrinkableHBox* box, gboolean hide_child_directly) {
241  g_return_if_fail(GTK_IS_CHROME_SHRINKABLE_HBOX(box));
242
243  if (hide_child_directly != box->hide_child_directly) {
244    box->hide_child_directly = hide_child_directly;
245    g_object_notify(G_OBJECT(box), "hide-child-directly");
246    gtk_widget_queue_resize(GTK_WIDGET(box));
247  }
248}
249
250gboolean gtk_chrome_shrinkable_hbox_get_hide_child_directly(
251    GtkChromeShrinkableHBox* box) {
252  g_return_val_if_fail(GTK_IS_CHROME_SHRINKABLE_HBOX(box), FALSE);
253
254  return box->hide_child_directly;
255}
256
257void gtk_chrome_shrinkable_hbox_pack_start(GtkChromeShrinkableHBox* box,
258                                           GtkWidget* child,
259                                           guint padding) {
260  g_return_if_fail(GTK_IS_CHROME_SHRINKABLE_HBOX(box));
261  g_return_if_fail(GTK_IS_WIDGET(child));
262
263  gtk_box_pack_start(GTK_BOX(box), child, FALSE, FALSE, 0);
264}
265
266void gtk_chrome_shrinkable_hbox_pack_end(GtkChromeShrinkableHBox* box,
267                                         GtkWidget* child,
268                                         guint padding) {
269  g_return_if_fail(GTK_IS_CHROME_SHRINKABLE_HBOX(box));
270  g_return_if_fail(GTK_IS_WIDGET(child));
271
272  gtk_box_pack_end(GTK_BOX(box), child, FALSE, FALSE, 0);
273}
274
275gint gtk_chrome_shrinkable_hbox_get_visible_child_count(
276    GtkChromeShrinkableHBox* box) {
277  gint visible_children_count = 0;
278  gtk_container_foreach(GTK_CONTAINER(box), CountVisibleChildren,
279                        &visible_children_count);
280  return visible_children_count;
281}
282
283G_END_DECLS
284