OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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 | |
29 void CountVisibleChildren(GtkWidget* child, gpointer userdata) { | |
30 if (gtk_widget_get_visible(child)) | |
31 ++(*reinterpret_cast<int*>(userdata)); | |
32 } | |
33 | |
34 void SumChildrenWidthRequisition(GtkWidget* child, gpointer userdata) { | |
35 if (gtk_widget_get_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 | |
42 void ChildSizeAllocate(GtkWidget* child, gpointer userdata) { | |
43 if (!gtk_widget_get_visible(child)) | |
44 return; | |
45 | |
46 SizeAllocateData* data = reinterpret_cast<SizeAllocateData*>(userdata); | |
47 GtkAllocation child_allocation; | |
48 gtk_widget_get_allocation(child, &child_allocation); | |
49 | |
50 if (data->homogeneous) { | |
51 // Make sure the child is not overlapped with others' boundary. | |
52 if (child_allocation.width > data->homogeneous_child_width) { | |
53 child_allocation.x += | |
54 (child_allocation.width - data->homogeneous_child_width) / 2; | |
55 child_allocation.width = data->homogeneous_child_width; | |
56 } | |
57 } else { | |
58 guint padding; | |
59 GtkPackType pack_type; | |
60 gtk_box_query_child_packing(GTK_BOX(data->box), child, NULL, NULL, | |
61 &padding, &pack_type); | |
62 | |
63 if ((data->direction == GTK_TEXT_DIR_RTL && pack_type == GTK_PACK_START) || | |
64 (data->direction != GTK_TEXT_DIR_RTL && pack_type == GTK_PACK_END)) { | |
65 // All children are right aligned, so make sure the child won't overflow | |
66 // its parent's left edge. | |
67 int overflow = (data->allocation->x + data->border_width + padding - | |
68 child_allocation.x); | |
69 if (overflow > 0) { | |
70 child_allocation.width -= overflow; | |
71 child_allocation.x += overflow; | |
72 } | |
73 } else { | |
74 // All children are left aligned, so make sure the child won't overflow | |
75 // its parent's right edge. | |
76 int overflow = (child_allocation.x + child_allocation.width + padding - | |
77 (data->allocation->x + data->allocation->width - data->border_width)); | |
78 if (overflow > 0) | |
79 child_allocation.width -= overflow; | |
80 } | |
81 } | |
82 | |
83 GtkAllocation current_allocation; | |
84 gtk_widget_get_allocation(child, ¤t_allocation); | |
85 | |
86 if (child_allocation.width != current_allocation.width) { | |
87 if (data->box->hide_child_directly || child_allocation.width <= 1) | |
88 gtk_widget_hide(child); | |
89 else | |
90 gtk_widget_size_allocate(child, &child_allocation); | |
91 } | |
92 } | |
93 | |
94 } // namespace | |
95 | |
96 G_BEGIN_DECLS | |
97 | |
98 static void gtk_chrome_shrinkable_hbox_set_property(GObject* object, | |
99 guint prop_id, | |
100 const GValue* value, | |
101 GParamSpec* pspec); | |
102 static void gtk_chrome_shrinkable_hbox_get_property(GObject* object, | |
103 guint prop_id, | |
104 GValue* value, | |
105 GParamSpec* pspec); | |
106 static void gtk_chrome_shrinkable_hbox_size_allocate(GtkWidget* widget, | |
107 GtkAllocation* allocation); | |
108 | |
109 G_DEFINE_TYPE(GtkChromeShrinkableHBox, gtk_chrome_shrinkable_hbox, | |
110 GTK_TYPE_HBOX) | |
111 | |
112 static void gtk_chrome_shrinkable_hbox_class_init( | |
113 GtkChromeShrinkableHBoxClass *klass) { | |
114 GObjectClass* object_class = G_OBJECT_CLASS(klass); | |
115 GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass); | |
116 | |
117 object_class->set_property = gtk_chrome_shrinkable_hbox_set_property; | |
118 object_class->get_property = gtk_chrome_shrinkable_hbox_get_property; | |
119 | |
120 widget_class->size_allocate = gtk_chrome_shrinkable_hbox_size_allocate; | |
121 | |
122 g_object_class_install_property(object_class, PROP_HIDE_CHILD_DIRECTLY, | |
123 g_param_spec_boolean("hide-child-directly", | |
124 "Hide child directly", | |
125 "Whether the children should be hid directly, " | |
126 "if there is no enough space in its parent", | |
127 FALSE, | |
128 static_cast<GParamFlags>( | |
129 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); | |
130 } | |
131 | |
132 static void gtk_chrome_shrinkable_hbox_init(GtkChromeShrinkableHBox* box) { | |
133 box->hide_child_directly = FALSE; | |
134 box->children_width_requisition = 0; | |
135 } | |
136 | |
137 static void gtk_chrome_shrinkable_hbox_set_property(GObject* object, | |
138 guint prop_id, | |
139 const GValue* value, | |
140 GParamSpec* pspec) { | |
141 GtkChromeShrinkableHBox* box = GTK_CHROME_SHRINKABLE_HBOX(object); | |
142 | |
143 switch (prop_id) { | |
144 case PROP_HIDE_CHILD_DIRECTLY: | |
145 gtk_chrome_shrinkable_hbox_set_hide_child_directly( | |
146 box, g_value_get_boolean(value)); | |
147 break; | |
148 default: | |
149 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); | |
150 break; | |
151 } | |
152 } | |
153 | |
154 static void gtk_chrome_shrinkable_hbox_get_property(GObject* object, | |
155 guint prop_id, | |
156 GValue* value, | |
157 GParamSpec* pspec) { | |
158 GtkChromeShrinkableHBox* box = GTK_CHROME_SHRINKABLE_HBOX(object); | |
159 | |
160 switch (prop_id) { | |
161 case PROP_HIDE_CHILD_DIRECTLY: | |
162 g_value_set_boolean(value, box->hide_child_directly); | |
163 break; | |
164 default: | |
165 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); | |
166 break; | |
167 } | |
168 } | |
169 | |
170 static void gtk_chrome_shrinkable_hbox_size_allocate( | |
171 GtkWidget* widget, GtkAllocation* allocation) { | |
172 GtkChromeShrinkableHBox* box = GTK_CHROME_SHRINKABLE_HBOX(widget); | |
173 gint children_width_requisition = 0; | |
174 gtk_container_foreach(GTK_CONTAINER(widget), SumChildrenWidthRequisition, | |
175 &children_width_requisition); | |
176 | |
177 GtkAllocation widget_allocation; | |
178 gtk_widget_get_allocation(widget, &widget_allocation); | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
OLD | NEW |