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