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 29 void CountVisibleChildren(GtkWidget* child, gpointer userdata) { 30 if (GTK_WIDGET_VISIBLE(child)) 31 ++(*reinterpret_cast<int*>(userdata)); 32 } 33 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 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 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 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 135 static void gtk_chrome_shrinkable_hbox_init(GtkChromeShrinkableHBox* box) { 136 box->hide_child_directly = FALSE; 137 box->children_width_requisition = 0; 138 } 139 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 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 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 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 284