Home | History | Annotate | Download | only in gtk
      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