// Copyright (c) 2011 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 "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_match.h"
#include "chrome/browser/autocomplete/autocomplete_popup_model.h"
#include "chrome/browser/defaults.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url.h"
#include "chrome/browser/search_engines/template_url_model.h"
#include "chrome/browser/ui/gtk/gtk_theme_service.h"
#include "chrome/browser/ui/gtk/gtk_util.h"
#include "content/common/notification_service.h"
#include "grit/theme_resources.h"
#include "ui/base/gtk/gtk_windowing.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/font.h"
#include "ui/gfx/gtk_util.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/skia_utils_gtk.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);
// 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 = 3 + kBorderThickness;
// Space between the image and the text.
const int kIconRightPadding = 5;
// 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.
const int kVerticalOffset = 3;
// The size delta between the font used for the edit and the result rows. Passed
// to gfx::Font::DeriveFont.
const int kEditFontAdjust = -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 string16& text, size_t text_offset) {
return UTF16ToUTF8(text.substr(0, text_offset)).size();
}
// 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
void AutocompletePopupViewGtk::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) {
// 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
// or WrapStringWithLTRFormatting will render the elllipsis at the left of the
// elided pure LTR text.
bool marked_with_lre = false;
string16 localized_text = text;
// Pango is really easy to overflow and send into a computational death
// spiral that can corrupt the screen. Assume that we'll never have more than
// 2000 characters, which should be a safe assumption until we all get robot
// eyes. http://crbug.com/66576
if (localized_text.size() > 2000)
localized_text = localized_text.substr(0, 2000);
bool is_rtl = base::i18n::IsRTL();
if (is_rtl && !base::i18n::StringContainsStrongRTLChars(localized_text)) {
localized_text.insert(0, 1, 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
// UTF-16 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 + UTF16ToUTF8(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();
}
}
if (i->style & ACMatchClassification::DIM)
color = dim_color;
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);
}
AutocompletePopupViewGtk::AutocompletePopupViewGtk(
const gfx::Font& font,
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_service_(GtkThemeService::GetFrom(profile)),
font_(font.DeriveFont(kEditFontAdjust)),
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);
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_service_->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);
}
bool AutocompletePopupViewGtk::IsOpen() const {
return opened_;
}
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_);
}
gfx::Rect AutocompletePopupViewGtk::GetTargetBounds() {
if (!GTK_WIDGET_REALIZED(window_))
return gfx::Rect();
gfx::Rect retval = gtk_util::GetWidgetScreenBounds(window_);
// The widget bounds don't update synchronously so may be out of sync with
// our last size request.
GtkRequisition req;
gtk_widget_size_request(window_, &req);
retval.set_width(req.width);
retval.set_height(req.height);
return retval;
}
void AutocompletePopupViewGtk::PaintUpdatesNow() {
// Paint our queued invalidations now, synchronously.
gdk_window_process_updates(window_->window, FALSE);
}
void AutocompletePopupViewGtk::OnDragCanceled() {
ignore_mouse_drag_ = true;
}
void AutocompletePopupViewGtk::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
DCHECK(type == NotificationType::BROWSER_THEME_CHANGED);
if (theme_service_->UseGtkTheme()) {
gtk_util::UndoForceFontSize(window_);
border_color_ = theme_service_->GetBorderColor();
gtk_util::GetTextColors(
&background_color_, &selected_background_color_,
&content_text_color_, &selected_content_text_color_);
hovered_background_color_ = gtk_util::AverageColors(
background_color_, selected_background_color_);
url_text_color_ = NormalURLColor(content_text_color_);
url_selected_text_color_ = SelectedURLColor(selected_content_text_color_,
selected_background_color_);
} else {
gtk_util::ForceFontSizePixels(window_, font_.GetFontSize());
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;
}
// Calculate dimmed colors.
content_dim_text_color_ =
gtk_util::AverageColors(content_text_color_,
background_color_);
selected_content_dim_text_color_ =
gtk_util::AverageColors(selected_content_text_color_,
selected_background_color_);
// 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 horizontal_offset = 1;
gtk_window_move(GTK_WINDOW(window_),
origin_x + allocation.x - kBorderThickness + horizontal_offset,
origin_y + allocation.y + allocation.height - kBorderThickness - 1 +
kVerticalOffset);
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));
ui::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);
string16 keyword;
const bool is_keyword_hint = model_->GetKeywordForMatch(match, &keyword);
edit_view_->OpenURL(url, disposition, match.transition, GURL(), line,
is_keyword_hint ? string16() : keyword);
}
GdkPixbuf* AutocompletePopupViewGtk::IconForMatch(
const AutocompleteMatch& match,
bool selected) {
const SkBitmap* bitmap = model_->GetIconIfExtensionMatch(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_EXTENSION_APP:
icon = IDR_OMNIBOX_EXTENSION_APP_DARK;
break;
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_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_service_->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, 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, 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);
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 :
(line_rect.width() - kIconLeftPadding - kIconWidth);
// 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 ? &selected_content_dim_text_color_ :
&content_dim_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 :
(text_width - actual_content_width),
content_y, layout_);
if (has_description) {
pango_layout_set_width(layout_,
(text_width - actual_content_width) * PANGO_SCALE);
// In Windows, a boolean "force_dim" is passed as true for the
// description. Here, we pass the dim text color for both normal and dim,
// to accomplish the same thing.
SetupLayoutForMatch(layout_, match.description, match.description_class,
is_selected ? &selected_content_dim_text_color_ :
&content_dim_text_color_,
is_selected ? &selected_content_dim_text_color_ :
&content_dim_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 + actual_content_width) :
(text_width - actual_content_width -
(actual_description_width / PANGO_SCALE)),
content_y, layout_);
}
}
g_object_unref(gc);
return TRUE;
}