Home | History | Annotate | Download | only in autocomplete
      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