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