Enable touch editing controller for views textfields behind a flag.

BUG=115237


Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=182542

Review URL: https://chromiumcodereview.appspot.com/12210154

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@182841 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/ui/base/touch/touch_editing_controller.cc b/ui/base/touch/touch_editing_controller.cc
index 8afb740..1b84ed9 100644
--- a/ui/base/touch/touch_editing_controller.cc
+++ b/ui/base/touch/touch_editing_controller.cc
@@ -6,9 +6,21 @@
 
 namespace ui {
 
+namespace {
+TouchSelectionControllerFactory* g_shared_instance = NULL;
+}  // namespace
+
 TouchSelectionController* TouchSelectionController::create(
     TouchEditable* client_view) {
+  if (g_shared_instance)
+    return g_shared_instance->create(client_view);
   return NULL;
 }
 
+// static
+void TouchSelectionControllerFactory::SetInstance(
+    TouchSelectionControllerFactory* instance) {
+  g_shared_instance = instance;
+}
+
 }  // namespace ui
diff --git a/ui/base/touch/touch_editing_controller.h b/ui/base/touch/touch_editing_controller.h
index 999c2ad3..e7985ce3 100644
--- a/ui/base/touch/touch_editing_controller.h
+++ b/ui/base/touch/touch_editing_controller.h
@@ -20,6 +20,17 @@
   // end of selection. Visually, |start| may lie after |end|.
   virtual void SelectRect(const gfx::Point& start, const gfx::Point& end) = 0;
 
+  // Gets the end points of the current selection. The end points p1 and p2 must
+  // be the cursor rect for the start and end of selection:
+  // ____________________________________
+  // | textfield with |selected text|   |
+  // ------------------------------------
+  //                  ^p1           ^p2
+  //
+  // p1 should be the logical start and p2 the logical end of selection. Hence,
+  // visually, p1 could be to the right of p2 in the figure above.
+  virtual void GetSelectionEndPoints(gfx::Rect* p1, gfx::Rect* p2) = 0;
+
   // Gets the bounds of the client view in parent's coordinates.
   virtual const gfx::Rect& GetBounds() = 0;
 
@@ -30,6 +41,13 @@
   virtual void ConvertPointToScreen(gfx::Point* point) = 0;
   virtual void ConvertPointFromScreen(gfx::Point* point) = 0;
 
+  // Returns true if the editable draws its own handles (hence, the
+  // TouchSelectionController need not draw handles).
+  virtual bool DrawsHandles() = 0;
+
+  // Tells the editable to open context menu.
+  virtual void OpenContextMenu(const gfx::Point anchor) = 0;
+
  protected:
   virtual ~TouchEditable() {}
 };
@@ -44,19 +62,18 @@
   static TouchSelectionController* create(
       TouchEditable* client_view);
 
-  // Notification that the text selection in TouchEditable has
-  // changed. p1 and p2 are lower corners of the start and end of selection:
-  // ____________________________________
-  // | textfield with |selected text|   |
-  // ------------------------------------
-  //                  ^p1           ^p2
-  //
-  // p1 is always the start and p2 is always the end of selection. Hence,
-  // p1 could be to the right of p2 in the figure above.
-  virtual void SelectionChanged(const gfx::Point& p1, const gfx::Point& p2) = 0;
+  // Notifies the controller that the selection has changed.
+  virtual void SelectionChanged() = 0;
+};
 
-  // Notification that the TouchEditable has lost focus.
-  virtual void ClientViewLostFocus() = 0;
+class UI_EXPORT TouchSelectionControllerFactory {
+ public:
+  static void SetInstance(TouchSelectionControllerFactory* instance);
+
+  virtual TouchSelectionController* create(TouchEditable* client_view) = 0;
+
+ protected:
+  virtual ~TouchSelectionControllerFactory() {}
 };
 
 }  // namespace views
diff --git a/ui/base/ui_base_switches.cc b/ui/base/ui_base_switches.cc
index e647ac3..b4fdabf 100644
--- a/ui/base/ui_base_switches.cc
+++ b/ui/base/ui_base_switches.cc
@@ -20,6 +20,9 @@
 
 const char kEnableTouchDragDrop[] = "enable-touch-drag-drop";
 
+// Enables controls that support touch base text editing.
+const char kEnableTouchEditing[] = "enable-touch-editing";
+
 // Enables the Views textfield on Windows.
 const char kEnableViewsTextfield[] = "enable-views-textfield";
 
diff --git a/ui/base/ui_base_switches.h b/ui/base/ui_base_switches.h
index 14bdebc44..b21e2b1 100644
--- a/ui/base/ui_base_switches.h
+++ b/ui/base/ui_base_switches.h
@@ -17,6 +17,7 @@
 UI_EXPORT extern const char kEnableBezelTouch[];
 UI_EXPORT extern const char kEnableNewDialogStyle[];
 UI_EXPORT extern const char kEnableTouchDragDrop[];
+UI_EXPORT extern const char kEnableTouchEditing[];
 UI_EXPORT extern const char kEnableViewsTextfield[];
 UI_EXPORT extern const char kForceDeviceScaleFactor[];
 UI_EXPORT extern const char kHighlightMissingScaledResources[];
