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