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