diff --git a/ui/views/controls/textfield/native_textfield_views.cc b/ui/views/controls/textfield/native_textfield_views.cc
index 08ba3fff..da41dc6 100644
--- a/ui/views/controls/textfield/native_textfield_views.cc
+++ b/ui/views/controls/textfield/native_textfield_views.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright (c) 2013 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.
 
@@ -73,8 +73,7 @@
       initiating_drag_(false),
       ALLOW_THIS_IN_INITIALIZER_LIST(cursor_timer_(this)),
       aggregated_clicks_(0),
-      ALLOW_THIS_IN_INITIALIZER_LIST(touch_selection_controller_(
-          ui::TouchSelectionController::create(this))) {
+      touch_selection_controller_(NULL) {
   set_border(text_border_);
 
 #if defined(OS_CHROMEOS)
@@ -108,6 +107,7 @@
   }
 
   OnAfterUserAction();
+  touch_selection_controller_.reset();
   return true;
 }
 
@@ -173,6 +173,12 @@
       OnAfterUserAction();
       event->SetHandled();
       break;
+    case ui::ET_GESTURE_TAP:
+      touch_selection_controller_.reset(
+          ui::TouchSelectionController::create(this));
+      if (touch_selection_controller_.get())
+        touch_selection_controller_->SelectionChanged();
+      break;
     default:
       View::OnGestureEvent(event);
       return;
@@ -316,6 +322,16 @@
   OnAfterUserAction();
 }
 
+void NativeTextfieldViews::GetSelectionEndPoints(gfx::Rect* p1,
+                                                 gfx::Rect* p2) {
+  gfx::RenderText* render_text = GetRenderText();
+  const gfx::SelectionModel& sel = render_text->selection_model();
+  gfx::SelectionModel start_sel =
+      render_text->GetSelectionModelForSelectionStart();
+  *p1 = render_text->GetCursorBounds(start_sel, true);
+  *p2 = render_text->GetCursorBounds(sel, true);
+}
+
 const gfx::Rect& NativeTextfieldViews::GetBounds() {
   return bounds();
 }
@@ -332,6 +348,15 @@
   View::ConvertPointFromScreen(this, point);
 }
 
+bool NativeTextfieldViews::DrawsHandles() {
+  return false;
+}
+
+void NativeTextfieldViews::OpenContextMenu(const gfx::Point anchor) {
+  touch_selection_controller_.reset();
+  ShowContextMenu(anchor, false);
+}
+
 gfx::NativeCursor NativeTextfieldViews::GetCursor(const ui::MouseEvent& event) {
   bool in_selection = GetRenderText()->IsPointInSelection(event.location());
   bool drag_event = event.type() == ui::ET_MOUSE_DRAGGED;
@@ -580,6 +605,7 @@
   bool handled = false;
   if (controller)
     handled = controller->HandleKeyEvent(textfield_, e);
+  touch_selection_controller_.reset();
   return handled || HandleKeyEvent(e);
 }
 
@@ -609,8 +635,7 @@
     RepaintCursor();
   }
 
-  if (touch_selection_controller_.get())
-    touch_selection_controller_->ClientViewLostFocus();
+  touch_selection_controller_.reset();
 
   ClearSelection();
 }
