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 <vector>
8
9#include "testing/gtest/include/gtest/gtest.h"
10
11namespace {
12
13const int kSpacing = 3;
14const int kBorderWidth = 5;
15
16}  // namespace
17
18class GtkChromeShrinkableHBoxTest : public testing::Test {
19 protected:
20  GtkChromeShrinkableHBoxTest()
21      : window_(gtk_window_new(GTK_WINDOW_TOPLEVEL)),
22        box_(gtk_chrome_shrinkable_hbox_new(FALSE, FALSE, kSpacing)) {
23    gtk_widget_set_direction(box_, GTK_TEXT_DIR_LTR);
24    gtk_window_set_default_size(GTK_WINDOW(window_), 200, 200);
25    gtk_container_add(GTK_CONTAINER(window_), box_);
26    gtk_container_set_border_width(GTK_CONTAINER(box_), kBorderWidth);
27  }
28
29  ~GtkChromeShrinkableHBoxTest() {
30    gtk_widget_destroy(window_);
31  }
32
33  // Add some children widgets with arbitrary width and padding.
34  void AddChildren(bool pack_start) {
35    static struct {
36      int width;
37      int padding;
38    } kChildrenData[] = {
39      { 60, 2 },
40      { 70, 3 },
41      { 80, 5 },
42      { 50, 7 },
43      { 40, 11 },
44      { 60, 0 },
45      { 0, 0 }
46    };
47
48    for (size_t i = 0; kChildrenData[i].width; ++i) {
49      GtkWidget* child = gtk_fixed_new();
50      gtk_widget_set_size_request(child, kChildrenData[i].width, -1);
51      if (pack_start) {
52        gtk_chrome_shrinkable_hbox_pack_start(
53            GTK_CHROME_SHRINKABLE_HBOX(box_), child, kChildrenData[i].padding);
54      } else {
55        gtk_chrome_shrinkable_hbox_pack_end(
56            GTK_CHROME_SHRINKABLE_HBOX(box_), child, kChildrenData[i].padding);
57      }
58    }
59  }
60
61  // Check if all children's size allocation are inside the |box_|'s boundary.
62  void Validate(bool pack_start) {
63    std::vector<ChildData> children_data;
64    gtk_container_foreach(GTK_CONTAINER(box_), CollectChildData,
65                          &children_data);
66
67    size_t children_count = children_data.size();
68    size_t visible_children_count = 0;
69    for (size_t i = 0; i < children_count; ++i) {
70      if (children_data[i].visible)
71        ++visible_children_count;
72    }
73
74    if (visible_children_count == 0)
75      return;
76
77    int border_width = gtk_container_get_border_width(GTK_CONTAINER(box_));
78    int x = box_->allocation.x + border_width;
79    int width = box_->allocation.width - border_width * 2;
80    int spacing = gtk_box_get_spacing(GTK_BOX(box_));
81    bool homogeneous = gtk_box_get_homogeneous(GTK_BOX(box_));
82
83    if (homogeneous) {
84      // If the |box_| is in homogeneous mode, then check if the visible
85      // children are not overlapped with each other.
86      int homogeneous_child_width =
87          (width - (visible_children_count - 1) * spacing) /
88          visible_children_count;
89
90      for (size_t i = 0; i < children_count; ++i) {
91        SCOPED_TRACE(testing::Message() << "Validate homogeneous child " << i
92                     << " visible: " << children_data[i].visible
93                     << " padding: " << children_data[i].padding
94                     << " x: " << children_data[i].x
95                     << " width: " << children_data[i].width);
96
97        if (children_data[i].visible)
98          ASSERT_LE(children_data[i].width, homogeneous_child_width);
99      }
100    } else {
101      // If the |box_| is not in homogeneous mode, then just check if all
102      // visible children are inside the |box_|'s boundary. And for those
103      // hidden children which are out of the boundary, they should only
104      // be hidden one by one from the end of the |box_|.
105      bool last_visible = pack_start;
106      bool visibility_changed = false;
107      for (size_t i = 0; i < children_count; ++i) {
108        SCOPED_TRACE(testing::Message() << "Validate child " << i
109                     << " visible: " << children_data[i].visible
110                     << " padding: " << children_data[i].padding
111                     << " x: " << children_data[i].x
112                     << " width: " << children_data[i].width);
113
114        if (last_visible != children_data[i].visible) {
115          ASSERT_FALSE(visibility_changed);
116          visibility_changed = true;
117          last_visible = children_data[i].visible;
118        }
119        if (children_data[i].visible) {
120          ASSERT_GE(children_data[i].x,
121                    x + children_data[i].padding);
122          ASSERT_LE(children_data[i].x + children_data[i].width,
123                    x + width - children_data[i].padding);
124        }
125      }
126    }
127  }
128
129  void Test(bool pack_start) {
130    gtk_widget_show_all(window_);
131    GtkAllocation allocation = { 0, 0, 0, 200 };
132    gtk_chrome_shrinkable_hbox_set_hide_child_directly(
133        GTK_CHROME_SHRINKABLE_HBOX(box_), FALSE);
134    for (int width = 500; width > kBorderWidth * 2; --width) {
135      SCOPED_TRACE(testing::Message() << "Shrink hide_child_directly = FALSE,"
136                   << " width = " << width);
137
138      allocation.width = width;
139      // Reducing the width may cause some children to be hidden, which will
140      // cause queue resize, so it's necessary to do another size allocation to
141      // emulate the queue resize.
142      gtk_widget_size_allocate(box_, &allocation);
143      gtk_widget_size_allocate(box_, &allocation);
144      ASSERT_NO_FATAL_FAILURE(Validate(pack_start)) << "width = " << width;
145    }
146
147    for (int width = kBorderWidth * 2; width <= 500; ++width) {
148      SCOPED_TRACE(testing::Message() << "Expand hide_child_directly = FALSE,"
149                   << " width = " << width);
150
151      allocation.width = width;
152      // Expanding the width may cause some invisible children to be shown,
153      // which will cause queue resize, so it's necessary to do another size
154      // allocation to emulate the queue resize.
155      gtk_widget_size_allocate(box_, &allocation);
156      gtk_widget_size_allocate(box_, &allocation);
157      ASSERT_NO_FATAL_FAILURE(Validate(pack_start));
158    }
159
160    gtk_chrome_shrinkable_hbox_set_hide_child_directly(
161        GTK_CHROME_SHRINKABLE_HBOX(box_), TRUE);
162    for (int width = 500; width > kBorderWidth * 2; --width) {
163      SCOPED_TRACE(testing::Message() << "Shrink hide_child_directly = TRUE,"
164                   << " width = " << width);
165
166      allocation.width = width;
167      gtk_widget_size_allocate(box_, &allocation);
168      gtk_widget_size_allocate(box_, &allocation);
169      ASSERT_NO_FATAL_FAILURE(Validate(pack_start));
170    }
171
172    for (int width = kBorderWidth * 2; width <= 500; ++width) {
173      SCOPED_TRACE(testing::Message() << "Expand hide_child_directly = TRUE,"
174                   << " width = " << width);
175
176      allocation.width = width;
177      gtk_widget_size_allocate(box_, &allocation);
178      gtk_widget_size_allocate(box_, &allocation);
179      ASSERT_NO_FATAL_FAILURE(Validate(pack_start));
180    }
181  }
182
183 protected:
184  GtkWidget* window_;
185  GtkWidget* box_;
186
187 private:
188  struct ChildData {
189    bool visible;
190    int padding;
191    int x;
192    int width;
193  };
194
195  static void CollectChildData(GtkWidget* child, gpointer userdata) {
196    guint padding;
197    gtk_box_query_child_packing(GTK_BOX(gtk_widget_get_parent(child)), child,
198                                NULL, NULL, &padding, NULL);
199
200    ChildData data;
201    data.visible = GTK_WIDGET_VISIBLE(child);
202    data.padding = padding;
203    data.x = child->allocation.x;
204    data.width = child->allocation.width;
205
206    reinterpret_cast<std::vector<ChildData>*>(userdata)->push_back(data);
207  }
208};
209
210TEST_F(GtkChromeShrinkableHBoxTest, PackStart) {
211  AddChildren(true);
212
213  {
214    SCOPED_TRACE("Test LTR");
215    gtk_widget_set_direction(box_, GTK_TEXT_DIR_LTR);
216    EXPECT_NO_FATAL_FAILURE(Test(true));
217  }
218  {
219    SCOPED_TRACE("Test RTL");
220    gtk_widget_set_direction(box_, GTK_TEXT_DIR_RTL);
221    EXPECT_NO_FATAL_FAILURE(Test(true));
222  }
223}
224
225TEST_F(GtkChromeShrinkableHBoxTest, PackEnd) {
226  AddChildren(false);
227
228  {
229    SCOPED_TRACE("Test LTR");
230    gtk_widget_set_direction(box_, GTK_TEXT_DIR_LTR);
231    EXPECT_NO_FATAL_FAILURE(Test(false));
232  }
233  {
234    SCOPED_TRACE("Test RTL");
235    gtk_widget_set_direction(box_, GTK_TEXT_DIR_RTL);
236    EXPECT_NO_FATAL_FAILURE(Test(false));
237  }
238}
239
240TEST_F(GtkChromeShrinkableHBoxTest, Homogeneous) {
241  AddChildren(true);
242  gtk_box_set_homogeneous(GTK_BOX(box_), true);
243
244  {
245    SCOPED_TRACE("Test LTR");
246    gtk_widget_set_direction(box_, GTK_TEXT_DIR_LTR);
247    EXPECT_NO_FATAL_FAILURE(Test(true));
248  }
249  {
250    SCOPED_TRACE("Test RTL");
251    gtk_widget_set_direction(box_, GTK_TEXT_DIR_RTL);
252    EXPECT_NO_FATAL_FAILURE(Test(true));
253  }
254}
255