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/autocomplete/autocomplete_popup_view_gtk.h"
6
7 #include <gtk/gtk.h>
8
9 #include "base/utf_string_conversions.h"
10 #include "chrome/browser/autocomplete/autocomplete.h"
11 #include "chrome/browser/autocomplete/autocomplete_match.h"
12 #include "chrome/browser/ui/gtk/gtk_util.h"
13 #include "testing/platform_test.h"
14
15 namespace {
16
17 static const float kLargeWidth = 10000;
18
19 const GdkColor kContentTextColor = GDK_COLOR_RGB(0x00, 0x00, 0x00);
20 const GdkColor kDimContentTextColor = GDK_COLOR_RGB(0x80, 0x80, 0x80);
21 const GdkColor kURLTextColor = GDK_COLOR_RGB(0x00, 0x88, 0x00);
22
23 } // namespace
24
25 class AutocompletePopupViewGtkTest : public PlatformTest {
26 public:
AutocompletePopupViewGtkTest()27 AutocompletePopupViewGtkTest() { }
28
SetUp()29 virtual void SetUp() {
30 PlatformTest::SetUp();
31
32 window_ = gtk_window_new(GTK_WINDOW_POPUP);
33 layout_ = gtk_widget_create_pango_layout(window_, NULL);
34 }
35
TearDown()36 virtual void TearDown() {
37 g_object_unref(layout_);
38 gtk_widget_destroy(window_);
39
40 PlatformTest::TearDown();
41 }
42
43 // The google C++ Testing Framework documentation suggests making
44 // accessors in the fixture so that each test doesn't need to be a
45 // friend of the class being tested. This method just proxies the
46 // call through after adding the fixture's layout_.
SetupLayoutForMatch(const string16 & text,const AutocompleteMatch::ACMatchClassifications & classifications,const GdkColor * base_color,const GdkColor * dim_color,const GdkColor * url_color,const std::string & prefix_text)47 void SetupLayoutForMatch(
48 const string16& text,
49 const AutocompleteMatch::ACMatchClassifications& classifications,
50 const GdkColor* base_color,
51 const GdkColor* dim_color,
52 const GdkColor* url_color,
53 const std::string& prefix_text) {
54 AutocompletePopupViewGtk::SetupLayoutForMatch(layout_,
55 text,
56 classifications,
57 base_color,
58 dim_color,
59 url_color,
60 prefix_text);
61 }
62
63 struct RunInfo {
64 PangoAttribute* attr_;
65 guint length_;
RunInfoAutocompletePopupViewGtkTest::RunInfo66 RunInfo() : attr_(NULL), length_(0) { }
67 };
68
RunInfoForAttrType(guint location,guint end_location,PangoAttrType type)69 RunInfo RunInfoForAttrType(guint location,
70 guint end_location,
71 PangoAttrType type) {
72 RunInfo retval;
73
74 PangoAttrList* attrs = pango_layout_get_attributes(layout_);
75 if (!attrs)
76 return retval;
77
78 PangoAttrIterator* attr_iter = pango_attr_list_get_iterator(attrs);
79 if (!attr_iter)
80 return retval;
81
82 for (gboolean more = true, findNextStart = false;
83 more;
84 more = pango_attr_iterator_next(attr_iter)) {
85 PangoAttribute* attr = pango_attr_iterator_get(attr_iter, type);
86
87 // This iterator segment doesn't have any elements of the
88 // desired type; keep looking.
89 if (!attr)
90 continue;
91
92 // Skip attribute ranges before the desired start point.
93 if (attr->end_index <= location)
94 continue;
95
96 // If the matching type went past the iterator segment, then set
97 // the length to the next start - location.
98 if (findNextStart) {
99 // If the start is still less than the location, then reset
100 // the match. Otherwise, check that the new attribute is, in
101 // fact different before shortening the run length.
102 if (attr->start_index <= location) {
103 findNextStart = false;
104 } else if (!pango_attribute_equal(retval.attr_, attr)) {
105 retval.length_ = attr->start_index - location;
106 break;
107 }
108 }
109
110 gint start_range, end_range;
111 pango_attr_iterator_range(attr_iter,
112 &start_range,
113 &end_range);
114
115 // Now we have a match. May need to keep going to shorten
116 // length if we reach a new item of the same type.
117 retval.attr_ = attr;
118 if (attr->end_index > (guint)end_range) {
119 retval.length_ = end_location - location;
120 findNextStart = true;
121 } else {
122 retval.length_ = attr->end_index - location;
123 break;
124 }
125 }
126
127 pango_attr_iterator_destroy(attr_iter);
128 return retval;
129 }
130
RunLengthForAttrType(guint location,guint end_location,PangoAttrType type)131 guint RunLengthForAttrType(guint location,
132 guint end_location,
133 PangoAttrType type) {
134 RunInfo info = RunInfoForAttrType(location,
135 end_location,
136 type);
137 return info.length_;
138 }
139
RunHasAttribute(guint location,guint end_location,PangoAttribute * attribute)140 gboolean RunHasAttribute(guint location,
141 guint end_location,
142 PangoAttribute* attribute) {
143 RunInfo info = RunInfoForAttrType(location,
144 end_location,
145 attribute->klass->type);
146
147 return info.attr_ && pango_attribute_equal(info.attr_, attribute);
148 }
149
RunHasColor(guint location,guint end_location,const GdkColor & color)150 gboolean RunHasColor(guint location,
151 guint end_location,
152 const GdkColor& color) {
153 PangoAttribute* attribute =
154 pango_attr_foreground_new(color.red,
155 color.green,
156 color.blue);
157
158 gboolean retval = RunHasAttribute(location,
159 end_location,
160 attribute);
161
162 pango_attribute_destroy(attribute);
163
164 return retval;
165 }
166
RunHasWeight(guint location,guint end_location,PangoWeight weight)167 gboolean RunHasWeight(guint location,
168 guint end_location,
169 PangoWeight weight) {
170 PangoAttribute* attribute = pango_attr_weight_new(weight);
171
172 gboolean retval = RunHasAttribute(location,
173 end_location,
174 attribute);
175
176 pango_attribute_destroy(attribute);
177
178 return retval;
179 }
180
181 GtkWidget* window_;
182 PangoLayout* layout_;
183
184 private:
185 DISALLOW_COPY_AND_ASSIGN(AutocompletePopupViewGtkTest);
186 };
187
188 // Simple inputs with no matches should result in styled output who's
189 // text matches the input string, with the passed-in color, and
190 // nothing bolded.
TEST_F(AutocompletePopupViewGtkTest,DecorateMatchedStringNoMatch)191 TEST_F(AutocompletePopupViewGtkTest, DecorateMatchedStringNoMatch) {
192 const string16 kContents = ASCIIToUTF16("This is a test");
193
194 AutocompleteMatch::ACMatchClassifications classifications;
195
196 SetupLayoutForMatch(kContents,
197 classifications,
198 &kContentTextColor,
199 &kDimContentTextColor,
200 &kURLTextColor,
201 std::string());
202
203 EXPECT_EQ(kContents.size(),
204 RunLengthForAttrType(0U,
205 kContents.size(),
206 PANGO_ATTR_FOREGROUND));
207
208 EXPECT_TRUE(RunHasColor(0U,
209 kContents.size(),
210 kContentTextColor));
211
212 // This part's a little wacky - either we don't have a weight, or
213 // the weight run is the entire string and is NORMAL
214 guint weightLength = RunLengthForAttrType(0U,
215 kContents.size(),
216 PANGO_ATTR_WEIGHT);
217 if (weightLength) {
218 EXPECT_EQ(kContents.size(), weightLength);
219 EXPECT_TRUE(RunHasWeight(0U,
220 kContents.size(),
221 PANGO_WEIGHT_NORMAL));
222 }
223 }
224
225 // Identical to DecorateMatchedStringNoMatch, except test that URL
226 // style gets a different color than we passed in.
TEST_F(AutocompletePopupViewGtkTest,DecorateMatchedStringURLNoMatch)227 TEST_F(AutocompletePopupViewGtkTest, DecorateMatchedStringURLNoMatch) {
228 const string16 kContents = ASCIIToUTF16("This is a test");
229 AutocompleteMatch::ACMatchClassifications classifications;
230
231 classifications.push_back(
232 ACMatchClassification(0U, ACMatchClassification::URL));
233
234 SetupLayoutForMatch(kContents,
235 classifications,
236 &kContentTextColor,
237 &kDimContentTextColor,
238 &kURLTextColor,
239 std::string());
240
241 EXPECT_EQ(kContents.size(),
242 RunLengthForAttrType(0U,
243 kContents.size(),
244 PANGO_ATTR_FOREGROUND));
245 EXPECT_TRUE(RunHasColor(0U,
246 kContents.size(),
247 kURLTextColor));
248
249 // This part's a little wacky - either we don't have a weight, or
250 // the weight run is the entire string and is NORMAL
251 guint weightLength = RunLengthForAttrType(0U,
252 kContents.size(),
253 PANGO_ATTR_WEIGHT);
254 if (weightLength) {
255 EXPECT_EQ(kContents.size(), weightLength);
256 EXPECT_TRUE(RunHasWeight(0U,
257 kContents.size(),
258 PANGO_WEIGHT_NORMAL));
259 }
260 }
261
262 // Test that DIM works as expected.
TEST_F(AutocompletePopupViewGtkTest,DecorateMatchedStringDimNoMatch)263 TEST_F(AutocompletePopupViewGtkTest, DecorateMatchedStringDimNoMatch) {
264 const string16 kContents = ASCIIToUTF16("This is a test");
265 // Dim "is".
266 const guint runLength1 = 5, runLength2 = 2, runLength3 = 7;
267 // Make sure nobody messed up the inputs.
268 EXPECT_EQ(runLength1 + runLength2 + runLength3, kContents.size());
269
270 // Push each run onto classifications.
271 AutocompleteMatch::ACMatchClassifications classifications;
272 classifications.push_back(
273 ACMatchClassification(0U, ACMatchClassification::NONE));
274 classifications.push_back(
275 ACMatchClassification(runLength1, ACMatchClassification::DIM));
276 classifications.push_back(
277 ACMatchClassification(runLength1 + runLength2,
278 ACMatchClassification::NONE));
279
280 SetupLayoutForMatch(kContents,
281 classifications,
282 &kContentTextColor,
283 &kDimContentTextColor,
284 &kURLTextColor,
285 std::string());
286
287 // Check the runs have expected color and length.
288 EXPECT_EQ(runLength1,
289 RunLengthForAttrType(0U,
290 kContents.size(),
291 PANGO_ATTR_FOREGROUND));
292 EXPECT_TRUE(RunHasColor(0U,
293 kContents.size(),
294 kContentTextColor));
295 EXPECT_EQ(runLength2,
296 RunLengthForAttrType(runLength1,
297 kContents.size(),
298 PANGO_ATTR_FOREGROUND));
299 EXPECT_TRUE(RunHasColor(runLength1,
300 kContents.size(),
301 kDimContentTextColor));
302 EXPECT_EQ(runLength3,
303 RunLengthForAttrType(runLength1 + runLength2,
304 kContents.size(),
305 PANGO_ATTR_FOREGROUND));
306 EXPECT_TRUE(RunHasColor(runLength1 + runLength2,
307 kContents.size(),
308 kContentTextColor));
309
310 // This part's a little wacky - either we don't have a weight, or
311 // the weight run is the entire string and is NORMAL
312 guint weightLength = RunLengthForAttrType(0U,
313 kContents.size(),
314 PANGO_ATTR_WEIGHT);
315 if (weightLength) {
316 EXPECT_EQ(kContents.size(), weightLength);
317 EXPECT_TRUE(RunHasWeight(0U,
318 kContents.size(),
319 PANGO_WEIGHT_NORMAL));
320 }
321 }
322
323 // Test that the matched run gets bold-faced, but keeps the same
324 // color.
TEST_F(AutocompletePopupViewGtkTest,DecorateMatchedStringMatch)325 TEST_F(AutocompletePopupViewGtkTest, DecorateMatchedStringMatch) {
326 const string16 kContents = ASCIIToUTF16("This is a test");
327 // Match "is".
328 const guint runLength1 = 5, runLength2 = 2, runLength3 = 7;
329 // Make sure nobody messed up the inputs.
330 EXPECT_EQ(runLength1 + runLength2 + runLength3, kContents.size());
331
332 // Push each run onto classifications.
333 AutocompleteMatch::ACMatchClassifications classifications;
334 classifications.push_back(
335 ACMatchClassification(0U, ACMatchClassification::NONE));
336 classifications.push_back(
337 ACMatchClassification(runLength1, ACMatchClassification::MATCH));
338 classifications.push_back(
339 ACMatchClassification(runLength1 + runLength2,
340 ACMatchClassification::NONE));
341
342 SetupLayoutForMatch(kContents,
343 classifications,
344 &kContentTextColor,
345 &kDimContentTextColor,
346 &kURLTextColor,
347 std::string());
348
349 // Check the runs have expected weight and length.
350 EXPECT_EQ(runLength1,
351 RunLengthForAttrType(0U,
352 kContents.size(),
353 PANGO_ATTR_WEIGHT));
354 EXPECT_TRUE(RunHasWeight(0U,
355 kContents.size(),
356 PANGO_WEIGHT_NORMAL));
357 EXPECT_EQ(runLength2,
358 RunLengthForAttrType(runLength1,
359 kContents.size(),
360 PANGO_ATTR_WEIGHT));
361 EXPECT_TRUE(RunHasWeight(runLength1,
362 kContents.size(),
363 PANGO_WEIGHT_BOLD));
364 EXPECT_EQ(runLength3,
365 RunLengthForAttrType(runLength1 + runLength2,
366 kContents.size(),
367 PANGO_ATTR_WEIGHT));
368 EXPECT_TRUE(RunHasWeight(runLength1 + runLength2,
369 kContents.size(),
370 PANGO_WEIGHT_NORMAL));
371
372 // The entire string should be the same, normal color.
373 EXPECT_EQ(kContents.size(),
374 RunLengthForAttrType(0U,
375 kContents.size(),
376 PANGO_ATTR_FOREGROUND));
377 EXPECT_TRUE(RunHasColor(0U,
378 kContents.size(),
379 kContentTextColor));
380 }
381
382 // Just like DecorateMatchedStringURLMatch, this time with URL style.
TEST_F(AutocompletePopupViewGtkTest,DecorateMatchedStringURLMatch)383 TEST_F(AutocompletePopupViewGtkTest, DecorateMatchedStringURLMatch) {
384 const string16 kContents = ASCIIToUTF16("http://hello.world/");
385 // Match "hello".
386 const guint runLength1 = 7, runLength2 = 5, runLength3 = 7;
387 // Make sure nobody messed up the inputs.
388 EXPECT_EQ(runLength1 + runLength2 + runLength3, kContents.size());
389
390 // Push each run onto classifications.
391 AutocompleteMatch::ACMatchClassifications classifications;
392 classifications.push_back(
393 ACMatchClassification(0U, ACMatchClassification::URL));
394 const int kURLMatch =
395 ACMatchClassification::URL | ACMatchClassification::MATCH;
396 classifications.push_back(
397 ACMatchClassification(runLength1,
398 kURLMatch));
399 classifications.push_back(
400 ACMatchClassification(runLength1 + runLength2,
401 ACMatchClassification::URL));
402
403 SetupLayoutForMatch(kContents,
404 classifications,
405 &kContentTextColor,
406 &kDimContentTextColor,
407 &kURLTextColor,
408 std::string());
409
410 // One color for the entire string, and it's not the one we passed
411 // in.
412 EXPECT_EQ(kContents.size(),
413 RunLengthForAttrType(0U,
414 kContents.size(),
415 PANGO_ATTR_FOREGROUND));
416 EXPECT_TRUE(RunHasColor(0U,
417 kContents.size(),
418 kURLTextColor));
419 }
420