#include "ui/views/touchui/touch_selection_controller_impl.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "grit/ui_strings.h"
#include "third_party/skia/include/effects/SkGradientShader.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/screen.h"
#include "ui/gfx/size.h"
#include "ui/gfx/transform.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/custom_button.h"
#include "ui/views/controls/button/text_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/menu/menu_config.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/widget/widget.h"
namespace {
const int kSelectionHandleRadius = 10;
const int kSelectionHandleCursorHeight = 10;
const int kSelectionHandleAlpha = 0x7F;
const SkColor kSelectionHandleColor =
SkColorSetA(SK_ColorBLUE, kSelectionHandleAlpha);
const int kMinSelectionSize = 4;
const int kContextMenuCommands[] = {IDS_APP_CUT,
IDS_APP_COPY,
IDS_APP_DELETE,
IDS_APP_SELECT_ALL};
const int kContextMenuPadding = 2;
const int kContextMenuTimoutMs = 1000;
const int kContextMenuVerticalOffset = 25;
struct Circle {
int radius;
gfx::Point center;
SkColor color;
};
views::Widget* CreateTouchSelectionPopupWidget() {
views::Widget* widget = new views::Widget;
views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
params.can_activate = false;
params.transparent = true;
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget->Init(params);
return widget;
}
void PaintCircle(const Circle& circle, gfx::Canvas* canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kFill_Style);
paint.setColor(circle.color);
canvas->DrawCircle(circle.center, circle.radius, paint);
}
bool IsEmptySelection(const gfx::Point& p1, const gfx::Point& p2) {
int delta_x = p2.x() - p1.x();
int delta_y = p2.y() - p1.y();
return (abs(delta_x) < kMinSelectionSize && abs(delta_y) < kMinSelectionSize);
}
}
namespace views {
class TouchSelectionControllerImpl::SelectionHandleView : public View {
public:
explicit SelectionHandleView(TouchSelectionControllerImpl* controller)
: controller_(controller) {
widget_.reset(CreateTouchSelectionPopupWidget());
widget_->SetContentsView(this);
widget_->SetAlwaysOnTop(true);
set_owned_by_client();
}
virtual ~SelectionHandleView() {
}
virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
Circle circle = {kSelectionHandleRadius, gfx::Point(kSelectionHandleRadius,
kSelectionHandleRadius + kSelectionHandleCursorHeight),
kSelectionHandleColor};
PaintCircle(circle, canvas);
canvas->DrawLine(gfx::Point(kSelectionHandleRadius, 0),
gfx::Point(kSelectionHandleRadius, kSelectionHandleCursorHeight),
kSelectionHandleColor);
}
virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE {
controller_->dragging_handle_ = this;
return true;
}
virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE {
controller_->SelectionHandleDragged(event.location());
return true;
}
virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE {
controller_->dragging_handle_ = NULL;
}
virtual void OnMouseCaptureLost() OVERRIDE {
controller_->dragging_handle_ = NULL;
}
virtual void SetVisible(bool visible) OVERRIDE {
if (visible != widget_->IsVisible()) {
if (visible)
widget_->Show();
else
widget_->Hide();
}
View::SetVisible(visible);
}
virtual gfx::Size GetPreferredSize() OVERRIDE {
return gfx::Size(2 * kSelectionHandleRadius,
2 * kSelectionHandleRadius + kSelectionHandleCursorHeight);
}
void SetScreenPosition(const gfx::Point& position) {
gfx::Rect widget_bounds(position.x() - kSelectionHandleRadius, position.y(),
2 * kSelectionHandleRadius,
2 * kSelectionHandleRadius + kSelectionHandleCursorHeight);
widget_->SetBounds(widget_bounds);
}
gfx::Point GetScreenPosition() {
return widget_->GetClientAreaBoundsInScreen().origin();
}
private:
scoped_ptr<Widget> widget_;
TouchSelectionControllerImpl* controller_;
DISALLOW_COPY_AND_ASSIGN(SelectionHandleView);
};
class ContextMenuButtonBackground : public Background {
public:
ContextMenuButtonBackground() {}
virtual void Paint(gfx::Canvas* canvas, View* view) const OVERRIDE {
CustomButton::ButtonState state = static_cast<CustomButton*>(view)->state();
SkColor background_color, border_color;
if (state == CustomButton::BS_NORMAL) {
background_color = SkColorSetARGB(102, 255, 255, 255);
border_color = SkColorSetARGB(36, 0, 0, 0);
} else {
background_color = SkColorSetARGB(13, 0, 0, 0);
border_color = SkColorSetARGB(72, 0, 0, 0);
}
int w = view->width();
int h = view->height();
canvas->FillRect(gfx::Rect(1, 1, w - 2, h - 2), background_color);
canvas->FillRect(gfx::Rect(2, 0, w - 4, 1), border_color);
canvas->FillRect(gfx::Rect(1, 1, 1, 1), border_color);
canvas->FillRect(gfx::Rect(0, 2, 1, h - 4), border_color);
canvas->FillRect(gfx::Rect(1, h - 2, 1, 1), border_color);
canvas->FillRect(gfx::Rect(2, h - 1, w - 4, 1), border_color);
canvas->FillRect(gfx::Rect(w - 2, 1, 1, 1), border_color);
canvas->FillRect(gfx::Rect(w - 1, 2, 1, h - 4), border_color);
canvas->FillRect(gfx::Rect(w - 2, h - 2, 1, 1), border_color);
}
private:
DISALLOW_COPY_AND_ASSIGN(ContextMenuButtonBackground);
};
class TouchSelectionControllerImpl::TouchContextMenuView
: public ButtonListener,
public View {
public:
explicit TouchContextMenuView(TouchSelectionControllerImpl* controller)
: controller_(controller) {
widget_.reset(CreateTouchSelectionPopupWidget());
widget_->SetContentsView(this);
widget_->SetAlwaysOnTop(true);
set_owned_by_client();
SetLayoutManager(new BoxLayout(BoxLayout::kHorizontal, kContextMenuPadding,
kContextMenuPadding, kContextMenuPadding));
}
virtual ~TouchContextMenuView() {
}
virtual void SetVisible(bool visible) OVERRIDE {
if (visible != widget_->IsVisible()) {
if (visible)
widget_->Show();
else
widget_->Hide();
}
View::SetVisible(visible);
}
void SetScreenPosition(const gfx::Point& position) {
RefreshButtonsAndSetWidgetPosition(position);
}
gfx::Point GetScreenPosition() {
return widget_->GetClientAreaBoundsInScreen().origin();
}
void OnPaintBackground(gfx::Canvas* canvas) OVERRIDE {
#if defined(OS_CHROMEOS)
static const SkColor kGradientColors[2] = {
SK_ColorWHITE,
SkColorSetRGB(0xF0, 0xF0, 0xF0)
};
static const SkScalar kGradientPoints[2] = {
SkIntToScalar(0),
SkIntToScalar(1)
};
SkPoint points[2];
points[0].iset(0, 0);
points[1].iset(0, height());
SkShader* shader = SkGradientShader::CreateLinear(points,
kGradientColors, kGradientPoints, arraysize(kGradientPoints),
SkShader::kRepeat_TileMode);
DCHECK(shader);
SkPaint paint;
paint.setShader(shader);
shader->unref();
paint.setStyle(SkPaint::kFill_Style);
paint.setXfermodeMode(SkXfermode::kSrc_Mode);
canvas->DrawRect(GetLocalBounds(), paint);
#else
canvas->DrawColor(SkColorSetRGB(210, 225, 246),
SkXfermode::kSrc_Mode);
#endif
}
virtual void ButtonPressed(Button* sender, const ui::Event& event) OVERRIDE {
controller_->ExecuteCommand(sender->tag());
}
private:
void RefreshButtonsAndSetWidgetPosition(const gfx::Point& position) {
RemoveAllChildViews(true);
int total_width = 0;
int height = 0;
for (size_t i = 0; i < arraysize(kContextMenuCommands); i++) {
int command_id = kContextMenuCommands[i];
if (controller_->IsCommandIdEnabled(command_id)) {
TextButton* button = new TextButton(
this, l10n_util::GetStringUTF16(command_id));
button->set_focusable(true);
button->set_request_focus_on_press(false);
button->set_prefix_type(TextButton::PREFIX_HIDE);
button->SetEnabledColor(MenuConfig::instance().text_color);
button->set_background(new ContextMenuButtonBackground());
button->set_alignment(TextButton::ALIGN_CENTER);
button->SetFont(ui::ResourceBundle::GetSharedInstance().GetFont(
ui::ResourceBundle::LargeFont));
button->set_tag(command_id);
AddChildView(button);
gfx::Size button_size = button->GetPreferredSize();
total_width += button_size.width() + kContextMenuPadding;
if (height < button_size.height())
height = button_size.height();
}
}
gfx::Rect widget_bounds(position.x() - total_width / 2,
position.y() - height,
total_width,
height);
gfx::Rect monitor_bounds =
gfx::Screen::GetDisplayNearestPoint(position).bounds();
widget_->SetBounds(widget_bounds.AdjustToFit(monitor_bounds));
Layout();
}
scoped_ptr<Widget> widget_;
TouchSelectionControllerImpl* controller_;
DISALLOW_COPY_AND_ASSIGN(TouchContextMenuView);
};
TouchSelectionControllerImpl::TouchSelectionControllerImpl(
TouchSelectionClientView* client_view)
: client_view_(client_view),
selection_handle_1_(new SelectionHandleView(this)),
selection_handle_2_(new SelectionHandleView(this)),
context_menu_(new TouchContextMenuView(this)),
dragging_handle_(NULL) {
}
TouchSelectionControllerImpl::~TouchSelectionControllerImpl() {
}
void TouchSelectionControllerImpl::SelectionChanged(const gfx::Point& p1,
const gfx::Point& p2) {
gfx::Point screen_pos_1(p1);
View::ConvertPointToScreen(client_view_, &screen_pos_1);
gfx::Point screen_pos_2(p2);
View::ConvertPointToScreen(client_view_, &screen_pos_2);
if (dragging_handle_) {
dragging_handle_->SetScreenPosition(screen_pos_2);
} else {
UpdateContextMenu(p1, p2);
if (IsEmptySelection(screen_pos_2, screen_pos_1)) {
selection_handle_1_->SetVisible(false);
selection_handle_2_->SetVisible(false);
return;
}
if (client_view_->bounds().Contains(p1)) {
selection_handle_1_->SetScreenPosition(screen_pos_1);
selection_handle_1_->SetVisible(true);
} else {
selection_handle_1_->SetVisible(false);
}
if (client_view_->bounds().Contains(p2)) {
selection_handle_2_->SetScreenPosition(screen_pos_2);
selection_handle_2_->SetVisible(true);
} else {
selection_handle_2_->SetVisible(false);
}
}
}
void TouchSelectionControllerImpl::ClientViewLostFocus() {
selection_handle_1_->SetVisible(false);
selection_handle_2_->SetVisible(false);
HideContextMenu();
}
void TouchSelectionControllerImpl::SelectionHandleDragged(
const gfx::Point& drag_pos) {
HideContextMenu();
context_menu_timer_.Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kContextMenuTimoutMs),
this,
&TouchSelectionControllerImpl::ContextMenuTimerFired);
if (client_view_->GetWidget()) {
DCHECK(dragging_handle_);
SelectionHandleView* fixed_handle = selection_handle_1_.get();
if (fixed_handle == dragging_handle_)
fixed_handle = selection_handle_2_.get();
gfx::Point p1(drag_pos.x() + kSelectionHandleRadius, drag_pos.y());
ConvertPointToClientView(dragging_handle_, &p1);
gfx::Point p2(kSelectionHandleRadius, 0);
ConvertPointToClientView(fixed_handle, &p2);
client_view_->SelectRect(p2, p1);
}
}
void TouchSelectionControllerImpl::ConvertPointToClientView(
SelectionHandleView* source, gfx::Point* point) {
View::ConvertPointToScreen(source, point);
gfx::Rect r = client_view_->GetWidget()->GetClientAreaBoundsInScreen();
point->SetPoint(point->x() - r.x(), point->y() - r.y());
View::ConvertPointFromWidget(client_view_, point);
}
bool TouchSelectionControllerImpl::IsCommandIdEnabled(int command_id) const {
return client_view_->IsCommandIdEnabled(command_id);
}
void TouchSelectionControllerImpl::ExecuteCommand(int command_id) {
HideContextMenu();
client_view_->ExecuteCommand(command_id);
}
void TouchSelectionControllerImpl::ContextMenuTimerFired() {
gfx::Point p1(kSelectionHandleRadius, 0);
ConvertPointToClientView(selection_handle_1_.get(), &p1);
gfx::Point p2(kSelectionHandleRadius, 0);
ConvertPointToClientView(selection_handle_2_.get(), &p2);
gfx::Point menu_pos;
if (client_view_->bounds().Contains(p1) &&
client_view_->bounds().Contains(p2)) {
menu_pos.set_x((p1.x() + p2.x()) / 2);
menu_pos.set_y(std::min(p1.y(), p2.y()) - kContextMenuVerticalOffset);
} else {
menu_pos.set_x(client_view_->x() + client_view_->width() / 2);
menu_pos.set_y(client_view_->y());
}
View::ConvertPointToScreen(client_view_, &menu_pos);
context_menu_->SetScreenPosition(menu_pos);
context_menu_->SetVisible(true);
}
void TouchSelectionControllerImpl::UpdateContextMenu(const gfx::Point& p1,
const gfx::Point& p2) {
HideContextMenu();
if (!IsEmptySelection(p1, p2)) {
context_menu_timer_.Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kContextMenuTimoutMs),
this,
&TouchSelectionControllerImpl::ContextMenuTimerFired);
}
}
void TouchSelectionControllerImpl::HideContextMenu() {
context_menu_->SetVisible(false);
context_menu_timer_.Stop();
}
gfx::Point TouchSelectionControllerImpl::GetSelectionHandle1Position() {
return selection_handle_1_->GetScreenPosition();
}
gfx::Point TouchSelectionControllerImpl::GetSelectionHandle2Position() {
return selection_handle_2_->GetScreenPosition();
}
bool TouchSelectionControllerImpl::IsSelectionHandle1Visible() {
return selection_handle_1_->visible();
}
bool TouchSelectionControllerImpl::IsSelectionHandle2Visible() {
return selection_handle_2_->visible();
}
TouchSelectionController* TouchSelectionController::create(
TouchSelectionClientView* client_view) {
return new TouchSelectionControllerImpl(client_view);
}
}