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