This source file includes following definitions.
- GetWindowRect
- GetUTF8Offset
- NormalURLColor
- SelectedURLColor
- opened_
- Init
- IsOpen
- InvalidateLine
- UpdatePopupAppearance
- GetTargetBounds
- PaintUpdatesNow
- OnDragCanceled
- Observe
- LineFromY
- GetRectForLine
- GetHiddenMatchCount
- GetResult
- SetupLayoutForMatch
- Show
- Hide
- StackWindow
- AcceptLine
- IconForMatch
- GetVisibleMatchForInput
- HandleMotion
- HandleButtonPress
- HandleButtonRelease
- HandleExpose
#include "chrome/browser/ui/gtk/omnibox/omnibox_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.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/autocomplete/autocomplete_match.h"
#include "chrome/browser/autocomplete/autocomplete_result.h"
#include "chrome/browser/chrome_notification_types.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_service.h"
#include "chrome/browser/ui/gtk/gtk_theme_service.h"
#include "chrome/browser/ui/gtk/gtk_util.h"
#include "chrome/browser/ui/omnibox/omnibox_edit_model.h"
#include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
#include "chrome/browser/ui/omnibox/omnibox_view.h"
#include "content/public/browser/notification_source.h"
#include "grit/theme_resources.h"
#include "ui/base/gtk/gtk_hig_constants.h"
#include "ui/base/gtk/gtk_screen_util.h"
#include "ui/base/gtk/gtk_windowing.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/gtk_compat.h"
#include "ui/gfx/gtk_util.h"
#include "ui/gfx/image/cairo_cached_surface.h"
#include "ui/gfx/image/image.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);
const int kBorderThickness = 1;
const int kHeightPerResult = 24;
const int kIconWidth = 17;
const int kIconTopPadding = 2;
const int kIconLeftPadding = 3 + kBorderThickness;
const int kIconRightPadding = 5;
const int kIconAreaWidth =
kIconLeftPadding + kIconWidth + kIconRightPadding;
const int kRightPadding = 3;
const float kContentWidthPercentage = 0.7;
const int kVerticalOffset = 3;
const int kEditFontAdjust = -1;
const char* kLRE = "\xe2\x80\xaa";
gfx::Rect GetWindowRect(GdkWindow* window) {
gint width = gdk_window_get_width(window);
gint height = gdk_window_get_height(window);
return gfx::Rect(width, height);
}
size_t GetUTF8Offset(const base::string16& text, size_t text_offset) {
return base::UTF16ToUTF8(text.substr(0, text_offset)).length();
}
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);
double s = std::max(0.5, fg_hsl.s);
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));
}
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);
double opposite_s = 1 - bg_hsl.s;
double s = std::max(0.2, std::min(0.8, opposite_s));
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));
}
}
OmniboxPopupViewGtk::OmniboxPopupViewGtk(const gfx::Font& font,
OmniboxView* omnibox_view,
OmniboxEditModel* edit_model,
GtkWidget* location_bar)
: omnibox_view_(omnibox_view),
location_bar_(location_bar),
window_(gtk_window_new(GTK_WINDOW_POPUP)),
layout_(NULL),
theme_service_(NULL),
font_(font.Derive(kEditFontAdjust, font.GetStyle())),
ignore_mouse_drag_(false),
opened_(false) {
if (edit_model) {
model_.reset(new OmniboxPopupModel(this, edit_model));
theme_service_ = GtkThemeService::GetFrom(edit_model->profile());
}
}
void OmniboxPopupViewGtk::Init() {
gtk_widget_set_can_focus(window_, FALSE);
gtk_window_set_resizable(GTK_WINDOW(window_), FALSE);
gtk_widget_set_app_paintable(window_, TRUE);
gtk_widget_set_double_buffered(window_, TRUE);
layout_ = gtk_widget_create_pango_layout(window_, NULL);
pango_layout_set_auto_dir(layout_, FALSE);
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,
chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
content::Source<ThemeService>(theme_service_));
theme_service_->InitThemesFor(this);
}
OmniboxPopupViewGtk::~OmniboxPopupViewGtk() {
model_.reset();
if (layout_) {
g_object_unref(layout_);
gtk_widget_destroy(window_);
}
}
bool OmniboxPopupViewGtk::IsOpen() const {
return opened_;
}
void OmniboxPopupViewGtk::InvalidateLine(size_t line) {
if (line < GetHiddenMatchCount())
return;
GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_));
GdkRectangle line_rect = GetRectForLine(
line, GetWindowRect(gdk_window).width()).ToGdkRectangle();
gdk_window_invalidate_rect(gdk_window, &line_rect, FALSE);
}
void OmniboxPopupViewGtk::UpdatePopupAppearance() {
const AutocompleteResult& result = GetResult();
const size_t hidden_matches = GetHiddenMatchCount();
if (result.size() <= hidden_matches) {
Hide();
return;
}
Show(result.size() - hidden_matches);
gtk_widget_queue_draw(window_);
}
gfx::Rect OmniboxPopupViewGtk::GetTargetBounds() {
if (!gtk_widget_get_realized(window_))
return gfx::Rect();
gfx::Rect retval = ui::GetWidgetScreenBounds(window_);
GtkRequisition req;
gtk_widget_size_request(window_, &req);
retval.set_width(req.width);
retval.set_height(req.height);
return retval;
}
void OmniboxPopupViewGtk::PaintUpdatesNow() {
GdkWindow* gdk_window = gtk_widget_get_window(window_);
gdk_window_process_updates(gdk_window, FALSE);
}
void OmniboxPopupViewGtk::OnDragCanceled() {
ignore_mouse_drag_ = true;
}
void OmniboxPopupViewGtk::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK(type == chrome::NOTIFICATION_BROWSER_THEME_CHANGED);
if (theme_service_->UsingNativeTheme()) {
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;
}
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_);
gtk_widget_modify_bg(window_, GTK_STATE_NORMAL, &background_color_);
}
size_t OmniboxPopupViewGtk::LineFromY(int y) const {
if (model_)
DCHECK_NE(0U, model_->result().size());
size_t line = std::max(y - kBorderThickness, 0) / kHeightPerResult;
return std::min(line + GetHiddenMatchCount(), GetResult().size() - 1);
}
gfx::Rect OmniboxPopupViewGtk::GetRectForLine(size_t line, int width) const {
size_t visible_line = line - GetHiddenMatchCount();
return gfx::Rect(kBorderThickness,
(visible_line * kHeightPerResult) + kBorderThickness,
width - (kBorderThickness * 2),
kHeightPerResult);
}
size_t OmniboxPopupViewGtk::GetHiddenMatchCount() const {
return GetResult().ShouldHideTopMatch() ? 1 : 0;
}
const AutocompleteResult& OmniboxPopupViewGtk::GetResult() const {
return model_->result();
}
void OmniboxPopupViewGtk::SetupLayoutForMatch(
PangoLayout* layout,
const base::string16& text,
const AutocompleteMatch::ACMatchClassifications& classifications,
const GdkColor* base_color,
const GdkColor* dim_color,
const GdkColor* url_color,
const std::string& prefix_text) {
bool marked_with_lre = false;
base::string16 localized_text = text;
if (localized_text.length() > 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;
}
size_t additional_offset = prefix_text.length();
std::string text_utf8 = prefix_text + base::UTF16ToUTF8(localized_text);
PangoAttrList* attrs = pango_attr_list_new();
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);
for (ACMatchClassifications::const_iterator i = classifications.begin();
i != classifications.end(); ++i) {
size_t offset = GetUTF8Offset(localized_text, i->offset) +
additional_offset;
const GdkColor* color = base_color;
if (i->style & ACMatchClassification::URL) {
color = url_color;
if (is_rtl && !marked_with_lre) {
std::string lre(kLRE);
text_utf8.insert(offset, lre);
additional_offset += lre.length();
}
}
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);
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);
}
pango_layout_set_text(layout, text_utf8.data(), text_utf8.length());
pango_layout_set_attributes(layout, attrs);
pango_attr_list_unref(attrs);
}
void OmniboxPopupViewGtk::Show(size_t num_results) {
gint origin_x, origin_y;
GdkWindow* gdk_window = gtk_widget_get_window(location_bar_);
gdk_window_get_origin(gdk_window, &origin_x, &origin_y);
GtkAllocation allocation;
gtk_widget_get_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 OmniboxPopupViewGtk::Hide() {
gtk_widget_hide(window_);
opened_ = false;
}
void OmniboxPopupViewGtk::StackWindow() {
gfx::NativeView omnibox_view = omnibox_view_->GetNativeView();
DCHECK(GTK_IS_WIDGET(omnibox_view));
GtkWidget* toplevel = gtk_widget_get_toplevel(omnibox_view);
DCHECK(gtk_widget_is_toplevel(toplevel));
ui::StackPopupWindow(window_, toplevel);
}
void OmniboxPopupViewGtk::AcceptLine(size_t line,
WindowOpenDisposition disposition) {
omnibox_view_->OpenMatch(GetResult().match_at(line), disposition, GURL(),
base::string16(), line);
}
gfx::Image OmniboxPopupViewGtk::IconForMatch(
const AutocompleteMatch& match,
bool selected,
bool is_selected_keyword) {
const gfx::Image image = model_->GetIconIfExtensionMatch(match);
if (!image.IsEmpty())
return image;
int icon;
if (is_selected_keyword)
icon = IDR_OMNIBOX_TTS;
else if (match.starred)
icon = IDR_OMNIBOX_STAR;
else
icon = 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_SEARCH:
icon = IDR_OMNIBOX_SEARCH_DARK;
break;
case IDR_OMNIBOX_STAR:
icon = IDR_OMNIBOX_STAR_DARK;
break;
case IDR_OMNIBOX_TTS:
icon = IDR_OMNIBOX_TTS_DARK;
break;
default:
NOTREACHED();
break;
}
}
return theme_service_->GetImageNamed(icon);
}
void OmniboxPopupViewGtk::GetVisibleMatchForInput(
size_t index,
const AutocompleteMatch** match,
bool* is_selected_keyword) {
const AutocompleteResult& result = GetResult();
if (result.match_at(index).associated_keyword.get() &&
model_->selected_line() == index &&
model_->selected_line_state() == OmniboxPopupModel::KEYWORD) {
*match = result.match_at(index).associated_keyword.get();
*is_selected_keyword = true;
return;
}
*match = &result.match_at(index);
*is_selected_keyword = false;
}
gboolean OmniboxPopupViewGtk::HandleMotion(GtkWidget* widget,
GdkEventMotion* event) {
if (!IsOpen())
return FALSE;
size_t line = LineFromY(static_cast<int>(event->y));
model_->SetHoveredLine(line);
if (!ignore_mouse_drag_ && (event->state & GDK_BUTTON1_MASK))
model_->SetSelectedLine(line, false, false);
return TRUE;
}
gboolean OmniboxPopupViewGtk::HandleButtonPress(GtkWidget* widget,
GdkEventButton* event) {
if (!IsOpen())
return FALSE;
ignore_mouse_drag_ = false;
size_t line = LineFromY(static_cast<int>(event->y));
model_->SetHoveredLine(line);
if (event->button == 1)
model_->SetSelectedLine(line, false, false);
return TRUE;
}
gboolean OmniboxPopupViewGtk::HandleButtonRelease(GtkWidget* widget,
GdkEventButton* event) {
if (!IsOpen())
return FALSE;
if (ignore_mouse_drag_) {
ignore_mouse_drag_ = false;
return TRUE;
}
size_t line = LineFromY(static_cast<int>(event->y));
switch (event->button) {
case 1:
AcceptLine(line, CURRENT_TAB);
break;
case 2:
AcceptLine(line, NEW_BACKGROUND_TAB);
break;
default:
break;
}
return TRUE;
}
gboolean OmniboxPopupViewGtk::HandleExpose(GtkWidget* widget,
GdkEventExpose* event) {
bool ltr = !base::i18n::IsRTL();
const AutocompleteResult& result = GetResult();
gfx::Rect window_rect = GetWindowRect(event->window);
gfx::Rect damage_rect = gfx::Rect(event->area);
if (window_rect.width() < (kIconAreaWidth * 3))
return TRUE;
cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget));
gdk_cairo_rectangle(cr, &event->area);
cairo_clip(cr);
COMPILE_ASSERT(kBorderThickness == 1, border_1px_implied);
gdk_cairo_set_source_color(cr, &border_color_);
cairo_rectangle(cr, 0, 0, window_rect.width(), window_rect.height());
cairo_stroke(cr);
pango_layout_set_height(layout_, kHeightPerResult * PANGO_SCALE);
for (size_t i = GetHiddenMatchCount(); i < result.size(); ++i) {
gfx::Rect line_rect = GetRectForLine(i, window_rect.width());
if (!line_rect.Intersects(damage_rect))
continue;
const AutocompleteMatch* match = NULL;
bool is_selected_keyword = false;
GetVisibleMatchForInput(i, &match, &is_selected_keyword);
bool is_selected = (model_->selected_line() == i);
bool is_hovered = (model_->hovered_line() == i);
if (is_selected || is_hovered) {
gdk_cairo_set_source_color(cr, is_selected ? &selected_background_color_ :
&hovered_background_color_);
cairo_rectangle(cr, line_rect.x(), line_rect.y(),
line_rect.width(), line_rect.height());
cairo_fill(cr);
}
int icon_start_x = ltr ? kIconLeftPadding :
(line_rect.width() - kIconLeftPadding - kIconWidth);
gtk_util::DrawFullImage(cr, widget,
IconForMatch(*match, is_selected,
is_selected_keyword),
icon_start_x, line_rect.y() + kIconTopPadding);
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);
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;
int content_y = std::max(line_rect.y(),
line_rect.y() + ((kHeightPerResult - actual_content_height) / 2));
cairo_save(cr);
cairo_move_to(cr,
ltr ? kIconAreaWidth :
(text_width - actual_content_width),
content_y);
pango_cairo_show_layout(cr, layout_);
cairo_restore(cr);
if (has_description) {
pango_layout_set_width(layout_,
(text_width - actual_content_width) * PANGO_SCALE);
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);
cairo_save(cr);
cairo_move_to(cr, ltr ?
(kIconAreaWidth + actual_content_width) :
(text_width - actual_content_width -
(actual_description_width / PANGO_SCALE)),
content_y);
pango_cairo_show_layout(cr, layout_);
cairo_restore(cr);
}
if (match->associated_keyword.get()) {
icon_start_x = ltr ? (line_rect.width() - kIconLeftPadding - kIconWidth) :
kIconLeftPadding;
gtk_util::DrawFullImage(cr, widget,
theme_service_->GetImageNamed(
is_selected ? IDR_OMNIBOX_TTS_DARK :
IDR_OMNIBOX_TTS),
icon_start_x, line_rect.y() + kIconTopPadding);
}
}
cairo_destroy(cr);
return TRUE;
}