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