diff options
Diffstat (limited to 'chrome/browser/autocomplete/autocomplete_popup_view_gtk.cc')
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_popup_view_gtk.cc | 681 |
1 files changed, 681 insertions, 0 deletions
diff --git a/chrome/browser/autocomplete/autocomplete_popup_view_gtk.cc b/chrome/browser/autocomplete/autocomplete_popup_view_gtk.cc new file mode 100644 index 0000000..8a6c24b --- /dev/null +++ b/chrome/browser/autocomplete/autocomplete_popup_view_gtk.cc @@ -0,0 +1,681 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/autocomplete/autocomplete_popup_view_gtk.h" + +#include <gtk/gtk.h> + +#include <algorithm> +#include <string> + +#include "app/resource_bundle.h" +#include "base/basictypes.h" +#include "base/i18n/rtl.h" +#include "base/logging.h" +#include "base/stl_util-inl.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/autocomplete/autocomplete.h" +#include "chrome/browser/autocomplete/autocomplete_edit.h" +#include "chrome/browser/autocomplete/autocomplete_edit_view_gtk.h" +#include "chrome/browser/autocomplete/autocomplete_popup_model.h" +#include "chrome/browser/defaults.h" +#include "chrome/browser/gtk/gtk_theme_provider.h" +#include "chrome/browser/gtk/gtk_util.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/search_engines/template_url.h" +#include "chrome/browser/search_engines/template_url_model.h" +#include "chrome/common/notification_service.h" +#include "gfx/color_utils.h" +#include "gfx/font.h" +#include "gfx/gtk_util.h" +#include "gfx/rect.h" +#include "gfx/skia_utils_gtk.h" +#include "grit/theme_resources.h" + +namespace { + +const GdkColor kBorderColor = GDK_COLOR_RGB(0xc7, 0xca, 0xce); +const GdkColor kBackgroundColor = GDK_COLOR_RGB(0xff, 0xff, 0xff); +const GdkColor kSelectedBackgroundColor = GDK_COLOR_RGB(0xdf, 0xe6, 0xf6); +const GdkColor kHoveredBackgroundColor = GDK_COLOR_RGB(0xef, 0xf2, 0xfa); + +const GdkColor kContentTextColor = GDK_COLOR_RGB(0x00, 0x00, 0x00); +const GdkColor kURLTextColor = GDK_COLOR_RGB(0x00, 0x88, 0x00); +const GdkColor kDescriptionTextColor = GDK_COLOR_RGB(0x80, 0x80, 0x80); +const GdkColor kDescriptionSelectedTextColor = GDK_COLOR_RGB(0x78, 0x82, 0xb1); + +// We have a 1 pixel border around the entire results popup. +const int kBorderThickness = 1; + +// The vertical height of each result. +const int kHeightPerResult = 24; + +// Width of the icons. +const int kIconWidth = 17; + +// We want to vertically center the image in the result space. +const int kIconTopPadding = 2; + +// Space between the left edge (including the border) and the text. +const int kIconLeftPadding = 5 + kBorderThickness; + +// Space between the image and the text. +const int kIconRightPadding = 7; + +// Space between the left edge (including the border) and the text. +const int kIconAreaWidth = + kIconLeftPadding + kIconWidth + kIconRightPadding; + +// Space between the right edge (including the border) and the text. +const int kRightPadding = 3; + +// When we have both a content and description string, we don't want the +// content to push the description off. Limit the content to a percentage of +// the total width. +const float kContentWidthPercentage = 0.7; + +// How much to offset the popup from the bottom of the location bar in gtk mode. +const int kGtkVerticalOffset = 3; + +// How much we shrink the popup on the left/right in gtk mode. +const int kGtkHorizontalOffset = 1; + +// UTF-8 Left-to-right embedding. +const char* kLRE = "\xe2\x80\xaa"; + +// Return a Rect covering the whole area of |window|. +gfx::Rect GetWindowRect(GdkWindow* window) { + gint width, height; + gdk_drawable_get_size(GDK_DRAWABLE(window), &width, &height); + return gfx::Rect(width, height); +} + +// Return a Rect for the space for a result line. This excludes the border, +// but includes the padding. This is the area that is colored for a selection. +gfx::Rect GetRectForLine(size_t line, int width) { + return gfx::Rect(kBorderThickness, + (line * kHeightPerResult) + kBorderThickness, + width - (kBorderThickness * 2), + kHeightPerResult); +} + +// Helper for drawing an entire pixbuf without dithering. +void DrawFullPixbuf(GdkDrawable* drawable, GdkGC* gc, GdkPixbuf* pixbuf, + gint dest_x, gint dest_y) { + gdk_draw_pixbuf(drawable, gc, pixbuf, + 0, 0, // Source. + dest_x, dest_y, // Dest. + -1, -1, // Width/height (auto). + GDK_RGB_DITHER_NONE, 0, 0); // Don't dither. +} + +// TODO(deanm): Find some better home for this, and make it more efficient. +size_t GetUTF8Offset(const std::wstring& wide_text, size_t wide_text_offset) { + return WideToUTF8(wide_text.substr(0, wide_text_offset)).size(); +} + +void SetupLayoutForMatch(PangoLayout* layout, + const std::wstring& text, + AutocompleteMatch::ACMatchClassifications classifications, + const GdkColor* base_color, + const GdkColor* url_color, + const std::string& prefix_text) { + // In RTL, mark text with left-to-right embedding mark if there is no strong + // RTL characters inside it, so the ending punctuation displays correctly + // and the eliding ellipsis displays correctly. We only mark the text with + // LRE. Wrapping it with LRE and PDF by calling AdjustStringForLocaleDirection + // will render the elllipsis at the left of the elided pure LTR text. + bool marked_with_lre = false; + std::wstring localized_text = text; + bool is_rtl = base::i18n::IsRTL(); + if (is_rtl && !base::i18n::StringContainsStrongRTLChars(localized_text)) { + localized_text.insert(0, 1, + static_cast<wchar_t>(base::i18n::kLeftToRightEmbeddingMark)); + marked_with_lre = true; + } + + // We can have a prefix, or insert additional characters while processing the + // classifications. We need to take this in to account when we translate the + // wide offsets in the classification into text_utf8 byte offsets. + size_t additional_offset = prefix_text.size(); // Length in utf-8 bytes. + std::string text_utf8 = prefix_text + WideToUTF8(localized_text); + + PangoAttrList* attrs = pango_attr_list_new(); + + // TODO(deanm): This is a hack, just to handle coloring prefix_text. + // Hopefully I can clean up the match situation a bit and this will + // come out cleaner. For now, apply the base color to the whole text + // so that our prefix will have the base color applied. + PangoAttribute* base_fg_attr = pango_attr_foreground_new( + base_color->red, base_color->green, base_color->blue); + pango_attr_list_insert(attrs, base_fg_attr); // Ownership taken. + + // Walk through the classifications, they are linear, in order, and should + // cover the entire text. We create a bunch of overlapping attributes, + // extending from the offset to the end of the string. The ones created + // later will override the previous ones, meaning we will still setup each + // portion correctly, we just don't need to compute the end offset. + for (ACMatchClassifications::const_iterator i = classifications.begin(); + i != classifications.end(); ++i) { + size_t offset = GetUTF8Offset(localized_text, i->offset) + + additional_offset; + + // TODO(deanm): All the colors should probably blend based on whether this + // result is selected or not. This would include the green URLs. Right + // now the caller is left to blend only the base color. Do we need to + // handle things like DIM urls? Turns out DIM means something different + // than you'd think, all of the description text is not DIM, it is a + // special case that is not very common, but we should figure out and + // support it. + const GdkColor* color = base_color; + if (i->style & ACMatchClassification::URL) { + color = url_color; + // Insert a left to right embedding to make sure that URLs are shown LTR. + if (is_rtl && !marked_with_lre) { + std::string lre(kLRE); + text_utf8.insert(offset, lre); + additional_offset += lre.size(); + } + } + + PangoAttribute* fg_attr = pango_attr_foreground_new( + color->red, color->green, color->blue); + fg_attr->start_index = offset; + pango_attr_list_insert(attrs, fg_attr); // Ownership taken. + + // Matched portions are bold, otherwise use the normal weight. + PangoWeight weight = (i->style & ACMatchClassification::MATCH) ? + PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL; + PangoAttribute* weight_attr = pango_attr_weight_new(weight); + weight_attr->start_index = offset; + pango_attr_list_insert(attrs, weight_attr); // Ownership taken. + } + + pango_layout_set_text(layout, text_utf8.data(), text_utf8.size()); + pango_layout_set_attributes(layout, attrs); // Ref taken. + pango_attr_list_unref(attrs); +} + +// Generates the normal URL color, a green color used in unhighlighted URL +// text. It is a mix of |kURLTextColor| and the current text color. Unlike the +// selected text color, It is more important to match the qualities of the +// foreground typeface color instead of taking the background into account. +GdkColor NormalURLColor(GdkColor foreground) { + color_utils::HSL fg_hsl; + color_utils::SkColorToHSL(gfx::GdkColorToSkColor(foreground), &fg_hsl); + + color_utils::HSL hue_hsl; + color_utils::SkColorToHSL(gfx::GdkColorToSkColor(kURLTextColor), &hue_hsl); + + // Only allow colors that have a fair amount of saturation in them (color vs + // white). This means that our output color will always be fairly green. + double s = std::max(0.5, fg_hsl.s); + + // Make sure the luminance is at least as bright as the |kURLTextColor| green + // would be if we were to use that. + double l; + if (fg_hsl.l < hue_hsl.l) + l = hue_hsl.l; + else + l = (fg_hsl.l + hue_hsl.l) / 2; + + color_utils::HSL output = { hue_hsl.h, s, l }; + return gfx::SkColorToGdkColor(color_utils::HSLToSkColor(output, 255)); +} + +// Generates the selected URL color, a green color used on URL text in the +// currently highlighted entry in the autocomplete popup. It's a mix of +// |kURLTextColor|, the current text color, and the background color (the +// select highlight). It is more important to contrast with the background +// saturation than to look exactly like the foreground color. +GdkColor SelectedURLColor(GdkColor foreground, GdkColor background) { + color_utils::HSL fg_hsl; + color_utils::SkColorToHSL(gfx::GdkColorToSkColor(foreground), &fg_hsl); + + color_utils::HSL bg_hsl; + color_utils::SkColorToHSL(gfx::GdkColorToSkColor(background), &bg_hsl); + + color_utils::HSL hue_hsl; + color_utils::SkColorToHSL(gfx::GdkColorToSkColor(kURLTextColor), &hue_hsl); + + // The saturation of the text should be opposite of the background, clamped + // to 0.2-0.8. We make sure it's greater than 0.2 so there's some color, but + // less than 0.8 so it's not the oversaturated neon-color. + double opposite_s = 1 - bg_hsl.s; + double s = std::max(0.2, std::min(0.8, opposite_s)); + + // The luminance should match the luminance of the foreground text. Again, + // we clamp so as to have at some amount of color (green) in the text. + double opposite_l = fg_hsl.l; + double l = std::max(0.1, std::min(0.9, opposite_l)); + + color_utils::HSL output = { hue_hsl.h, s, l }; + return gfx::SkColorToGdkColor(color_utils::HSLToSkColor(output, 255)); +} + +} // namespace + +AutocompletePopupViewGtk::AutocompletePopupViewGtk( + AutocompleteEditView* edit_view, + AutocompleteEditModel* edit_model, + Profile* profile, + GtkWidget* location_bar) + : model_(new AutocompletePopupModel(this, edit_model, profile)), + edit_view_(edit_view), + location_bar_(location_bar), + window_(gtk_window_new(GTK_WINDOW_POPUP)), + layout_(NULL), + theme_provider_(GtkThemeProvider::GetFrom(profile)), + ignore_mouse_drag_(false), + opened_(false) { + GTK_WIDGET_UNSET_FLAGS(window_, GTK_CAN_FOCUS); + // Don't allow the window to be resized. This also forces the window to + // shrink down to the size of its child contents. + gtk_window_set_resizable(GTK_WINDOW(window_), FALSE); + gtk_widget_set_app_paintable(window_, TRUE); + // Have GTK double buffer around the expose signal. + gtk_widget_set_double_buffered(window_, TRUE); + + // Cache the layout so we don't have to create it for every expose. If we + // were a real widget we should handle changing directions, but we're not + // doing RTL or anything yet, so it shouldn't be important now. + layout_ = gtk_widget_create_pango_layout(window_, NULL); + // We don't want the layout of search results depending on their language. + pango_layout_set_auto_dir(layout_, FALSE); + // We always ellipsize when drawing our text runs. + pango_layout_set_ellipsize(layout_, PANGO_ELLIPSIZE_END); + // TODO(deanm): We might want to eventually follow what Windows does and + // plumb a gfx::Font through. This is because popup windows have a + // different font size, although we could just derive that font here. + // For now, force the font size. + gfx::Font font = gfx::Font::CreateFont( + gfx::Font().FontName(), browser_defaults::kAutocompletePopupFontSize); + PangoFontDescription* pfd = gfx::Font::PangoFontFromGfxFont(font); + pango_layout_set_font_description(layout_, pfd); + pango_font_description_free(pfd); + + gtk_widget_add_events(window_, GDK_BUTTON_MOTION_MASK | + GDK_POINTER_MOTION_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK); + g_signal_connect(window_, "motion-notify-event", + G_CALLBACK(&HandleMotionThunk), this); + g_signal_connect(window_, "button-press-event", + G_CALLBACK(&HandleButtonPressThunk), this); + g_signal_connect(window_, "button-release-event", + G_CALLBACK(&HandleButtonReleaseThunk), this); + g_signal_connect(window_, "expose-event", + G_CALLBACK(&HandleExposeThunk), this); + + registrar_.Add(this, + NotificationType::BROWSER_THEME_CHANGED, + NotificationService::AllSources()); + theme_provider_->InitThemesFor(this); + + // TODO(erg): There appears to be a bug somewhere in something which shows + // itself when we're in NX. Previously, we called + // gtk_util::ActAsRoundedWindow() to make this popup have rounded + // corners. This worked on the standard xorg server (both locally and + // remotely), but broke over NX. My current hypothesis is that it can't + // handle shaping top-level windows during an expose event, but I'm not sure + // how else to get accurate shaping information. + // + // r25080 (the original patch that added rounded corners here) should + // eventually be cherry picked once I know what's going + // on. http://crbug.com/22015. +} + +AutocompletePopupViewGtk::~AutocompletePopupViewGtk() { + // Explicitly destroy our model here, before we destroy our GTK widgets. + // This is because the model destructor can call back into us, and we need + // to make sure everything is still valid when it does. + model_.reset(); + g_object_unref(layout_); + gtk_widget_destroy(window_); + + for (PixbufMap::iterator it = pixbufs_.begin(); it != pixbufs_.end(); ++it) + g_object_unref(it->second); +} + +void AutocompletePopupViewGtk::InvalidateLine(size_t line) { + // TODO(deanm): Is it possible to use some constant for the width, instead + // of having to query the width of the window? + GdkRectangle line_rect = GetRectForLine( + line, GetWindowRect(window_->window).width()).ToGdkRectangle(); + gdk_window_invalidate_rect(window_->window, &line_rect, FALSE); +} + +void AutocompletePopupViewGtk::UpdatePopupAppearance() { + const AutocompleteResult& result = model_->result(); + if (result.empty()) { + Hide(); + return; + } + + Show(result.size()); + gtk_widget_queue_draw(window_); +} + +void AutocompletePopupViewGtk::PaintUpdatesNow() { + // Paint our queued invalidations now, synchronously. + gdk_window_process_updates(window_->window, FALSE); +} + +void AutocompletePopupViewGtk::OnDragCanceled() { + ignore_mouse_drag_ = true; +} + +AutocompletePopupModel* AutocompletePopupViewGtk::GetModel() { + return model_.get(); +} + +void AutocompletePopupViewGtk::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(type == NotificationType::BROWSER_THEME_CHANGED); + + if (theme_provider_->UseGtkTheme()) { + border_color_ = theme_provider_->GetBorderColor(); + + // Create a fake gtk table + GtkWidget* fake_tree = gtk_entry_new(); + GtkStyle* style = gtk_rc_get_style(fake_tree); + + background_color_ = style->base[GTK_STATE_NORMAL]; + selected_background_color_ = style->base[GTK_STATE_SELECTED]; + hovered_background_color_ = gtk_util::AverageColors( + background_color_, selected_background_color_); + + content_text_color_ = style->text[GTK_STATE_NORMAL]; + selected_content_text_color_ = style->text[GTK_STATE_SELECTED]; + url_text_color_ = + NormalURLColor(style->text[GTK_STATE_NORMAL]); + url_selected_text_color_ = + SelectedURLColor(style->text[GTK_STATE_SELECTED], + style->base[GTK_STATE_SELECTED]); + + description_text_color_ = style->text[GTK_STATE_NORMAL]; + description_selected_text_color_ = style->text[GTK_STATE_SELECTED]; + + g_object_ref_sink(fake_tree); + g_object_unref(fake_tree); + } else { + border_color_ = kBorderColor; + background_color_ = kBackgroundColor; + selected_background_color_ = kSelectedBackgroundColor; + hovered_background_color_ = kHoveredBackgroundColor; + + content_text_color_ = kContentTextColor; + selected_content_text_color_ = kContentTextColor; + url_text_color_ = kURLTextColor; + url_selected_text_color_ = kURLTextColor; + description_text_color_ = kDescriptionTextColor; + description_selected_text_color_ = kDescriptionSelectedTextColor; + } + + // Set the background color, so we don't need to paint it manually. + gtk_widget_modify_bg(window_, GTK_STATE_NORMAL, &background_color_); +} + +void AutocompletePopupViewGtk::Show(size_t num_results) { + gint origin_x, origin_y; + gdk_window_get_origin(location_bar_->window, &origin_x, &origin_y); + GtkAllocation allocation = location_bar_->allocation; + int vertical_offset = 0; + int horizontal_offset = 0; + if (theme_provider_->UseGtkTheme()) { + // Shrink the popup by 1 pixel on both sides in gtk mode. The darkest line + // is usually one pixel in, and is almost always +/-1 pixel from this, + // meaning the vertical offset will hide (hopefully) problems when this is + // wrong. + horizontal_offset = kGtkHorizontalOffset; + + // We offset the the popup from the bottom of the location bar in gtk + // mode. The background color between the bottom of the location bar and + // the popup helps hide the fact that we can't really reliably match what + // the user would otherwise preceive as the left/right edges of the + // location bar. + vertical_offset = kGtkVerticalOffset; + } + + gtk_window_move(GTK_WINDOW(window_), + origin_x + allocation.x - kBorderThickness + horizontal_offset, + origin_y + allocation.y + allocation.height - kBorderThickness - 1 + + vertical_offset); + gtk_widget_set_size_request(window_, + allocation.width + (kBorderThickness * 2) - (horizontal_offset * 2), + (num_results * kHeightPerResult) + (kBorderThickness * 2)); + gtk_widget_show(window_); + StackWindow(); + opened_ = true; +} + +void AutocompletePopupViewGtk::Hide() { + gtk_widget_hide(window_); + opened_ = false; +} + +void AutocompletePopupViewGtk::StackWindow() { + gfx::NativeView edit_view = edit_view_->GetNativeView(); + DCHECK(GTK_IS_WIDGET(edit_view)); + GtkWidget* toplevel = gtk_widget_get_toplevel(edit_view); + DCHECK(GTK_WIDGET_TOPLEVEL(toplevel)); + gtk_util::StackPopupWindow(window_, toplevel); +} + +size_t AutocompletePopupViewGtk::LineFromY(int y) { + size_t line = std::max(y - kBorderThickness, 0) / kHeightPerResult; + return std::min(line, model_->result().size() - 1); +} + +void AutocompletePopupViewGtk::AcceptLine(size_t line, + WindowOpenDisposition disposition) { + const AutocompleteMatch& match = model_->result().match_at(line); + // OpenURL() may close the popup, which will clear the result set and, by + // extension, |match| and its contents. So copy the relevant strings out to + // make sure they stay alive until the call completes. + const GURL url(match.destination_url); + std::wstring keyword; + const bool is_keyword_hint = model_->GetKeywordForMatch(match, &keyword); + edit_view_->OpenURL(url, disposition, match.transition, GURL(), line, + is_keyword_hint ? std::wstring() : keyword); +} + +GdkPixbuf* AutocompletePopupViewGtk::IconForMatch( + const AutocompleteMatch& match, bool selected) { + const SkBitmap* bitmap = model_->GetSpecialIconForMatch(match); + if (bitmap) { + if (!ContainsKey(pixbufs_, bitmap)) + pixbufs_[bitmap] = gfx::GdkPixbufFromSkBitmap(bitmap); + return pixbufs_[bitmap]; + } + + int icon = match.starred ? + IDR_OMNIBOX_STAR : AutocompleteMatch::TypeToIcon(match.type); + if (selected) { + switch (icon) { + case IDR_OMNIBOX_HTTP: icon = IDR_OMNIBOX_HTTP_DARK; break; + case IDR_OMNIBOX_HISTORY: icon = IDR_OMNIBOX_HISTORY_DARK; break; + case IDR_OMNIBOX_SEARCH: icon = IDR_OMNIBOX_SEARCH_DARK; break; + case IDR_OMNIBOX_MORE: icon = IDR_OMNIBOX_MORE_DARK; break; + case IDR_OMNIBOX_STAR: icon = IDR_OMNIBOX_STAR_DARK; break; + default: NOTREACHED(); break; + } + } + + // TODO(estade): Do we want to flip these for RTL? (Windows doesn't). + return theme_provider_->GetPixbufNamed(icon); +} + +gboolean AutocompletePopupViewGtk::HandleMotion(GtkWidget* widget, + GdkEventMotion* event) { + // TODO(deanm): Windows has a bunch of complicated logic here. + size_t line = LineFromY(static_cast<int>(event->y)); + // There is both a hovered and selected line, hovered just means your mouse + // is over it, but selected is what's showing in the location edit. + model_->SetHoveredLine(line); + // Select the line if the user has the left mouse button down. + if (!ignore_mouse_drag_ && (event->state & GDK_BUTTON1_MASK)) + model_->SetSelectedLine(line, false); + return TRUE; +} + +gboolean AutocompletePopupViewGtk::HandleButtonPress(GtkWidget* widget, + GdkEventButton* event) { + ignore_mouse_drag_ = false; + // Very similar to HandleMotion. + size_t line = LineFromY(static_cast<int>(event->y)); + model_->SetHoveredLine(line); + if (event->button == 1) + model_->SetSelectedLine(line, false); + return TRUE; +} + +gboolean AutocompletePopupViewGtk::HandleButtonRelease(GtkWidget* widget, + GdkEventButton* event) { + if (ignore_mouse_drag_) { + // See header comment about this flag. + ignore_mouse_drag_ = false; + return TRUE; + } + + size_t line = LineFromY(static_cast<int>(event->y)); + switch (event->button) { + case 1: // Left click. + AcceptLine(line, CURRENT_TAB); + break; + case 2: // Middle click. + AcceptLine(line, NEW_BACKGROUND_TAB); + break; + default: + // Don't open the result. + break; + } + return TRUE; +} + +gboolean AutocompletePopupViewGtk::HandleExpose(GtkWidget* widget, + GdkEventExpose* event) { + bool ltr = !base::i18n::IsRTL(); + const AutocompleteResult& result = model_->result(); + + gfx::Rect window_rect = GetWindowRect(event->window); + gfx::Rect damage_rect = gfx::Rect(event->area); + // Handle when our window is super narrow. A bunch of the calculations + // below would go negative, and really we're not going to fit anything + // useful in such a small window anyway. Just don't paint anything. + // This means we won't draw the border, but, yeah, whatever. + // TODO(deanm): Make the code more robust and remove this check. + if (window_rect.width() < (kIconAreaWidth * 3)) + return TRUE; + + GdkDrawable* drawable = GDK_DRAWABLE(event->window); + GdkGC* gc = gdk_gc_new(drawable); + + // kBorderColor is unallocated, so use the GdkRGB routine. + gdk_gc_set_rgb_fg_color(gc, &border_color_); + + // This assert is kinda ugly, but it would be more currently unneeded work + // to support painting a border that isn't 1 pixel thick. There is no point + // in writing that code now, and explode if that day ever comes. + COMPILE_ASSERT(kBorderThickness == 1, border_1px_implied); + // Draw the 1px border around the entire window. + gdk_draw_rectangle(drawable, gc, FALSE, + 0, 0, + window_rect.width() - 1, window_rect.height() - 1); + + pango_layout_set_height(layout_, kHeightPerResult * PANGO_SCALE); + + // An offset to align text in gtk mode. The hard coded constants in this file + // are all created for the chrome-theme. In an effort to make this look good + // on the majority of gtk themes, we shrink the popup by one pixel on each + // side and push it downwards a bit so there's space between the drawn + // location bar and the popup so we don't touch it (contrast with + // chrome-theme where that's exactly what we want). Because of that, we need + // to shift the content inside the popup by one pixel. + int gtk_offset = 0; + if (theme_provider_->UseGtkTheme()) + gtk_offset = kGtkHorizontalOffset; + + for (size_t i = 0; i < result.size(); ++i) { + gfx::Rect line_rect = GetRectForLine(i, window_rect.width()); + // Only repaint and layout damaged lines. + if (!line_rect.Intersects(damage_rect)) + continue; + + const AutocompleteMatch& match = result.match_at(i); + bool is_selected = (model_->selected_line() == i); + bool is_hovered = (model_->hovered_line() == i); + if (is_selected || is_hovered) { + gdk_gc_set_rgb_fg_color(gc, is_selected ? &selected_background_color_ : + &hovered_background_color_); + // This entry is selected or hovered, fill a rect with the color. + gdk_draw_rectangle(drawable, gc, TRUE, + line_rect.x(), line_rect.y(), + line_rect.width(), line_rect.height()); + } + + int icon_start_x = ltr ? (kIconLeftPadding - gtk_offset) : + (line_rect.width() - kIconLeftPadding - kIconWidth + gtk_offset); + // Draw the icon for this result. + DrawFullPixbuf(drawable, gc, + IconForMatch(match, is_selected), + icon_start_x, line_rect.y() + kIconTopPadding); + + // Draw the results text vertically centered in the results space. + // First draw the contents / url, but don't let it take up the whole width + // if there is also a description to be shown. + bool has_description = !match.description.empty(); + int text_width = window_rect.width() - (kIconAreaWidth + kRightPadding); + int allocated_content_width = has_description ? + static_cast<int>(text_width * kContentWidthPercentage) : text_width; + pango_layout_set_width(layout_, allocated_content_width * PANGO_SCALE); + + // Note: We force to URL to LTR for all text directions. + SetupLayoutForMatch(layout_, match.contents, match.contents_class, + is_selected ? &selected_content_text_color_ : + &content_text_color_, + is_selected ? &url_selected_text_color_ : + &url_text_color_, + std::string()); + + int actual_content_width, actual_content_height; + pango_layout_get_size(layout_, + &actual_content_width, &actual_content_height); + actual_content_width /= PANGO_SCALE; + actual_content_height /= PANGO_SCALE; + + // DCHECK_LT(actual_content_height, kHeightPerResult); // Font is too tall. + // Center the text within the line. + int content_y = std::max(line_rect.y(), + line_rect.y() + ((kHeightPerResult - actual_content_height) / 2)); + + gdk_draw_layout(drawable, gc, + ltr ? (kIconAreaWidth - gtk_offset) : + (text_width - actual_content_width + gtk_offset), + content_y, layout_); + + if (has_description) { + pango_layout_set_width(layout_, + (text_width - actual_content_width) * PANGO_SCALE); + SetupLayoutForMatch(layout_, match.description, match.description_class, + is_selected ? &description_selected_text_color_ : + &description_text_color_, + is_selected ? &url_selected_text_color_ : + &url_text_color_, + std::string(" - ")); + gint actual_description_width; + pango_layout_get_size(layout_, &actual_description_width, NULL); + gdk_draw_layout(drawable, gc, ltr ? + (kIconAreaWidth - gtk_offset + actual_content_width) : + (text_width - actual_content_width + gtk_offset - + (actual_description_width / PANGO_SCALE)), + content_y, layout_); + } + } + + g_object_unref(gc); + + return TRUE; +} |