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|.
GetWindowRect(GdkWindow * 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.
GetRectForLine(size_t line,int width)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.
DrawFullPixbuf(GdkDrawable * drawable,GdkGC * gc,GdkPixbuf * pixbuf,gint dest_x,gint dest_y)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.
GetUTF8Offset(const string16 & text,size_t text_offset)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.
NormalURLColor(GdkColor foreground)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.
SelectedURLColor(GdkColor foreground,GdkColor background)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
SetupLayoutForMatch(PangoLayout * layout,const string16 & text,const AutocompleteMatch::ACMatchClassifications & classifications,const GdkColor * base_color,const GdkColor * dim_color,const GdkColor * url_color,const std::string & prefix_text)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
AutocompletePopupViewGtk(const gfx::Font & font,AutocompleteEditView * edit_view,AutocompleteEditModel * edit_model,Profile * profile,GtkWidget * location_bar)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
~AutocompletePopupViewGtk()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
IsOpen() const344 bool AutocompletePopupViewGtk::IsOpen() const {
345 return opened_;
346 }
347
InvalidateLine(size_t line)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
UpdatePopupAppearance()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
GetTargetBounds()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
PaintUpdatesNow()383 void AutocompletePopupViewGtk::PaintUpdatesNow() {
384 // Paint our queued invalidations now, synchronously.
385 gdk_window_process_updates(window_->window, FALSE);
386 }
387
OnDragCanceled()388 void AutocompletePopupViewGtk::OnDragCanceled() {
389 ignore_mouse_drag_ = true;
390 }
391
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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
Show(size_t num_results)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
Hide()455 void AutocompletePopupViewGtk::Hide() {
456 gtk_widget_hide(window_);
457 opened_ = false;
458 }
459
StackWindow()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
LineFromY(int y)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
AcceptLine(size_t line,WindowOpenDisposition disposition)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
IconForMatch(const AutocompleteMatch & match,bool selected)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
HandleMotion(GtkWidget * widget,GdkEventMotion * event)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
HandleButtonPress(GtkWidget * widget,GdkEventButton * event)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
HandleButtonRelease(GtkWidget * widget,GdkEventButton * event)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
HandleExpose(GtkWidget * widget,GdkEventExpose * event)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