blob: 3d648b8deb3f5bbe2feab62edbd039cfd28bb4f3 [file] [log] [blame]
// Copyright (c) 2012 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/ui/views/autofill/autofill_popup_view_views.h"
#include "chrome/browser/ui/autofill/autofill_popup_controller.h"
#include "grit/ui_resources.h"
#include "third_party/WebKit/public/web/WebAutofillClient.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/gfx/point.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/screen.h"
#include "ui/views/border.h"
#include "ui/views/event_utils.h"
#include "ui/views/widget/widget.h"
#if defined(USE_AURA)
#include "ui/views/corewm/window_animations.h"
#endif
using WebKit::WebAutofillClient;
namespace {
const SkColor kBorderColor = SkColorSetARGB(0xFF, 0xC7, 0xCA, 0xCE);
const SkColor kHoveredBackgroundColor = SkColorSetARGB(0xFF, 0xCD, 0xCD, 0xCD);
const SkColor kItemTextColor = SkColorSetARGB(0xFF, 0x7F, 0x7F, 0x7F);
const SkColor kPopupBackground = SkColorSetARGB(0xFF, 0xFF, 0xFF, 0xFF);
const SkColor kValueTextColor = SkColorSetARGB(0xFF, 0x00, 0x00, 0x00);
const SkColor kWarningTextColor = SkColorSetARGB(0xFF, 0x7F, 0x7F, 0x7F);
} // namespace
namespace autofill {
AutofillPopupViewViews::AutofillPopupViewViews(
AutofillPopupController* controller, views::Widget* observing_widget)
: controller_(controller),
observing_widget_(observing_widget) {}
AutofillPopupViewViews::~AutofillPopupViewViews() {
if (controller_) {
controller_->ViewDestroyed();
HideInternal();
}
}
void AutofillPopupViewViews::Hide() {
// The controller is no longer valid after it hides us.
controller_ = NULL;
HideInternal();
if (GetWidget()) {
// Don't call CloseNow() because some of the functions higher up the stack
// assume the the widget is still valid after this point.
// http://crbug.com/229224
// NOTE: This deletes |this|.
GetWidget()->Close();
} else {
delete this;
}
}
void AutofillPopupViewViews::OnPaint(gfx::Canvas* canvas) {
if (!controller_)
return;
canvas->DrawColor(kPopupBackground);
OnPaintBorder(canvas);
for (size_t i = 0; i < controller_->names().size(); ++i) {
gfx::Rect line_rect = controller_->GetRowBounds(i);
if (controller_->identifiers()[i] ==
WebAutofillClient::MenuItemIDSeparator) {
canvas->DrawRect(line_rect, kItemTextColor);
} else {
DrawAutofillEntry(canvas, i, line_rect);
}
}
}
void AutofillPopupViewViews::OnMouseCaptureLost() {
if (controller_)
controller_->MouseExitedPopup();
}
bool AutofillPopupViewViews::OnMouseDragged(const ui::MouseEvent& event) {
if (!controller_)
return false;
if (HitTestPoint(event.location())) {
controller_->MouseHovered(event.x(), event.y());
// We must return true in order to get future OnMouseDragged and
// OnMouseReleased events.
return true;
}
// If we move off of the popup, we lose the selection.
controller_->MouseExitedPopup();
return false;
}
void AutofillPopupViewViews::OnMouseExited(const ui::MouseEvent& event) {
if (controller_)
controller_->MouseExitedPopup();
}
void AutofillPopupViewViews::OnMouseMoved(const ui::MouseEvent& event) {
if (!controller_)
return;
if (HitTestPoint(event.location()))
controller_->MouseHovered(event.x(), event.y());
else
controller_->MouseExitedPopup();
}
bool AutofillPopupViewViews::OnMousePressed(const ui::MouseEvent& event) {
if (HitTestPoint(event.location()))
return true;
if (controller_->hide_on_outside_click()) {
GetWidget()->ReleaseCapture();
gfx::Point screen_loc = event.location();
views::View::ConvertPointToScreen(this, &screen_loc);
ui::MouseEvent mouse_event = event;
mouse_event.set_location(screen_loc);
if (controller_->ShouldRepostEvent(mouse_event)) {
gfx::NativeView native_view = GetWidget()->GetNativeView();
gfx::Screen* screen = gfx::Screen::GetScreenFor(native_view);
gfx::NativeWindow window = screen->GetWindowAtScreenPoint(screen_loc);
views::RepostLocatedEvent(window, mouse_event);
}
controller_->Hide();
// |this| is now deleted.
}
return false;
}
void AutofillPopupViewViews::OnMouseReleased(const ui::MouseEvent& event) {
if (!controller_)
return;
// Because this view can can be shown in response to a mouse press, it can
// receive an OnMouseReleased event just after showing. This breaks the mouse
// capture, so restart capturing here.
if (controller_->hide_on_outside_click() && GetWidget())
GetWidget()->SetCapture(this);
// We only care about the left click.
if (event.IsOnlyLeftMouseButton() && HitTestPoint(event.location()))
controller_->MouseClicked(event.x(), event.y());
}
void AutofillPopupViewViews::OnWidgetBoundsChanged(
views::Widget* widget,
const gfx::Rect& new_bounds) {
DCHECK_EQ(widget, observing_widget_);
controller_->Hide();
}
void AutofillPopupViewViews::Show() {
if (!GetWidget()) {
observing_widget_->AddObserver(this);
// The widget is destroyed by the corresponding NativeWidget, so we use
// a weak pointer to hold the reference and don't have to worry about
// deletion.
views::Widget* widget = new views::Widget;
views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
params.delegate = this;
params.parent = controller_->container_view();
widget->Init(params);
widget->SetContentsView(this);
#if defined(USE_AURA)
// No animation for popup appearance (too distracting).
views::corewm::SetWindowVisibilityAnimationTransition(
widget->GetNativeView(), views::corewm::ANIMATE_HIDE);
#endif
}
set_border(views::Border::CreateSolidBorder(kBorderThickness, kBorderColor));
UpdateBoundsAndRedrawPopup();
GetWidget()->Show();
if (controller_->hide_on_outside_click())
GetWidget()->SetCapture(this);
}
void AutofillPopupViewViews::InvalidateRow(size_t row) {
SchedulePaintInRect(controller_->GetRowBounds(row));
}
void AutofillPopupViewViews::UpdateBoundsAndRedrawPopup() {
GetWidget()->SetBounds(controller_->popup_bounds());
SchedulePaint();
}
void AutofillPopupViewViews::HideInternal() {
observing_widget_->RemoveObserver(this);
}
void AutofillPopupViewViews::DrawAutofillEntry(gfx::Canvas* canvas,
int index,
const gfx::Rect& entry_rect) {
if (controller_->selected_line() == index)
canvas->FillRect(entry_rect, kHoveredBackgroundColor);
bool is_rtl = controller_->IsRTL();
int value_text_width = controller_->GetNameFontForRow(index).GetStringWidth(
controller_->names()[index]);
int value_content_x = is_rtl ?
entry_rect.width() - value_text_width - kEndPadding : kEndPadding;
canvas->DrawStringInt(
controller_->names()[index],
controller_->GetNameFontForRow(index),
controller_->IsWarning(index) ? kWarningTextColor : kValueTextColor,
value_content_x,
entry_rect.y(),
canvas->GetStringWidth(controller_->names()[index],
controller_->GetNameFontForRow(index)),
entry_rect.height(),
gfx::Canvas::TEXT_ALIGN_CENTER);
// Use this to figure out where all the other Autofill items should be placed.
int x_align_left = is_rtl ? kEndPadding : entry_rect.width() - kEndPadding;
// Draw the Autofill icon, if one exists
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
int row_height = controller_->GetRowBounds(index).height();
if (!controller_->icons()[index].empty()) {
int icon = controller_->GetIconResourceID(controller_->icons()[index]);
DCHECK_NE(-1, icon);
const gfx::ImageSkia* image = rb.GetImageSkiaNamed(icon);
int icon_y = entry_rect.y() + (row_height - image->height()) / 2;
x_align_left += is_rtl ? 0 : -image->width();
canvas->DrawImageInt(*image, x_align_left, icon_y);
x_align_left += is_rtl ? image->width() + kIconPadding : -kIconPadding;
}
// Draw the name text.
if (!is_rtl) {
x_align_left -= canvas->GetStringWidth(controller_->subtexts()[index],
controller_->subtext_font());
}
canvas->DrawStringInt(
controller_->subtexts()[index],
controller_->subtext_font(),
kItemTextColor,
x_align_left,
entry_rect.y(),
canvas->GetStringWidth(controller_->subtexts()[index],
controller_->subtext_font()),
entry_rect.height(),
gfx::Canvas::TEXT_ALIGN_CENTER);
}
AutofillPopupView* AutofillPopupView::Create(
AutofillPopupController* controller) {
views::Widget* observing_widget =
views::Widget::GetTopLevelWidgetForNativeView(
controller->container_view());
// If the top level widget can't be found, cancel the popup since we can't
// fully set it up.
if (!observing_widget)
return NULL;
return new AutofillPopupViewViews(controller, observing_widget);
}
} // namespace autofill