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 <algorithm> 10 #include <string> 11 12 #include "base/basictypes.h" 13 #include "base/i18n/rtl.h" 14 #include "base/logging.h" 15 #include "base/stl_util-inl.h" 16 #include "base/utf_string_conversions.h" 17 #include "chrome/browser/autocomplete/autocomplete.h" 18 #include "chrome/browser/autocomplete/autocomplete_edit.h" 19 #include "chrome/browser/autocomplete/autocomplete_edit_view_gtk.h" 20 #include "chrome/browser/autocomplete/autocomplete_match.h" 21 #include "chrome/browser/autocomplete/autocomplete_popup_model.h" 22 #include "chrome/browser/defaults.h" 23 #include "chrome/browser/profiles/profile.h" 24 #include "chrome/browser/search_engines/template_url.h" 25 #include "chrome/browser/search_engines/template_url_model.h" 26 #include "chrome/browser/ui/gtk/gtk_theme_service.h" 27 #include "chrome/browser/ui/gtk/gtk_util.h" 28 #include "content/common/notification_service.h" 29 #include "grit/theme_resources.h" 30 #include "ui/base/gtk/gtk_windowing.h" 31 #include "ui/gfx/color_utils.h" 32 #include "ui/gfx/font.h" 33 #include "ui/gfx/gtk_util.h" 34 #include "ui/gfx/rect.h" 35 #include "ui/gfx/skia_utils_gtk.h" 36 37 namespace { 38 39 const GdkColor kBorderColor = GDK_COLOR_RGB(0xc7, 0xca, 0xce); 40 const GdkColor kBackgroundColor = GDK_COLOR_RGB(0xff, 0xff, 0xff); 41 const GdkColor kSelectedBackgroundColor = GDK_COLOR_RGB(0xdf, 0xe6, 0xf6); 42 const GdkColor kHoveredBackgroundColor = GDK_COLOR_RGB(0xef, 0xf2, 0xfa); 43 44 const GdkColor kContentTextColor = GDK_COLOR_RGB(0x00, 0x00, 0x00); 45 const GdkColor kURLTextColor = GDK_COLOR_RGB(0x00, 0x88, 0x00); 46 47 // We have a 1 pixel border around the entire results popup. 48 const int kBorderThickness = 1; 49 50 // The vertical height of each result. 51 const int kHeightPerResult = 24; 52 53 // Width of the icons. 54 const int kIconWidth = 17; 55 56 // We want to vertically center the image in the result space. 57 const int kIconTopPadding = 2; 58 59 // Space between the left edge (including the border) and the text. 60 const int kIconLeftPadding = 3 + kBorderThickness; 61 62 // Space between the image and the text. 63 const int kIconRightPadding = 5; 64 65 // Space between the left edge (including the border) and the text. 66 const int kIconAreaWidth = 67 kIconLeftPadding + kIconWidth + kIconRightPadding; 68 69 // Space between the right edge (including the border) and the text. 70 const int kRightPadding = 3; 71 72 // When we have both a content and description string, we don't want the 73 // content to push the description off. Limit the content to a percentage of 74 // the total width. 75 const float kContentWidthPercentage = 0.7; 76 77 // How much to offset the popup from the bottom of the location bar. 78 const int kVerticalOffset = 3; 79 80 // The size delta between the font used for the edit and the result rows. Passed 81 // to gfx::Font::DeriveFont. 82 const int kEditFontAdjust = -1; 83 84 // UTF-8 Left-to-right embedding. 85 const char* kLRE = "\xe2\x80\xaa"; 86 87 // Return a Rect covering the whole area of |window|. 88 gfx::Rect GetWindowRect(GdkWindow* window) { 89 gint width, height; 90 gdk_drawable_get_size(GDK_DRAWABLE(window), &width, &height); 91 return gfx::Rect(width, height); 92 } 93 94 // Return a Rect for the space for a result line. This excludes the border, 95 // but includes the padding. This is the area that is colored for a selection. 96 gfx::Rect GetRectForLine(size_t line, int width) { 97 return gfx::Rect(kBorderThickness, 98 (line * kHeightPerResult) + kBorderThickness, 99 width - (kBorderThickness * 2), 100 kHeightPerResult); 101 } 102 103 // Helper for drawing an entire pixbuf without dithering. 104 void DrawFullPixbuf(GdkDrawable* drawable, GdkGC* gc, GdkPixbuf* pixbuf, 105 gint dest_x, gint dest_y) { 106 gdk_draw_pixbuf(drawable, gc, pixbuf, 107 0, 0, // Source. 108 dest_x, dest_y, // Dest. 109 -1, -1, // Width/height (auto). 110 GDK_RGB_DITHER_NONE, 0, 0); // Don't dither. 111 } 112 113 // TODO(deanm): Find some better home for this, and make it more efficient. 114 size_t GetUTF8Offset(const string16& text, size_t text_offset) { 115 return UTF16ToUTF8(text.substr(0, text_offset)).size(); 116 } 117 118 // Generates the normal URL color, a green color used in unhighlighted URL 119 // text. It is a mix of |kURLTextColor| and the current text color. Unlike the 120 // selected text color, it is more important to match the qualities of the 121 // foreground typeface color instead of taking the background into account. 122 GdkColor NormalURLColor(GdkColor foreground) { 123 color_utils::HSL fg_hsl; 124 color_utils::SkColorToHSL(gfx::GdkColorToSkColor(foreground), &fg_hsl); 125 126 color_utils::HSL hue_hsl; 127 color_utils::SkColorToHSL(gfx::GdkColorToSkColor(kURLTextColor), &hue_hsl); 128 129 // Only allow colors that have a fair amount of saturation in them (color vs 130 // white). This means that our output color will always be fairly green. 131 double s = std::max(0.5, fg_hsl.s); 132 133 // Make sure the luminance is at least as bright as the |kURLTextColor| green 134 // would be if we were to use that. 135 double l; 136 if (fg_hsl.l < hue_hsl.l) 137 l = hue_hsl.l; 138 else 139 l = (fg_hsl.l + hue_hsl.l) / 2; 140 141 color_utils::HSL output = { hue_hsl.h, s, l }; 142 return gfx::SkColorToGdkColor(color_utils::HSLToSkColor(output, 255)); 143 } 144 145 // Generates the selected URL color, a green color used on URL text in the 146 // currently highlighted entry in the autocomplete popup. It's a mix of 147 // |kURLTextColor|, the current text color, and the background color (the 148 // select highlight). It is more important to contrast with the background 149 // saturation than to look exactly like the foreground color. 150 GdkColor SelectedURLColor(GdkColor foreground, GdkColor background) { 151 color_utils::HSL fg_hsl; 152 color_utils::SkColorToHSL(gfx::GdkColorToSkColor(foreground), &fg_hsl); 153 154 color_utils::HSL bg_hsl; 155 color_utils::SkColorToHSL(gfx::GdkColorToSkColor(background), &bg_hsl); 156 157 color_utils::HSL hue_hsl; 158 color_utils::SkColorToHSL(gfx::GdkColorToSkColor(kURLTextColor), &hue_hsl); 159 160 // The saturation of the text should be opposite of the background, clamped 161 // to 0.2-0.8. We make sure it's greater than 0.2 so there's some color, but 162 // less than 0.8 so it's not the oversaturated neon-color. 163 double opposite_s = 1 - bg_hsl.s; 164 double s = std::max(0.2, std::min(0.8, opposite_s)); 165 166 // The luminance should match the luminance of the foreground text. Again, 167 // we clamp so as to have at some amount of color (green) in the text. 168 double opposite_l = fg_hsl.l; 169 double l = std::max(0.1, std::min(0.9, opposite_l)); 170 171 color_utils::HSL output = { hue_hsl.h, s, l }; 172 return gfx::SkColorToGdkColor(color_utils::HSLToSkColor(output, 255)); 173 } 174 } // namespace 175 176 void AutocompletePopupViewGtk::SetupLayoutForMatch( 177 PangoLayout* layout, 178 const string16& text, 179 const AutocompleteMatch::ACMatchClassifications& classifications, 180 const GdkColor* base_color, 181 const GdkColor* dim_color, 182 const GdkColor* url_color, 183 const std::string& prefix_text) { 184 // In RTL, mark text with left-to-right embedding mark if there is no strong 185 // RTL characters inside it, so the ending punctuation displays correctly 186 // and the eliding ellipsis displays correctly. We only mark the text with 187 // LRE. Wrapping it with LRE and PDF by calling AdjustStringForLocaleDirection 188 // or WrapStringWithLTRFormatting will render the elllipsis at the left of the 189 // elided pure LTR text. 190 bool marked_with_lre = false; 191 string16 localized_text = text; 192 // Pango is really easy to overflow and send into a computational death 193 // spiral that can corrupt the screen. Assume that we'll never have more than 194 // 2000 characters, which should be a safe assumption until we all get robot 195 // eyes. http://crbug.com/66576 196 if (localized_text.size() > 2000) 197 localized_text = localized_text.substr(0, 2000); 198 bool is_rtl = base::i18n::IsRTL(); 199 if (is_rtl && !base::i18n::StringContainsStrongRTLChars(localized_text)) { 200 localized_text.insert(0, 1, base::i18n::kLeftToRightEmbeddingMark); 201 marked_with_lre = true; 202 } 203 204 // We can have a prefix, or insert additional characters while processing the 205 // classifications. We need to take this in to account when we translate the 206 // UTF-16 offsets in the classification into text_utf8 byte offsets. 207 size_t additional_offset = prefix_text.size(); // Length in utf-8 bytes. 208 std::string text_utf8 = prefix_text + UTF16ToUTF8(localized_text); 209 210 PangoAttrList* attrs = pango_attr_list_new(); 211 212 // TODO(deanm): This is a hack, just to handle coloring prefix_text. 213 // Hopefully I can clean up the match situation a bit and this will 214 // come out cleaner. For now, apply the base color to the whole text 215 // so that our prefix will have the base color applied. 216 PangoAttribute* base_fg_attr = pango_attr_foreground_new( 217 base_color->red, base_color->green, base_color->blue); 218 pango_attr_list_insert(attrs, base_fg_attr); // Ownership taken. 219 220 // Walk through the classifications, they are linear, in order, and should 221 // cover the entire text. We create a bunch of overlapping attributes, 222 // extending from the offset to the end of the string. The ones created 223 // later will override the previous ones, meaning we will still setup each 224 // portion correctly, we just don't need to compute the end offset. 225 for (ACMatchClassifications::const_iterator i = classifications.begin(); 226 i != classifications.end(); ++i) { 227 size_t offset = GetUTF8Offset(localized_text, i->offset) + 228 additional_offset; 229 230 // TODO(deanm): All the colors should probably blend based on whether this 231 // result is selected or not. This would include the green URLs. Right 232 // now the caller is left to blend only the base color. Do we need to 233 // handle things like DIM urls? Turns out DIM means something different 234 // than you'd think, all of the description text is not DIM, it is a 235 // special case that is not very common, but we should figure out and 236 // support it. 237 const GdkColor* color = base_color; 238 if (i->style & ACMatchClassification::URL) { 239 color = url_color; 240 // Insert a left to right embedding to make sure that URLs are shown LTR. 241 if (is_rtl && !marked_with_lre) { 242 std::string lre(kLRE); 243 text_utf8.insert(offset, lre); 244 additional_offset += lre.size(); 245 } 246 } 247 248 if (i->style & ACMatchClassification::DIM) 249 color = dim_color; 250 251 PangoAttribute* fg_attr = pango_attr_foreground_new( 252 color->red, color->green, color->blue); 253 fg_attr->start_index = offset; 254 pango_attr_list_insert(attrs, fg_attr); // Ownership taken. 255 256 // Matched portions are bold, otherwise use the normal weight. 257 PangoWeight weight = (i->style & ACMatchClassification::MATCH) ? 258 PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL; 259 PangoAttribute* weight_attr = pango_attr_weight_new(weight); 260 weight_attr->start_index = offset; 261 pango_attr_list_insert(attrs, weight_attr); // Ownership taken. 262 } 263 264 pango_layout_set_text(layout, text_utf8.data(), text_utf8.size()); 265 pango_layout_set_attributes(layout, attrs); // Ref taken. 266 pango_attr_list_unref(attrs); 267 } 268 269 AutocompletePopupViewGtk::AutocompletePopupViewGtk( 270 const gfx::Font& font, 271 AutocompleteEditView* edit_view, 272 AutocompleteEditModel* edit_model, 273 Profile* profile, 274 GtkWidget* location_bar) 275 : model_(new AutocompletePopupModel(this, edit_model, profile)), 276 edit_view_(edit_view), 277 location_bar_(location_bar), 278 window_(gtk_window_new(GTK_WINDOW_POPUP)), 279 layout_(NULL), 280 theme_service_(GtkThemeService::GetFrom(profile)), 281 font_(font.DeriveFont(kEditFontAdjust)), 282 ignore_mouse_drag_(false), 283 opened_(false) { 284 GTK_WIDGET_UNSET_FLAGS(window_, GTK_CAN_FOCUS); 285 // Don't allow the window to be resized. This also forces the window to 286 // shrink down to the size of its child contents. 287 gtk_window_set_resizable(GTK_WINDOW(window_), FALSE); 288 gtk_widget_set_app_paintable(window_, TRUE); 289 // Have GTK double buffer around the expose signal. 290 gtk_widget_set_double_buffered(window_, TRUE); 291 292 // Cache the layout so we don't have to create it for every expose. If we 293 // were a real widget we should handle changing directions, but we're not 294 // doing RTL or anything yet, so it shouldn't be important now. 295 layout_ = gtk_widget_create_pango_layout(window_, NULL); 296 // We don't want the layout of search results depending on their language. 297 pango_layout_set_auto_dir(layout_, FALSE); 298 // We always ellipsize when drawing our text runs. 299 pango_layout_set_ellipsize(layout_, PANGO_ELLIPSIZE_END); 300 301 gtk_widget_add_events(window_, GDK_BUTTON_MOTION_MASK | 302 GDK_POINTER_MOTION_MASK | 303 GDK_BUTTON_PRESS_MASK | 304 GDK_BUTTON_RELEASE_MASK); 305 g_signal_connect(window_, "motion-notify-event", 306 G_CALLBACK(&HandleMotionThunk), this); 307 g_signal_connect(window_, "button-press-event", 308 G_CALLBACK(&HandleButtonPressThunk), this); 309 g_signal_connect(window_, "button-release-event", 310 G_CALLBACK(&HandleButtonReleaseThunk), this); 311 g_signal_connect(window_, "expose-event", 312 G_CALLBACK(&HandleExposeThunk), this); 313 314 registrar_.Add(this, 315 NotificationType::BROWSER_THEME_CHANGED, 316 NotificationService::AllSources()); 317 theme_service_->InitThemesFor(this); 318 319 // TODO(erg): There appears to be a bug somewhere in something which shows 320 // itself when we're in NX. Previously, we called 321 // gtk_util::ActAsRoundedWindow() to make this popup have rounded 322 // corners. This worked on the standard xorg server (both locally and 323 // remotely), but broke over NX. My current hypothesis is that it can't 324 // handle shaping top-level windows during an expose event, but I'm not sure 325 // how else to get accurate shaping information. 326 // 327 // r25080 (the original patch that added rounded corners here) should 328 // eventually be cherry picked once I know what's going 329 // on. http://crbug.com/22015. 330 } 331 332 AutocompletePopupViewGtk::~AutocompletePopupViewGtk() { 333 // Explicitly destroy our model here, before we destroy our GTK widgets. 334 // This is because the model destructor can call back into us, and we need 335 // to make sure everything is still valid when it does. 336 model_.reset(); 337 g_object_unref(layout_); 338 gtk_widget_destroy(window_); 339 340 for (PixbufMap::iterator it = pixbufs_.begin(); it != pixbufs_.end(); ++it) 341 g_object_unref(it->second); 342 } 343 344 bool AutocompletePopupViewGtk::IsOpen() const { 345 return opened_; 346 } 347 348 void AutocompletePopupViewGtk::InvalidateLine(size_t line) { 349 // TODO(deanm): Is it possible to use some constant for the width, instead 350 // of having to query the width of the window? 351 GdkRectangle line_rect = GetRectForLine( 352 line, GetWindowRect(window_->window).width()).ToGdkRectangle(); 353 gdk_window_invalidate_rect(window_->window, &line_rect, FALSE); 354 } 355 356 void AutocompletePopupViewGtk::UpdatePopupAppearance() { 357 const AutocompleteResult& result = model_->result(); 358 if (result.empty()) { 359 Hide(); 360 return; 361 } 362 363 Show(result.size()); 364 gtk_widget_queue_draw(window_); 365 } 366 367 gfx::Rect AutocompletePopupViewGtk::GetTargetBounds() { 368 if (!GTK_WIDGET_REALIZED(window_)) 369 return gfx::Rect(); 370 371 gfx::Rect retval = gtk_util::GetWidgetScreenBounds(window_); 372 373 // The widget bounds don't update synchronously so may be out of sync with 374 // our last size request. 375 GtkRequisition req; 376 gtk_widget_size_request(window_, &req); 377 retval.set_width(req.width); 378 retval.set_height(req.height); 379 380 return retval; 381 } 382 383 void AutocompletePopupViewGtk::PaintUpdatesNow() { 384 // Paint our queued invalidations now, synchronously. 385 gdk_window_process_updates(window_->window, FALSE); 386 } 387 388 void AutocompletePopupViewGtk::OnDragCanceled() { 389 ignore_mouse_drag_ = true; 390 } 391 392 void AutocompletePopupViewGtk::Observe(NotificationType type, 393 const NotificationSource& source, 394 const NotificationDetails& details) { 395 DCHECK(type == NotificationType::BROWSER_THEME_CHANGED); 396 397 if (theme_service_->UseGtkTheme()) { 398 gtk_util::UndoForceFontSize(window_); 399 400 border_color_ = theme_service_->GetBorderColor(); 401 402 gtk_util::GetTextColors( 403 &background_color_, &selected_background_color_, 404 &content_text_color_, &selected_content_text_color_); 405 406 hovered_background_color_ = gtk_util::AverageColors( 407 background_color_, selected_background_color_); 408 url_text_color_ = NormalURLColor(content_text_color_); 409 url_selected_text_color_ = SelectedURLColor(selected_content_text_color_, 410 selected_background_color_); 411 } else { 412 gtk_util::ForceFontSizePixels(window_, font_.GetFontSize()); 413 414 border_color_ = kBorderColor; 415 background_color_ = kBackgroundColor; 416 selected_background_color_ = kSelectedBackgroundColor; 417 hovered_background_color_ = kHoveredBackgroundColor; 418 419 content_text_color_ = kContentTextColor; 420 selected_content_text_color_ = kContentTextColor; 421 url_text_color_ = kURLTextColor; 422 url_selected_text_color_ = kURLTextColor; 423 } 424 425 // Calculate dimmed colors. 426 content_dim_text_color_ = 427 gtk_util::AverageColors(content_text_color_, 428 background_color_); 429 selected_content_dim_text_color_ = 430 gtk_util::AverageColors(selected_content_text_color_, 431 selected_background_color_); 432 433 // Set the background color, so we don't need to paint it manually. 434 gtk_widget_modify_bg(window_, GTK_STATE_NORMAL, &background_color_); 435 } 436 437 void AutocompletePopupViewGtk::Show(size_t num_results) { 438 gint origin_x, origin_y; 439 gdk_window_get_origin(location_bar_->window, &origin_x, &origin_y); 440 GtkAllocation allocation = location_bar_->allocation; 441 442 int horizontal_offset = 1; 443 gtk_window_move(GTK_WINDOW(window_), 444 origin_x + allocation.x - kBorderThickness + horizontal_offset, 445 origin_y + allocation.y + allocation.height - kBorderThickness - 1 + 446 kVerticalOffset); 447 gtk_widget_set_size_request(window_, 448 allocation.width + (kBorderThickness * 2) - (horizontal_offset * 2), 449 (num_results * kHeightPerResult) + (kBorderThickness * 2)); 450 gtk_widget_show(window_); 451 StackWindow(); 452 opened_ = true; 453 } 454 455 void AutocompletePopupViewGtk::Hide() { 456 gtk_widget_hide(window_); 457 opened_ = false; 458 } 459 460 void AutocompletePopupViewGtk::StackWindow() { 461 gfx::NativeView edit_view = edit_view_->GetNativeView(); 462 DCHECK(GTK_IS_WIDGET(edit_view)); 463 GtkWidget* toplevel = gtk_widget_get_toplevel(edit_view); 464 DCHECK(GTK_WIDGET_TOPLEVEL(toplevel)); 465 ui::StackPopupWindow(window_, toplevel); 466 } 467 468 size_t AutocompletePopupViewGtk::LineFromY(int y) { 469 size_t line = std::max(y - kBorderThickness, 0) / kHeightPerResult; 470 return std::min(line, model_->result().size() - 1); 471 } 472 473 void AutocompletePopupViewGtk::AcceptLine(size_t line, 474 WindowOpenDisposition disposition) { 475 const AutocompleteMatch& match = model_->result().match_at(line); 476 // OpenURL() may close the popup, which will clear the result set and, by 477 // extension, |match| and its contents. So copy the relevant strings out to 478 // make sure they stay alive until the call completes. 479 const GURL url(match.destination_url); 480 string16 keyword; 481 const bool is_keyword_hint = model_->GetKeywordForMatch(match, &keyword); 482 edit_view_->OpenURL(url, disposition, match.transition, GURL(), line, 483 is_keyword_hint ? string16() : keyword); 484 } 485 486 GdkPixbuf* AutocompletePopupViewGtk::IconForMatch( 487 const AutocompleteMatch& match, 488 bool selected) { 489 const SkBitmap* bitmap = model_->GetIconIfExtensionMatch(match); 490 if (bitmap) { 491 if (!ContainsKey(pixbufs_, bitmap)) 492 pixbufs_[bitmap] = gfx::GdkPixbufFromSkBitmap(bitmap); 493 return pixbufs_[bitmap]; 494 } 495 496 int icon = match.starred ? 497 IDR_OMNIBOX_STAR : AutocompleteMatch::TypeToIcon(match.type); 498 if (selected) { 499 switch (icon) { 500 case IDR_OMNIBOX_EXTENSION_APP: 501 icon = IDR_OMNIBOX_EXTENSION_APP_DARK; 502 break; 503 case IDR_OMNIBOX_HTTP: 504 icon = IDR_OMNIBOX_HTTP_DARK; 505 break; 506 case IDR_OMNIBOX_HISTORY: 507 icon = IDR_OMNIBOX_HISTORY_DARK; 508 break; 509 case IDR_OMNIBOX_SEARCH: 510 icon = IDR_OMNIBOX_SEARCH_DARK; 511 break; 512 case IDR_OMNIBOX_STAR: 513 icon = IDR_OMNIBOX_STAR_DARK; 514 break; 515 default: 516 NOTREACHED(); 517 break; 518 } 519 } 520 521 // TODO(estade): Do we want to flip these for RTL? (Windows doesn't). 522 return theme_service_->GetPixbufNamed(icon); 523 } 524 525 gboolean AutocompletePopupViewGtk::HandleMotion(GtkWidget* widget, 526 GdkEventMotion* event) { 527 // TODO(deanm): Windows has a bunch of complicated logic here. 528 size_t line = LineFromY(static_cast<int>(event->y)); 529 // There is both a hovered and selected line, hovered just means your mouse 530 // is over it, but selected is what's showing in the location edit. 531 model_->SetHoveredLine(line); 532 // Select the line if the user has the left mouse button down. 533 if (!ignore_mouse_drag_ && (event->state & GDK_BUTTON1_MASK)) 534 model_->SetSelectedLine(line, false, false); 535 return TRUE; 536 } 537 538 gboolean AutocompletePopupViewGtk::HandleButtonPress(GtkWidget* widget, 539 GdkEventButton* event) { 540 ignore_mouse_drag_ = false; 541 // Very similar to HandleMotion. 542 size_t line = LineFromY(static_cast<int>(event->y)); 543 model_->SetHoveredLine(line); 544 if (event->button == 1) 545 model_->SetSelectedLine(line, false, false); 546 return TRUE; 547 } 548 549 gboolean AutocompletePopupViewGtk::HandleButtonRelease(GtkWidget* widget, 550 GdkEventButton* event) { 551 if (ignore_mouse_drag_) { 552 // See header comment about this flag. 553 ignore_mouse_drag_ = false; 554 return TRUE; 555 } 556 557 size_t line = LineFromY(static_cast<int>(event->y)); 558 switch (event->button) { 559 case 1: // Left click. 560 AcceptLine(line, CURRENT_TAB); 561 break; 562 case 2: // Middle click. 563 AcceptLine(line, NEW_BACKGROUND_TAB); 564 break; 565 default: 566 // Don't open the result. 567 break; 568 } 569 return TRUE; 570 } 571 572 gboolean AutocompletePopupViewGtk::HandleExpose(GtkWidget* widget, 573 GdkEventExpose* event) { 574 bool ltr = !base::i18n::IsRTL(); 575 const AutocompleteResult& result = model_->result(); 576 577 gfx::Rect window_rect = GetWindowRect(event->window); 578 gfx::Rect damage_rect = gfx::Rect(event->area); 579 // Handle when our window is super narrow. A bunch of the calculations 580 // below would go negative, and really we're not going to fit anything 581 // useful in such a small window anyway. Just don't paint anything. 582 // This means we won't draw the border, but, yeah, whatever. 583 // TODO(deanm): Make the code more robust and remove this check. 584 if (window_rect.width() < (kIconAreaWidth * 3)) 585 return TRUE; 586 587 GdkDrawable* drawable = GDK_DRAWABLE(event->window); 588 GdkGC* gc = gdk_gc_new(drawable); 589 590 // kBorderColor is unallocated, so use the GdkRGB routine. 591 gdk_gc_set_rgb_fg_color(gc, &border_color_); 592 593 // This assert is kinda ugly, but it would be more currently unneeded work 594 // to support painting a border that isn't 1 pixel thick. There is no point 595 // in writing that code now, and explode if that day ever comes. 596 COMPILE_ASSERT(kBorderThickness == 1, border_1px_implied); 597 // Draw the 1px border around the entire window. 598 gdk_draw_rectangle(drawable, gc, FALSE, 599 0, 0, 600 window_rect.width() - 1, window_rect.height() - 1); 601 602 pango_layout_set_height(layout_, kHeightPerResult * PANGO_SCALE); 603 604 for (size_t i = 0; i < result.size(); ++i) { 605 gfx::Rect line_rect = GetRectForLine(i, window_rect.width()); 606 // Only repaint and layout damaged lines. 607 if (!line_rect.Intersects(damage_rect)) 608 continue; 609 610 const AutocompleteMatch& match = result.match_at(i); 611 bool is_selected = (model_->selected_line() == i); 612 bool is_hovered = (model_->hovered_line() == i); 613 if (is_selected || is_hovered) { 614 gdk_gc_set_rgb_fg_color(gc, is_selected ? &selected_background_color_ : 615 &hovered_background_color_); 616 // This entry is selected or hovered, fill a rect with the color. 617 gdk_draw_rectangle(drawable, gc, TRUE, 618 line_rect.x(), line_rect.y(), 619 line_rect.width(), line_rect.height()); 620 } 621 622 int icon_start_x = ltr ? kIconLeftPadding : 623 (line_rect.width() - kIconLeftPadding - kIconWidth); 624 // Draw the icon for this result. 625 DrawFullPixbuf(drawable, gc, 626 IconForMatch(match, is_selected), 627 icon_start_x, line_rect.y() + kIconTopPadding); 628 629 // Draw the results text vertically centered in the results space. 630 // First draw the contents / url, but don't let it take up the whole width 631 // if there is also a description to be shown. 632 bool has_description = !match.description.empty(); 633 int text_width = window_rect.width() - (kIconAreaWidth + kRightPadding); 634 int allocated_content_width = has_description ? 635 static_cast<int>(text_width * kContentWidthPercentage) : text_width; 636 pango_layout_set_width(layout_, allocated_content_width * PANGO_SCALE); 637 638 // Note: We force to URL to LTR for all text directions. 639 SetupLayoutForMatch(layout_, match.contents, match.contents_class, 640 is_selected ? &selected_content_text_color_ : 641 &content_text_color_, 642 is_selected ? &selected_content_dim_text_color_ : 643 &content_dim_text_color_, 644 is_selected ? &url_selected_text_color_ : 645 &url_text_color_, 646 std::string()); 647 648 int actual_content_width, actual_content_height; 649 pango_layout_get_size(layout_, 650 &actual_content_width, &actual_content_height); 651 actual_content_width /= PANGO_SCALE; 652 actual_content_height /= PANGO_SCALE; 653 654 // DCHECK_LT(actual_content_height, kHeightPerResult); // Font is too tall. 655 // Center the text within the line. 656 int content_y = std::max(line_rect.y(), 657 line_rect.y() + ((kHeightPerResult - actual_content_height) / 2)); 658 659 gdk_draw_layout(drawable, gc, 660 ltr ? kIconAreaWidth : 661 (text_width - actual_content_width), 662 content_y, layout_); 663 664 if (has_description) { 665 pango_layout_set_width(layout_, 666 (text_width - actual_content_width) * PANGO_SCALE); 667 668 // In Windows, a boolean "force_dim" is passed as true for the 669 // description. Here, we pass the dim text color for both normal and dim, 670 // to accomplish the same thing. 671 SetupLayoutForMatch(layout_, match.description, match.description_class, 672 is_selected ? &selected_content_dim_text_color_ : 673 &content_dim_text_color_, 674 is_selected ? &selected_content_dim_text_color_ : 675 &content_dim_text_color_, 676 is_selected ? &url_selected_text_color_ : 677 &url_text_color_, 678 std::string(" - ")); 679 gint actual_description_width; 680 pango_layout_get_size(layout_, &actual_description_width, NULL); 681 gdk_draw_layout(drawable, gc, ltr ? 682 (kIconAreaWidth + actual_content_width) : 683 (text_width - actual_content_width - 684 (actual_description_width / PANGO_SCALE)), 685 content_y, layout_); 686 } 687 } 688 689 g_object_unref(gc); 690 691 return TRUE; 692 } 693