@@ -679,6 +704,7 @@
 }
 
 void NativeTextfieldViews::ExecuteCommand(int command_id) {
+  touch_selection_controller_.reset();
   if (!IsCommandIdEnabled(command_id))
     return;
 
@@ -1194,17 +1220,8 @@
     textfield_->GetInputMethod()->OnCaretBoundsChanged(textfield_);
 
   // Notify selection controller
-  if (!touch_selection_controller_.get())
-    return;
-  gfx::RenderText* render_text = GetRenderText();
-  const gfx::SelectionModel& sel = render_text->selection_model();
-  gfx::SelectionModel start_sel =
-      render_text->GetSelectionModelForSelectionStart();
-  gfx::Rect start_cursor = render_text->GetCursorBounds(start_sel, true);
-  gfx::Rect end_cursor = render_text->GetCursorBounds(sel, true);
-  gfx::Point start(start_cursor.x(), start_cursor.bottom() - 1);
-  gfx::Point end(end_cursor.x(), end_cursor.bottom() - 1);
-  touch_selection_controller_->SelectionChanged(start, end);
+  if (touch_selection_controller_.get())
+    touch_selection_controller_->SelectionChanged();
 }
 
 void NativeTextfieldViews::OnBeforeUserAction() {
diff --git a/ui/views/controls/textfield/native_textfield_views.h b/ui/views/controls/textfield/native_textfield_views.h
index b9a0a73..112d901 100644
--- a/ui/views/controls/textfield/native_textfield_views.h
+++ b/ui/views/controls/textfield/native_textfield_views.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright (c) 2013 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.
 
@@ -80,10 +80,13 @@
   // ui::TouchEditable overrides:
   virtual void SelectRect(const gfx::Point& start,
                           const gfx::Point& end) OVERRIDE;
+  virtual void GetSelectionEndPoints(gfx::Rect* p1, gfx::Rect* p2) OVERRIDE;
   virtual const gfx::Rect& GetBounds() OVERRIDE;
   virtual gfx::NativeView GetNativeView() OVERRIDE;
   virtual void ConvertPointToScreen(gfx::Point* point) OVERRIDE;
   virtual void ConvertPointFromScreen(gfx::Point* point) OVERRIDE;
+  virtual bool DrawsHandles() OVERRIDE;
+  virtual void OpenContextMenu(const gfx::Point anchor) OVERRIDE;
 
   // ContextMenuController overrides:
   virtual void ShowContextMenuForView(View* source,
diff --git a/ui/views/touchui/touch_editing_menu.cc b/ui/views/touchui/touch_editing_menu.cc
new file mode 100644
index 0000000..ed1dae9
--- /dev/null
+++ b/ui/views/touchui/touch_editing_menu.cc
@@ -0,0 +1,150 @@
+// Copyright (c) 2013 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 "ui/views/touchui/touch_editing_menu.h"
+
+#include "base/utf_string_conversions.h"
+#include "grit/ui_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/text_utils.h"
+#include "ui/views/bubble/bubble_border.h"
+#include "ui/views/bubble/bubble_frame_view.h"
+#include "ui/views/controls/button/custom_button.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/widget/widget.h"
+
+namespace {
+
+const int kMenuCommands[] = {IDS_APP_CUT,
+                             IDS_APP_COPY,
+                             IDS_APP_PASTE,
+                             IDS_APP_DELETE,
+                             IDS_APP_SELECT_ALL};
+const int kSpacingBetweenButtons = 0;
+const int kButtonSeparatorColor = SkColorSetARGB(13, 0, 0, 0);
+const int kMenuButtonBorderThickness = 5;
+const SkColor kMenuButtonColorNormal = SkColorSetARGB(102, 255, 255, 255);
+const SkColor kMenuButtonColorHover = SkColorSetARGB(13, 0, 0, 0);
+
+const char* kEllipsesButtonText = "...";
+const int kEllipsesButtonTag = -1;
+}  // namespace
+
+namespace views {
+
+class TouchEditingMenuButtonBackground : public Background {
+ public:
+  TouchEditingMenuButtonBackground() {}
+
+  virtual void Paint(gfx::Canvas* canvas, View* view) const OVERRIDE {
+    CustomButton::ButtonState state = static_cast<CustomButton*>(view)->state();
+    SkColor background_color = (state == CustomButton::STATE_NORMAL)?
+        kMenuButtonColorNormal : kMenuButtonColorHover;
+    int w = view->width();
+    int h = view->height();
+    canvas->FillRect(gfx::Rect(1, 0, w, h), background_color);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TouchEditingMenuButtonBackground);
+};
+
+TouchEditingMenuView::TouchEditingMenuView(
+    TouchEditingMenuController* controller,
+    gfx::Point anchor_point,
+    gfx::NativeView context)
+    : BubbleDelegateView(NULL, views::BubbleBorder::BOTTOM_CENTER),
+      controller_(controller) {
+  set_anchor_point(anchor_point);
+  set_shadow(views::BubbleBorder::SMALL_SHADOW);
+  set_parent_window(context);
+  set_margins(gfx::Insets());
+  set_use_focusless(true);
+  set_adjust_if_offscreen(true);
+
+  SetLayoutManager(new BoxLayout(BoxLayout::kHorizontal, 0, 0,
+      kSpacingBetweenButtons));
+  CreateButtons();
+  views::BubbleDelegateView::CreateBubble(this);
+  GetBubbleFrameView()->set_background(NULL);
+  Show();
+}
+
+TouchEditingMenuView::~TouchEditingMenuView() {
+}
+
+void TouchEditingMenuView::Close() {
+  if (GetWidget()) {
+    controller_ = NULL;
+    GetWidget()->Close();
+  }
+}
+
+void TouchEditingMenuView::WindowClosing() {
+  views::BubbleDelegateView::WindowClosing();
+  if (controller_)
+    controller_->OnMenuClosed(this);
+}
+
+void TouchEditingMenuView::ButtonPressed(Button* sender,
+                                         const ui::Event& event) {
+  if (controller_) {
+    if (sender->tag() != kEllipsesButtonTag)
+      controller_->ExecuteCommand(sender->tag());
+    else
+      controller_->OpenContextMenu();
+  }
+}
+
+void TouchEditingMenuView::OnPaint(gfx::Canvas* canvas) {
+  BubbleDelegateView::OnPaint(canvas);
+
+  // Draw separator bars.
+  int x = 0;
+  for (int i = 0; i < child_count() - 1; ++i) {
+    View* child = child_at(i);
+    x += child->width();
+    canvas->FillRect(gfx::Rect(x, 0, 1, child->height()),
+        kButtonSeparatorColor);
+  }
+}
+
+void TouchEditingMenuView::CreateButtons() {
+  RemoveAllChildViews(true);
+  for (size_t i = 0; i < arraysize(kMenuCommands); i++) {
+    int command_id = kMenuCommands[i];
+    if (controller_ && controller_->IsCommandIdEnabled(command_id)) {
+      Button* button = CreateButton(l10n_util::GetStringUTF16(command_id),
+          command_id);
+      AddChildView(button);
+    }
+  }
+
+  // Finally, add ellipses button.
+  AddChildView(CreateButton(
+      UTF8ToUTF16(kEllipsesButtonText), kEllipsesButtonTag));
+  Layout();
+}
+
+Button* TouchEditingMenuView::CreateButton(const string16& title, int tag) {
+  LabelButton* button = new LabelButton(this, gfx::RemoveAcceleratorChar(
+      title, '&', NULL, NULL));
+  button->set_focusable(true);
+  button->set_request_focus_on_press(false);
+  button->set_background(new TouchEditingMenuButtonBackground);
+  button->set_border(Border::CreateEmptyBorder(kMenuButtonBorderThickness,
+                                               kMenuButtonBorderThickness,
+                                               kMenuButtonBorderThickness,
+                                               kMenuButtonBorderThickness));
+  button->SetFont(ui::ResourceBundle::GetSharedInstance().GetFont(
+      ui::ResourceBundle::SmallFont));
+  button->set_tag(tag);
+  return button;
+}
+
+}  // namespace views
diff --git a/ui/views/touchui/touch_editing_menu.h b/ui/views/touchui/touch_editing_menu.h
new file mode 100644
index 0000000..a667749
--- /dev/null
+++ b/ui/views/touchui/touch_editing_menu.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2013 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.
+
+#ifndef UI_VIEWS_TOUCHUI_TOUCH_EDITING_MENU_H_
+#define UI_VIEWS_TOUCHUI_TOUCH_EDITING_MENU_H_
+
+#include "ui/gfx/point.h"
+#include "ui/views/bubble/bubble_delegate.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/views_export.h"
+
+namespace gfx {
+class Canvas;
+}
+
+namespace views {
+class TouchEditingMenuView;
+class Widget;
+
+class VIEWS_EXPORT TouchEditingMenuController {
+ public:
+  // Checks if the specified menu command is supported.
+  virtual bool IsCommandIdEnabled(int command_id) const = 0;
+
+  // Send a context menu command to the controller.
+  virtual void ExecuteCommand(int command_id) = 0;
+
+  // Tell the controller that user has selected the context menu button.
+  virtual void OpenContextMenu() = 0;
+
+  // Called when the menu is closed.
+  virtual void OnMenuClosed(TouchEditingMenuView* menu) = 0;
+
+ protected:
+  virtual ~TouchEditingMenuController() {}
+};
+
+// A View that displays the touch context menu.
+class VIEWS_EXPORT TouchEditingMenuView : public BubbleDelegateView,
+                                          public ButtonListener {
+ public:
+  TouchEditingMenuView(TouchEditingMenuController* controller,
+                       gfx::Point anchor_point,
+                       gfx::NativeView context);
+  virtual ~TouchEditingMenuView();
+
+  void Close();
+
+ private:
+  // views::WidgetDelegate overrides:
+  virtual void WindowClosing() OVERRIDE;
+
+  // Overridden from ButtonListener.
+  virtual void ButtonPressed(Button* sender, const ui::Event& event) OVERRIDE;
+
+  // Overridden from BubbleDelegateView.
+  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+
+  // Queries the |controller_| for what elements to show in the menu and sizes
+  // the menu appropriately.
+  void CreateButtons();
+
+  // Helper method to create a single button.
+  Button* CreateButton(const string16& title, int tag);
+
+  TouchEditingMenuController* controller_;
+
+  DISALLOW_COPY_AND_ASSIGN(TouchEditingMenuView);
+};
+
+}  // namespace views
+
+#endif  // UI_VIEWS_TOUCHUI_TOUCH_SELECTION_CONTROLLER_IMPL_H_
diff --git a/ui/views/touchui/touch_selection_controller_impl.cc b/ui/views/touchui/touch_selection_controller_impl.cc
index 5956373..a0ca742 100644
--- a/ui/views/touchui/touch_selection_controller_impl.cc
+++ b/ui/views/touchui/touch_selection_controller_impl.cc
@@ -1,52 +1,32 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright (c) 2013 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 "ui/views/touchui/touch_selection_controller_impl.h"
 
+#include "base/command_line.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/base/ui_base_switches.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/rect.h"
 #include "ui/gfx/screen.h"
 #include "ui/gfx/size.h"
-#include "ui/gfx/text_utils.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 {
 
 // Constants defining the visual attributes of selection handles
 const int kSelectionHandleRadius = 10;
-const int kSelectionHandleCursorHeight = 10;
 const int kSelectionHandleAlpha = 0x7F;
 const SkColor kSelectionHandleColor =
-    SkColorSetA(SK_ColorBLUE, kSelectionHandleAlpha);
+    SkColorSetA(SK_ColorBLACK, kSelectionHandleAlpha);
 
 // The minimum selection size to trigger selection controller.
 const int kMinSelectionSize = 4;
 
-const int kContextMenuCommands[] = {IDS_APP_CUT,
-                                    IDS_APP_COPY,
-// TODO(varunjain): PASTE is acting funny due to some gtk clipboard issue.
-// Uncomment the following when that is fixed.
-//                                    IDS_APP_PASTE,
-                                    IDS_APP_DELETE,
-                                    IDS_APP_SELECT_ALL};
-const int kContextMenuPadding = 2;
 const int kContextMenuTimoutMs = 1000;
-const int kContextMenuVerticalOffset = 25;
+const int kContextMenuVerticalOffset = 5;
 
 // Convenience struct to represent a circle shape.
 struct Circle {
@@ -56,12 +36,13 @@
 };
 
 // Creates a widget to host SelectionHandleView.
-views::Widget* CreateTouchSelectionPopupWidget() {
+views::Widget* CreateTouchSelectionPopupWidget(gfx::NativeView context) {
   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;
+  params.context = context;
   widget->Init(params);
   return widget;
 }
@@ -90,9 +71,11 @@
 // A View that displays the text selection handle.
 class TouchSelectionControllerImpl::SelectionHandleView : public View {
  public:
-  explicit SelectionHandleView(TouchSelectionControllerImpl* controller)
-      : controller_(controller) {
-    widget_.reset(CreateTouchSelectionPopupWidget());
+  explicit SelectionHandleView(TouchSelectionControllerImpl* controller,
+                               gfx::NativeView context)
+      : controller_(controller),
+        cursor_height_(0) {
+    widget_.reset(CreateTouchSelectionPopupWidget(context));
     widget_->SetContentsView(this);
     widget_->SetAlwaysOnTop(true);
 
@@ -105,30 +88,30 @@
 
   virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
     Circle circle = {kSelectionHandleRadius, gfx::Point(kSelectionHandleRadius,
-                     kSelectionHandleRadius + kSelectionHandleCursorHeight),
+                     kSelectionHandleRadius + cursor_height_),
                      kSelectionHandleColor};
     PaintCircle(circle, canvas);
     canvas->DrawLine(gfx::Point(kSelectionHandleRadius, 0),
-        gfx::Point(kSelectionHandleRadius, kSelectionHandleCursorHeight),
+        gfx::Point(kSelectionHandleRadius, cursor_height_),
         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 OnGestureEvent(ui::GestureEvent* event) OVERRIDE {
+    event->SetHandled();
+    switch (event->type()) {
+      case ui::ET_GESTURE_SCROLL_BEGIN:
+        controller_->dragging_handle_ = this;
+        break;
+      case ui::ET_GESTURE_SCROLL_UPDATE:
+        controller_->SelectionHandleDragged(event->location());
+        break;
+      case ui::ET_GESTURE_SCROLL_END:
+      case ui::ET_GESTURE_END:
+        controller_->dragging_handle_ = NULL;
+        break;
+      default:
+        break;
+    }
   }
 
   virtual void SetVisible(bool visible) OVERRIDE {
@@ -144,13 +127,18 @@
 
   virtual gfx::Size GetPreferredSize() OVERRIDE {
     return gfx::Size(2 * kSelectionHandleRadius,
-                     2 * kSelectionHandleRadius + kSelectionHandleCursorHeight);
+                     2 * kSelectionHandleRadius + cursor_height_);
   }
 
-  void SetScreenPosition(const gfx::Point& position) {
-    gfx::Rect widget_bounds(position.x() - kSelectionHandleRadius, position.y(),
+  bool IsWidgetVisible() const {
+    return widget_->IsVisible();
+  }
+
+  void SetSelectionRectInScreen(const gfx::Rect& rect) {
+    cursor_height_ = rect.height();
+    gfx::Rect widget_bounds(rect.x() - kSelectionHandleRadius, rect.y(),
         2 * kSelectionHandleRadius,
-        2 * kSelectionHandleRadius + kSelectionHandleCursorHeight);
+        2 * kSelectionHandleRadius + cursor_height_);
     widget_->SetBounds(widget_bounds);
   }
 
@@ -161,194 +149,65 @@
  private:
   scoped_ptr<Widget> widget_;
   TouchSelectionControllerImpl* controller_;
+  int cursor_height_;
 
   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::STATE_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);
-};
-
-// A View that displays the touch context menu.
-class TouchSelectionControllerImpl::TouchContextMenuView
-    : public ButtonListener,
-      public View {
- public:
-  explicit TouchContextMenuView(TouchSelectionControllerImpl* controller)
-      : controller_(controller) {
-    widget_.reset(CreateTouchSelectionPopupWidget());
-    widget_->SetContentsView(this);
-    widget_->SetAlwaysOnTop(true);
-
-    // We are owned by the TouchSelectionController.
-    set_owned_by_client();
-    SetLayoutManager(new BoxLayout(BoxLayout::kHorizontal, kContextMenuPadding,
-        kContextMenuPadding, kContextMenuPadding));
-  }
-
-  virtual ~TouchContextMenuView() {
-  }
-
-  virtual void SetVisible(bool visible) OVERRIDE {
-    // We simply show/hide the container widget.
-    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 {
-    // TODO(varunjain): the following color scheme is copied from
-    // menu_scroll_view_container.cc. Figure out how to consolidate the two
-    // pieces of code.
-#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());
-
-    skia::RefPtr<SkShader> shader = skia::AdoptRef(
-        SkGradientShader::CreateLinear(
-            points, kGradientColors, kGradientPoints,
-            arraysize(kGradientPoints),
-            SkShader::kRepeat_TileMode));
-    DCHECK(shader);
-
-    SkPaint paint;
-    paint.setShader(shader.get());
-
-    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
-  }
-
-  // Overridden from ButtonListener:
-  virtual void ButtonPressed(Button* sender, const ui::Event& event) OVERRIDE {
-    controller_->ExecuteCommand(sender->tag());
-  }
-
- private:
-  // Queries the client view for what elements to show in the menu and sizes
-  // the menu appropriately.
-  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, gfx::RemoveAcceleratorChar(
-            l10n_util::GetStringUTF16(command_id), '&', NULL, NULL));
-        button->set_focusable(true);
-        button->set_request_focus_on_press(false);
-        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::GetNativeScreen()->
-        GetDisplayNearestPoint(position).bounds();
-    widget_->SetBounds(widget_bounds.AdjustToFit(monitor_bounds));
-    Layout();
-  }
-
-  scoped_ptr<Widget> widget_;
-  TouchSelectionControllerImpl* controller_;
-
-  DISALLOW_COPY_AND_ASSIGN(TouchContextMenuView);
-};
-
 TouchSelectionControllerImpl::TouchSelectionControllerImpl(
     ui::TouchEditable* client_view)
     : client_view_(client_view),
-      selection_handle_1_(new SelectionHandleView(this)),
-      selection_handle_2_(new SelectionHandleView(this)),
-      context_menu_(new TouchContextMenuView(this)),
+      client_widget_(NULL),
+      selection_handle_1_(new SelectionHandleView(this,
+                          client_view->GetNativeView())),
+      selection_handle_2_(new SelectionHandleView(this,
+                          client_view->GetNativeView())),
+      context_menu_(NULL),
       dragging_handle_(NULL) {
+  client_widget_ = Widget::GetWidgetForNativeView(
+      client_view_->GetNativeView());
+  client_widget_->AddObserver(this);
 }
 
 TouchSelectionControllerImpl::~TouchSelectionControllerImpl() {
+  HideContextMenu();
+  if (client_widget_)
+    client_widget_->RemoveObserver(this);
 }
 
-void TouchSelectionControllerImpl::SelectionChanged(const gfx::Point& p1,
-                                                    const gfx::Point& p2) {
-  gfx::Point screen_pos_1(p1);
+void TouchSelectionControllerImpl::SelectionChanged() {
+  gfx::Rect r1, r2;
+  client_view_->GetSelectionEndPoints(&r1, &r2);
+  gfx::Point screen_pos_1(r1.origin());
   client_view_->ConvertPointToScreen(&screen_pos_1);
-  gfx::Point screen_pos_2(p2);
+  gfx::Point screen_pos_2(r2.origin());
   client_view_->ConvertPointToScreen(&screen_pos_2);
+  gfx::Rect screen_rect_1(screen_pos_1, r1.size());
+  gfx::Rect screen_rect_2(screen_pos_2, r2.size());
 
+  if (client_view_->DrawsHandles()) {
+    UpdateContextMenu(r1.origin(), r2.origin());
+    return;
+  }
   if (dragging_handle_) {
     // We need to reposition only the selection handle that is being dragged.
     // The other handle stays the same. Also, the selection handle being dragged
     // will always be at the end of selection, while the other handle will be at
     // the start.
-    dragging_handle_->SetScreenPosition(screen_pos_2);
+    dragging_handle_->SetSelectionRectInScreen(screen_rect_2);
+
+    // The non-dragging-handle might have recently become visible.
+    SelectionHandleView* non_dragging_handle =
+        dragging_handle_ == selection_handle_1_.get()?
+            selection_handle_2_.get() : selection_handle_1_.get();
+    if (client_view_->GetBounds().Contains(r1.origin())) {
+      non_dragging_handle->SetSelectionRectInScreen(screen_rect_1);
+      non_dragging_handle->SetVisible(true);
+    } else {
+      non_dragging_handle->SetVisible(false);
+    }
   } else {
-    UpdateContextMenu(p1, p2);
+    UpdateContextMenu(r1.origin(), r2.origin());
 
     // Check if there is any selection at all.
     if (IsEmptySelection(screen_pos_2, screen_pos_1)) {
@@ -357,15 +216,15 @@
       return;
     }
 
-    if (client_view_->GetBounds().Contains(p1)) {
-      selection_handle_1_->SetScreenPosition(screen_pos_1);
+    if (client_view_->GetBounds().Contains(r1.origin())) {
+      selection_handle_1_->SetSelectionRectInScreen(screen_rect_1);
       selection_handle_1_->SetVisible(true);
     } else {
       selection_handle_1_->SetVisible(false);
     }
 
-    if (client_view_->GetBounds().Contains(p2)) {
-      selection_handle_2_->SetScreenPosition(screen_pos_2);
+    if (client_view_->GetBounds().Contains(r2.origin())) {
+      selection_handle_2_->SetSelectionRectInScreen(screen_rect_2);
       selection_handle_2_->SetVisible(true);
     } else {
       selection_handle_2_->SetVisible(false);
@@ -373,12 +232,6 @@
   }
 }
 
-void TouchSelectionControllerImpl::ClientViewLostFocus() {
-  selection_handle_1_->SetVisible(false);
-  selection_handle_2_->SetVisible(false);
-  HideContextMenu();
-}
-
 void TouchSelectionControllerImpl::SelectionHandleDragged(
     const gfx::Point& drag_pos) {
   // We do not want to show the context menu while dragging.
@@ -423,21 +276,44 @@
   client_view_->ExecuteCommand(command_id);
 }
 
+void TouchSelectionControllerImpl::OpenContextMenu() {
+  gfx::Point anchor = context_menu_->anchor_point();
+  HideContextMenu();
+  client_view_->OpenContextMenu(anchor);
+}
+
+void TouchSelectionControllerImpl::OnMenuClosed(TouchEditingMenuView* menu) {
+  if (menu == context_menu_)
+    context_menu_ = NULL;
+}
+
+void TouchSelectionControllerImpl::OnWidgetClosing(Widget* widget) {
+  DCHECK_EQ(client_widget_, widget);
+  client_widget_ = NULL;
+}
+
+void TouchSelectionControllerImpl::OnWidgetBoundsChanged(
+    Widget* widget,
+    const gfx::Rect& new_bounds) {
+  DCHECK_EQ(client_widget_, widget);
+  HideContextMenu();
+  SelectionChanged();
+}
+
 void TouchSelectionControllerImpl::ContextMenuTimerFired() {
   // Get selection end points in client_view's space.
-  gfx::Point p1(kSelectionHandleRadius, 0);
-  ConvertPointToClientView(selection_handle_1_.get(), &p1);
-  gfx::Point p2(kSelectionHandleRadius, 0);
-  ConvertPointToClientView(selection_handle_2_.get(), &p2);
+  gfx::Rect r1, r2;
+  client_view_->GetSelectionEndPoints(&r1, &r2);
 
   // if selection is completely inside the view, we display the context menu
   // in the middle of the end points on the top. Else, we show the menu on the
   // top border of the view in the center.
   gfx::Point menu_pos;
   gfx::Rect client_bounds = client_view_->GetBounds();
-  if (client_bounds.Contains(p1) && client_bounds.Contains(p2)) {
-    menu_pos.set_x((p1.x() + p2.x()) / 2);
-    menu_pos.set_y(std::min(p1.y(), p2.y()) - kContextMenuVerticalOffset);
+  if (client_bounds.Contains(r1.origin()) &&
+      client_bounds.Contains(r2.origin())) {
+    menu_pos.set_x((r1.origin().x() + r2.origin().x()) / 2);
+    menu_pos.set_y(std::min(r1.y(), r2.y()) - kContextMenuVerticalOffset);
   } else {
     menu_pos.set_x(client_bounds.x() + client_bounds.width() / 2);
     menu_pos.set_y(client_bounds.y());
@@ -445,8 +321,9 @@
 
   client_view_->ConvertPointToScreen(&menu_pos);
 
-  context_menu_->SetScreenPosition(menu_pos);
-  context_menu_->SetVisible(true);
+  DCHECK(!context_menu_);
+  context_menu_ = new TouchEditingMenuView(this, menu_pos,
+      client_view_->GetNativeView());
 }
 
 void TouchSelectionControllerImpl::UpdateContextMenu(const gfx::Point& p1,
@@ -465,7 +342,9 @@
 }
 
 void TouchSelectionControllerImpl::HideContextMenu() {
-  context_menu_->SetVisible(false);
+  if (context_menu_)
+    context_menu_->Close();
+  context_menu_ = NULL;
   context_menu_timer_.Stop();
 }
 
@@ -485,13 +364,17 @@
   return selection_handle_2_->visible();
 }
 
-}  // namespace views
-
-namespace ui {
-
-TouchSelectionController* TouchSelectionController::create(
-    TouchEditable* client_view) {
-  return new views::TouchSelectionControllerImpl(client_view);
+ViewsTouchSelectionControllerFactory::ViewsTouchSelectionControllerFactory() {
 }
 
-}  // namespace ui
+ui::TouchSelectionController* ViewsTouchSelectionControllerFactory::create(
+    ui::TouchEditable* client_view) {
+#if defined(OS_CHROMEOS)
+  if (CommandLine::ForCurrentProcess()->HasSwitch(
+      switches::kEnableTouchEditing))
+    return new views::TouchSelectionControllerImpl(client_view);
+#endif
+  return NULL;
+}
+
+}  // namespace views
diff --git a/ui/views/touchui/touch_selection_controller_impl.h b/ui/views/touchui/touch_selection_controller_impl.h
index 5d042e7..9e60a997 100644
--- a/ui/views/touchui/touch_selection_controller_impl.h
+++ b/ui/views/touchui/touch_selection_controller_impl.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2013 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.
 
@@ -8,6 +8,7 @@
 #include "base/timer.h"
 #include "ui/base/touch/touch_editing_controller.h"
 #include "ui/gfx/point.h"
+#include "ui/views/touchui/touch_editing_menu.h"
 #include "ui/views/view.h"
 #include "ui/views/views_export.h"
 
@@ -16,7 +17,9 @@
 // Touch specific implementation of TouchSelectionController. Responsible for
 // displaying selection handles and menu elements relevant in a touch interface.
 class VIEWS_EXPORT TouchSelectionControllerImpl
-    : public ui::TouchSelectionController {
+    : public ui::TouchSelectionController,
+      public TouchEditingMenuController,
+      public WidgetObserver {
  public:
   // Use TextSelectionController::create().
   explicit TouchSelectionControllerImpl(
@@ -25,15 +28,11 @@
   virtual ~TouchSelectionControllerImpl();
 
   // TextSelectionController.
-  virtual void SelectionChanged(const gfx::Point& p1,
-                                const gfx::Point& p2) OVERRIDE;
-
-  virtual void ClientViewLostFocus() OVERRIDE;
+  virtual void SelectionChanged() OVERRIDE;
 
  private:
   friend class TouchSelectionControllerImplTest;
   class SelectionHandleView;
-  class TouchContextMenuView;
 
   // Callback to inform the client view that the selection handle has been
   // dragged, hence selection may need to be updated.
@@ -43,11 +42,18 @@
   // system to that of the client view.
   void ConvertPointToClientView(SelectionHandleView* source, gfx::Point* point);
 
-  // Checks if the client view supports a context menu command.
-  bool IsCommandIdEnabled(int command_id) const;
+  // Overridden from TouchEditingMenuController.
+  virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE;
+  virtual void ExecuteCommand(int command_id) OVERRIDE;
+  virtual void OpenContextMenu() OVERRIDE;
+  virtual void OnMenuClosed(TouchEditingMenuView* menu) OVERRIDE;
 
-  // Sends a context menu command to the client view.
-  void ExecuteCommand(int command_id);
+  // Overridden from WidgetObserver. We will observe the widget backing the
+  // |client_view_| so that when its moved/resized, we can update the selection
+  // handles appropriately.
+  virtual void OnWidgetClosing(Widget* widget) OVERRIDE;
+  virtual void OnWidgetBoundsChanged(Widget* widget,
+                                     const gfx::Rect& new_bounds) OVERRIDE;
 
   // Time to show context menu.
   void ContextMenuTimerFired();
@@ -65,9 +71,10 @@
   bool IsSelectionHandle2Visible();
 
   ui::TouchEditable* client_view_;
+  Widget* client_widget_;
   scoped_ptr<SelectionHandleView> selection_handle_1_;
   scoped_ptr<SelectionHandleView> selection_handle_2_;
-  scoped_ptr<TouchContextMenuView> context_menu_;
+  TouchEditingMenuView* context_menu_;
 
   // Timer to trigger |context_menu| (|context_menu| is not shown if the
   // selection handles are being updated. It appears only when the handles are
@@ -80,6 +87,16 @@
   DISALLOW_COPY_AND_ASSIGN(TouchSelectionControllerImpl);
 };
 
+class VIEWS_EXPORT ViewsTouchSelectionControllerFactory
+    : public ui::TouchSelectionControllerFactory {
+ public:
+  ViewsTouchSelectionControllerFactory();
+
+  // Overridden from ui::TouchSelectionControllerFactory.
+  virtual ui::TouchSelectionController* create(
+      ui::TouchEditable* client_view) OVERRIDE;
+};
+
 }  // namespace views
 
 #endif  // UI_UI_VIEWS_TOUCHUI_TOUCH_SELECTION_CONTROLLER_IMPL_H_
diff --git a/ui/views/views.gyp b/ui/views/views.gyp
index 1a154558..81024839 100644
--- a/ui/views/views.gyp
+++ b/ui/views/views.gyp
@@ -309,6 +309,10 @@
         'repeat_controller.h',
         'round_rect_painter.cc',
         'round_rect_painter.h',
+        'touchui/touch_editing_menu.cc',
+        'touchui/touch_editing_menu.h',
+        'touchui/touch_selection_controller_impl.cc',
+        'touchui/touch_selection_controller_impl.h',
         'view.cc',
         'view.h',
         'view_constants.cc',
@@ -321,6 +325,7 @@
         'view_text_utils.cc',
         'view_text_utils.h',
         'view_win.cc',
+        'views_delegate.cc',
         'views_delegate.h',
         'widget/aero_tooltip_manager.cc',
         'widget/aero_tooltip_manager.h',
diff --git a/ui/views/views_delegate.cc b/ui/views/views_delegate.cc
new file mode 100644
index 0000000..20cb88f9
--- /dev/null
+++ b/ui/views/views_delegate.cc
@@ -0,0 +1,20 @@
+// Copyright (c) 2013 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 "ui/views/views_delegate.h"
+
+#include "ui/views/touchui/touch_selection_controller_impl.h"
+
+namespace views {
+
+ViewsDelegate::ViewsDelegate()
+    : views_tsc_factory_(new views::ViewsTouchSelectionControllerFactory) {
+  ui::TouchSelectionControllerFactory::SetInstance(views_tsc_factory_.get());
+}
+
+ViewsDelegate::~ViewsDelegate() {
+  ui::TouchSelectionControllerFactory::SetInstance(NULL);
+}
+
+}  // namespace views
diff --git a/ui/views/views_delegate.h b/ui/views/views_delegate.h
index 105bab04..72aaeeaa 100644
--- a/ui/views/views_delegate.h
+++ b/ui/views/views_delegate.h
@@ -32,6 +32,7 @@
 
 class NativeWidget;
 class NonClientFrameView;
+class ViewsTouchSelectionControllerFactory;
 class View;
 class Widget;
 namespace internal {
@@ -49,7 +50,9 @@
   // The active ViewsDelegate used by the views system.
   static ViewsDelegate* views_delegate;
 
-  virtual ~ViewsDelegate() {}
+  ViewsDelegate();
+
+  virtual ~ViewsDelegate();
 
   // Saves the position, size and "show" state for the window with the
   // specified name.
@@ -106,6 +109,9 @@
   // Gives the platform a chance to modify the properties of a Widget.
   virtual void OnBeforeWidgetInit(Widget::InitParams* params,
                             internal::NativeWidgetDelegate* delegate) = 0;
+
+ private:
+  scoped_ptr<ViewsTouchSelectionControllerFactory> views_tsc_factory_;
 };
 
 }  // namespace views