• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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