1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/browser/web_contents/touch_editable_impl_aura.h"
6
7#include "content/browser/renderer_host/render_widget_host_impl.h"
8#include "content/browser/renderer_host/render_widget_host_view_aura.h"
9#include "content/browser/web_contents/web_contents_impl.h"
10#include "content/common/view_messages.h"
11#include "content/public/browser/render_view_host.h"
12#include "content/public/browser/render_widget_host.h"
13#include "ui/aura/client/screen_position_client.h"
14#include "ui/aura/window.h"
15#include "ui/aura/window_tree_host.h"
16#include "ui/base/clipboard/clipboard.h"
17#include "ui/base/ui_base_switches_util.h"
18#include "ui/gfx/range/range.h"
19#include "ui/strings/grit/ui_strings.h"
20#include "ui/wm/public/activation_client.h"
21
22namespace content {
23
24////////////////////////////////////////////////////////////////////////////////
25// TouchEditableImplAura, public:
26
27TouchEditableImplAura::~TouchEditableImplAura() {
28  Cleanup();
29}
30
31// static
32TouchEditableImplAura* TouchEditableImplAura::Create() {
33  if (switches::IsTouchEditingEnabled())
34    return new TouchEditableImplAura();
35  return NULL;
36}
37
38void TouchEditableImplAura::AttachToView(RenderWidgetHostViewAura* view) {
39  if (rwhva_ == view)
40    return;
41
42  Cleanup();
43  if (!view)
44    return;
45
46  rwhva_ = view;
47  rwhva_->set_touch_editing_client(this);
48}
49
50void TouchEditableImplAura::UpdateEditingController() {
51  if (!rwhva_ || !rwhva_->HasFocus())
52    return;
53
54  if (text_input_type_ != ui::TEXT_INPUT_TYPE_NONE ||
55      selection_anchor_rect_ != selection_focus_rect_) {
56    if (touch_selection_controller_)
57      touch_selection_controller_->SelectionChanged();
58  } else {
59    EndTouchEditing(false);
60  }
61}
62
63void TouchEditableImplAura::OverscrollStarted() {
64  scrolls_in_progress_++;
65}
66
67void TouchEditableImplAura::OverscrollCompleted() {
68  ScrollEnded();
69}
70
71////////////////////////////////////////////////////////////////////////////////
72// TouchEditableImplAura, RenderWidgetHostViewAura::TouchEditingClient
73// implementation:
74
75void TouchEditableImplAura::StartTouchEditing() {
76  if (!rwhva_ || !rwhva_->HasFocus())
77    return;
78
79  if (!touch_selection_controller_) {
80    touch_selection_controller_.reset(
81        ui::TouchSelectionController::create(this));
82  }
83  if (touch_selection_controller_)
84    touch_selection_controller_->SelectionChanged();
85}
86
87void TouchEditableImplAura::EndTouchEditing(bool quick) {
88  if (touch_selection_controller_) {
89    if (touch_selection_controller_->IsHandleDragInProgress()) {
90      touch_selection_controller_->SelectionChanged();
91    } else {
92      selection_gesture_in_process_ = false;
93      touch_selection_controller_->HideHandles(quick);
94      touch_selection_controller_.reset();
95    }
96  }
97}
98
99void TouchEditableImplAura::OnSelectionOrCursorChanged(const gfx::Rect& anchor,
100                                                       const gfx::Rect& focus) {
101  selection_anchor_rect_ = anchor;
102  selection_focus_rect_ = focus;
103
104  // If touch editing handles were not visible, we bring them up only if the
105  // current event is a gesture event, no scroll/fling/overscoll is in progress,
106  // and there is non-zero selection on the page
107  if (selection_gesture_in_process_ && !scrolls_in_progress_ &&
108      selection_anchor_rect_ != selection_focus_rect_) {
109    StartTouchEditing();
110    selection_gesture_in_process_ = false;
111  }
112
113  UpdateEditingController();
114}
115
116void TouchEditableImplAura::OnTextInputTypeChanged(ui::TextInputType type) {
117  text_input_type_ = type;
118}
119
120bool TouchEditableImplAura::HandleInputEvent(const ui::Event* event) {
121  DCHECK(rwhva_);
122  if (!event->IsGestureEvent()) {
123    // Ignore all non-gesture events. Non-gesture events that can deactivate
124    // touch editing are handled in TouchSelectionControllerImpl.
125    return false;
126  }
127
128  const ui::GestureEvent* gesture_event =
129      static_cast<const ui::GestureEvent*>(event);
130  switch (event->type()) {
131    case ui::ET_GESTURE_TAP:
132      // When the user taps, we want to show touch editing handles if user
133      // tapped on selected text.
134      if (gesture_event->details().tap_count() == 1 &&
135          selection_anchor_rect_ != selection_focus_rect_) {
136        // UnionRects only works for rects with non-zero width.
137        gfx::Rect anchor(selection_anchor_rect_.origin(),
138                         gfx::Size(1, selection_anchor_rect_.height()));
139        gfx::Rect focus(selection_focus_rect_.origin(),
140                        gfx::Size(1, selection_focus_rect_.height()));
141        gfx::Rect selection_rect = gfx::UnionRects(anchor, focus);
142        if (selection_rect.Contains(gesture_event->location())) {
143          StartTouchEditing();
144          return true;
145        }
146      }
147      // For single taps, not inside selected region, we want to show handles
148      // only when the tap is on an already focused textfield.
149      textfield_was_focused_on_tap_ =
150          gesture_event->details().tap_count() == 1 &&
151          text_input_type_ != ui::TEXT_INPUT_TYPE_NONE;
152      break;
153    case ui::ET_GESTURE_LONG_PRESS:
154      selection_gesture_in_process_ = true;
155      break;
156    case ui::ET_GESTURE_SCROLL_BEGIN:
157      scrolls_in_progress_++;
158      // We need to hide selection handles during scroll (including fling and
159      // overscroll), but they should be re-activated after scrolling if:
160      //  - an existing scroll decided that handles should be shown after
161      //    scrolling; or
162      //  - the gesture in progress is going to end in selection; or
163      //  - selection handles are currently active.
164      handles_hidden_due_to_scroll_ = handles_hidden_due_to_scroll_ ||
165                                      selection_gesture_in_process_ ||
166                                      touch_selection_controller_ != NULL;
167      selection_gesture_in_process_ = false;
168      EndTouchEditing(true);
169      break;
170    case ui::ET_GESTURE_SCROLL_END:
171      ScrollEnded();
172      break;
173    default:
174      break;
175  }
176  return false;
177}
178
179void TouchEditableImplAura::GestureEventAck(int gesture_event_type) {
180  DCHECK(rwhva_);
181  if (gesture_event_type == blink::WebInputEvent::GestureTap &&
182      text_input_type_ != ui::TEXT_INPUT_TYPE_NONE &&
183      textfield_was_focused_on_tap_) {
184    StartTouchEditing();
185    UpdateEditingController();
186  }
187}
188
189void TouchEditableImplAura::DidStopFlinging() {
190  ScrollEnded();
191}
192
193void TouchEditableImplAura::OnViewDestroyed() {
194  Cleanup();
195}
196
197////////////////////////////////////////////////////////////////////////////////
198// TouchEditableImplAura, ui::TouchEditable implementation:
199
200void TouchEditableImplAura::SelectRect(const gfx::Point& start,
201                                       const gfx::Point& end) {
202  RenderWidgetHost* host = rwhva_->GetRenderWidgetHost();
203  RenderViewHost* rvh = RenderViewHost::From(host);
204  WebContentsImpl* wc =
205      static_cast<WebContentsImpl*>(WebContents::FromRenderViewHost(rvh));
206  wc->SelectRange(start, end);
207}
208
209void TouchEditableImplAura::MoveCaretTo(const gfx::Point& point) {
210  if (!rwhva_)
211    return;
212
213  RenderWidgetHostImpl* host = RenderWidgetHostImpl::From(
214      rwhva_->GetRenderWidgetHost());
215  host->MoveCaret(point);
216}
217
218void TouchEditableImplAura::GetSelectionEndPoints(gfx::Rect* p1,
219                                                  gfx::Rect* p2) {
220  *p1 = selection_anchor_rect_;
221  *p2 = selection_focus_rect_;
222}
223
224gfx::Rect TouchEditableImplAura::GetBounds() {
225  return rwhva_ ? gfx::Rect(rwhva_->GetNativeView()->bounds().size()) :
226      gfx::Rect();
227}
228
229gfx::NativeView TouchEditableImplAura::GetNativeView() const {
230  return rwhva_ ? rwhva_->GetNativeView()->GetToplevelWindow() : NULL;
231}
232
233void TouchEditableImplAura::ConvertPointToScreen(gfx::Point* point) {
234  if (!rwhva_)
235    return;
236  aura::Window* window = rwhva_->GetNativeView();
237  aura::client::ScreenPositionClient* screen_position_client =
238      aura::client::GetScreenPositionClient(window->GetRootWindow());
239  if (screen_position_client)
240    screen_position_client->ConvertPointToScreen(window, point);
241}
242
243void TouchEditableImplAura::ConvertPointFromScreen(gfx::Point* point) {
244  if (!rwhva_)
245    return;
246  aura::Window* window = rwhva_->GetNativeView();
247  aura::client::ScreenPositionClient* screen_position_client =
248      aura::client::GetScreenPositionClient(window->GetRootWindow());
249  if (screen_position_client)
250    screen_position_client->ConvertPointFromScreen(window, point);
251}
252
253bool TouchEditableImplAura::DrawsHandles() {
254  return false;
255}
256
257void TouchEditableImplAura::OpenContextMenu(const gfx::Point& anchor) {
258  if (!rwhva_)
259    return;
260  gfx::Point point = anchor;
261  ConvertPointFromScreen(&point);
262  RenderWidgetHost* host = rwhva_->GetRenderWidgetHost();
263  host->Send(new ViewMsg_ShowContextMenu(
264      host->GetRoutingID(), ui::MENU_SOURCE_TOUCH_EDIT_MENU, point));
265  EndTouchEditing(false);
266}
267
268bool TouchEditableImplAura::IsCommandIdChecked(int command_id) const {
269  NOTREACHED();
270  return false;
271}
272
273bool TouchEditableImplAura::IsCommandIdEnabled(int command_id) const {
274  if (!rwhva_)
275    return false;
276  bool editable = rwhva_->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE;
277  bool readable = rwhva_->GetTextInputType() != ui::TEXT_INPUT_TYPE_PASSWORD;
278  gfx::Range selection_range;
279  rwhva_->GetSelectionRange(&selection_range);
280  bool has_selection = !selection_range.is_empty();
281  switch (command_id) {
282    case IDS_APP_CUT:
283      return editable && readable && has_selection;
284    case IDS_APP_COPY:
285      return readable && has_selection;
286    case IDS_APP_PASTE: {
287      base::string16 result;
288      ui::Clipboard::GetForCurrentThread()->ReadText(
289          ui::CLIPBOARD_TYPE_COPY_PASTE, &result);
290      return editable && !result.empty();
291    }
292    case IDS_APP_DELETE:
293      return editable && has_selection;
294    case IDS_APP_SELECT_ALL:
295      return true;
296    default:
297      return false;
298  }
299}
300
301bool TouchEditableImplAura::GetAcceleratorForCommandId(
302    int command_id,
303    ui::Accelerator* accelerator) {
304  return false;
305}
306
307void TouchEditableImplAura::ExecuteCommand(int command_id, int event_flags) {
308  RenderWidgetHost* host = rwhva_->GetRenderWidgetHost();
309  RenderViewHost* rvh = RenderViewHost::From(host);
310  WebContents* wc = WebContents::FromRenderViewHost(rvh);
311
312  switch (command_id) {
313    case IDS_APP_CUT:
314      wc->Cut();
315      break;
316    case IDS_APP_COPY:
317      wc->Copy();
318      break;
319    case IDS_APP_PASTE:
320      wc->Paste();
321      break;
322    case IDS_APP_DELETE:
323      wc->Delete();
324      break;
325    case IDS_APP_SELECT_ALL:
326      wc->SelectAll();
327      break;
328    default:
329      NOTREACHED();
330      break;
331  }
332  EndTouchEditing(false);
333}
334
335void TouchEditableImplAura::DestroyTouchSelection() {
336  EndTouchEditing(false);
337}
338
339////////////////////////////////////////////////////////////////////////////////
340// TouchEditableImplAura, private:
341
342TouchEditableImplAura::TouchEditableImplAura()
343    : text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
344      rwhva_(NULL),
345      selection_gesture_in_process_(false),
346      handles_hidden_due_to_scroll_(false),
347      scrolls_in_progress_(0),
348      textfield_was_focused_on_tap_(false) {
349}
350
351void TouchEditableImplAura::ScrollEnded() {
352  scrolls_in_progress_--;
353  // If there is no scrolling left in progress, show selection handles if they
354  // were hidden due to scroll and there is a selection.
355  if (!scrolls_in_progress_ && handles_hidden_due_to_scroll_ &&
356      (selection_anchor_rect_ != selection_focus_rect_ ||
357          text_input_type_ != ui::TEXT_INPUT_TYPE_NONE)) {
358    StartTouchEditing();
359    UpdateEditingController();
360    handles_hidden_due_to_scroll_ = false;
361  }
362}
363
364void TouchEditableImplAura::Cleanup() {
365  if (rwhva_) {
366    rwhva_->set_touch_editing_client(NULL);
367    rwhva_ = NULL;
368  }
369  text_input_type_ = ui::TEXT_INPUT_TYPE_NONE;
370  EndTouchEditing(true);
371  selection_gesture_in_process_ = false;
372  handles_hidden_due_to_scroll_ = false;
373  scrolls_in_progress_ = 0;
374}
375
376}  // namespace content
377