| 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 |