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