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