• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
11 namespace {
12 
13 enum {
14   PROP_0,
15   PROP_HIDE_CHILD_DIRECTLY
16 };
17 
18 struct 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 
CountVisibleChildren(GtkWidget * child,gpointer userdata)29 void CountVisibleChildren(GtkWidget* child, gpointer userdata) {
30   if (GTK_WIDGET_VISIBLE(child))
31     ++(*reinterpret_cast<int*>(userdata));
32 }
33 
SumChildrenWidthRequisition(GtkWidget * child,gpointer userdata)34 void 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 
ShowInvisibleChildren(GtkWidget * child,gpointer userdata)42 void ShowInvisibleChildren(GtkWidget* child, gpointer userdata) {
43   if (!GTK_WIDGET_VISIBLE(child)) {
44     gtk_widget_show(child);
45     ++(*reinterpret_cast<int*>(userdata));
46   }
47 }
48 
ChildSizeAllocate(GtkWidget * child,gpointer userdata)49 void 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 
99 G_BEGIN_DECLS
100 
101 static void gtk_chrome_shrinkable_hbox_set_property(GObject* object,
102                                              guint prop_id,
103                                              const GValue* value,
104                                              GParamSpec* pspec);
105 static void gtk_chrome_shrinkable_hbox_get_property(GObject* object,
106                                              guint prop_id,
107                                              GValue* value,
108                                              GParamSpec* pspec);
109 static void gtk_chrome_shrinkable_hbox_size_allocate(GtkWidget* widget,
110                                               GtkAllocation* allocation);
111 
G_DEFINE_TYPE(GtkChromeShrinkableHBox,gtk_chrome_shrinkable_hbox,GTK_TYPE_HBOX)112 G_DEFINE_TYPE(GtkChromeShrinkableHBox, gtk_chrome_shrinkable_hbox,
113               GTK_TYPE_HBOX)
114 
115 static 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 
gtk_chrome_shrinkable_hbox_init(GtkChromeShrinkableHBox * box)135 static void gtk_chrome_shrinkable_hbox_init(GtkChromeShrinkableHBox* box) {
136   box->hide_child_directly = FALSE;
137   box->children_width_requisition = 0;
138 }
139 
gtk_chrome_shrinkable_hbox_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)140 static 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 
gtk_chrome_shrinkable_hbox_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)157 static 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 
gtk_chrome_shrinkable_hbox_size_allocate(GtkWidget * widget,GtkAllocation * allocation)173 static 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 
gtk_chrome_shrinkable_hbox_new(gboolean hide_child_directly,gboolean homogeneous,gint spacing)229 GtkWidget* 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 
gtk_chrome_shrinkable_hbox_set_hide_child_directly(GtkChromeShrinkableHBox * box,gboolean hide_child_directly)239 void 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 
gtk_chrome_shrinkable_hbox_get_hide_child_directly(GtkChromeShrinkableHBox * box)250 gboolean 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 
gtk_chrome_shrinkable_hbox_pack_start(GtkChromeShrinkableHBox * box,GtkWidget * child,guint padding)257 void 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 
gtk_chrome_shrinkable_hbox_pack_end(GtkChromeShrinkableHBox * box,GtkWidget * child,guint padding)266 void 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 
gtk_chrome_shrinkable_hbox_get_visible_child_count(GtkChromeShrinkableHBox * box)275 gint 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 
283 G_END_DECLS
284