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