touch_editable_impl_aura.cc revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
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 "grit/ui_strings.h"
14#include "ui/aura/client/screen_position_client.h"
15#include "ui/aura/window.h"
16#include "ui/aura/window_tree_host.h"
17#include "ui/base/clipboard/clipboard.h"
18#include "ui/base/ui_base_switches_util.h"
19#include "ui/gfx/range/range.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 touch editing handles were not visible, we bring them up only if
55  // there is non-zero selection on the page. And the current event is a
56  // gesture event (we dont want to show handles if the user is selecting
57  // using mouse or keyboard).
58  if (selection_gesture_in_process_ && !scroll_in_progress_ &&
59      selection_anchor_rect_ != selection_focus_rect_)
60    StartTouchEditing();
61
62  if (text_input_type_ != ui::TEXT_INPUT_TYPE_NONE ||
63      selection_anchor_rect_ != selection_focus_rect_) {
64    if (touch_selection_controller_)
65      touch_selection_controller_->SelectionChanged();
66  } else {
67    EndTouchEditing(false);
68  }
69}
70
71void TouchEditableImplAura::OverscrollStarted() {
72  overscroll_in_progress_ = true;
73}
74
75void TouchEditableImplAura::OverscrollCompleted() {
76  // We might receive multiple OverscrollStarted() and OverscrollCompleted()
77  // during the same scroll session (for example, when the scroll direction
78  // changes). We want to show the handles only when:
79  // 1. Overscroll has completed
80  // 2. Scrolling session is over, i.e. we have received ET_GESTURE_SCROLL_END.
81  // 3. If we had hidden the handles when scrolling started
82  // 4. If there is still a need to show handles (there is a non-empty selection
83  // or non-NONE |text_input_type_|)
84  if (overscroll_in_progress_ && !scroll_in_progress_ &&
85      handles_hidden_due_to_scroll_ &&
86      (selection_anchor_rect_ != selection_focus_rect_ ||
87          text_input_type_ != ui::TEXT_INPUT_TYPE_NONE)) {
88    StartTouchEditing();
89    UpdateEditingController();
90  }
91  overscroll_in_progress_ = false;
92}
93
94////////////////////////////////////////////////////////////////////////////////
95// TouchEditableImplAura, RenderWidgetHostViewAura::TouchEditingClient
96// implementation:
97
98void TouchEditableImplAura::StartTouchEditing() {
99  if (!rwhva_ || !rwhva_->HasFocus())
100    return;
101
102  if (!touch_selection_controller_) {
103    touch_selection_controller_.reset(
104        ui::TouchSelectionController::create(this));
105  }
106  if (touch_selection_controller_)
107    touch_selection_controller_->SelectionChanged();
108}
109
110void TouchEditableImplAura::EndTouchEditing(bool quick) {
111  if (touch_selection_controller_) {
112    if (touch_selection_controller_->IsHandleDragInProgress()) {
113      touch_selection_controller_->SelectionChanged();
114    } else {
115      touch_selection_controller_->HideHandles(quick);
116      touch_selection_controller_.reset();
117    }
118  }
119}
120
121void TouchEditableImplAura::OnSelectionOrCursorChanged(const gfx::Rect& anchor,
122                                                       const gfx::Rect& focus) {
123  selection_anchor_rect_ = anchor;
124  selection_focus_rect_ = focus;
125  UpdateEditingController();
126}
127
128void TouchEditableImplAura::OnTextInputTypeChanged(ui::TextInputType type) {
129  text_input_type_ = type;
130}
131
132bool TouchEditableImplAura::HandleInputEvent(const ui::Event* event) {
133  DCHECK(rwhva_);
134  if (event->IsTouchEvent())
135    return false;
136
137  if (!event->IsGestureEvent()) {
138    EndTouchEditing(false);
139    return false;
140  }
141
142  const ui::GestureEvent* gesture_event =
143      static_cast<const ui::GestureEvent*>(event);
144  switch (event->type()) {
145    case ui::ET_GESTURE_TAP:
146      tap_gesture_tap_count_queue_.push(gesture_event->details().tap_count());
147      if (gesture_event->details().tap_count() > 1)
148        selection_gesture_in_process_ = true;
149      // When the user taps, we want to show touch editing handles if user
150      // tapped on selected text.
151      if (selection_anchor_rect_ != selection_focus_rect_) {
152        // UnionRects only works for rects with non-zero width.
153        gfx::Rect anchor(selection_anchor_rect_.origin(),
154                         gfx::Size(1, selection_anchor_rect_.height()));
155        gfx::Rect focus(selection_focus_rect_.origin(),
156                        gfx::Size(1, selection_focus_rect_.height()));
157        gfx::Rect selection_rect = gfx::UnionRects(anchor, focus);
158        if (selection_rect.Contains(gesture_event->location())) {
159          StartTouchEditing();
160          return true;
161        }
162      }
163      // For single taps, not inside selected region, we want to show handles
164      // only when the tap is on an already focused textfield.
165      is_tap_on_focused_textfield_ = false;
166      if (gesture_event->details().tap_count() == 1 &&
167          text_input_type_ != ui::TEXT_INPUT_TYPE_NONE)
168        is_tap_on_focused_textfield_ = true;
169      break;
170    case ui::ET_GESTURE_LONG_PRESS:
171      selection_gesture_in_process_ = true;
172      break;
173    case ui::ET_GESTURE_SCROLL_BEGIN:
174      // If selection handles are currently visible, we want to get them back up
175      // when scrolling ends. So we set |handles_hidden_due_to_scroll_| so that
176      // we can re-start touch editing when we call |UpdateEditingController()|
177      // on scroll end gesture.
178      scroll_in_progress_ = true;
179      handles_hidden_due_to_scroll_ = false;
180      if (touch_selection_controller_)
181        handles_hidden_due_to_scroll_ = true;
182      EndTouchEditing(true);
183      break;
184    case ui::ET_GESTURE_SCROLL_END:
185      // Scroll has ended, but we might still be in overscroll animation.
186      if (handles_hidden_due_to_scroll_ && !overscroll_in_progress_ &&
187          (selection_anchor_rect_ != selection_focus_rect_ ||
188              text_input_type_ != ui::TEXT_INPUT_TYPE_NONE)) {
189        StartTouchEditing();
190        UpdateEditingController();
191      }
192      // fall through to reset |scroll_in_progress_|.
193    case ui::ET_SCROLL_FLING_START:
194      selection_gesture_in_process_ = false;
195      scroll_in_progress_ = false;
196      break;
197    default:
198      break;
199  }
200  return false;
201}
202
203void TouchEditableImplAura::GestureEventAck(int gesture_event_type) {
204  DCHECK(rwhva_);
205  if (gesture_event_type == blink::WebInputEvent::GestureTap &&
206      text_input_type_ != ui::TEXT_INPUT_TYPE_NONE &&
207      is_tap_on_focused_textfield_) {
208    StartTouchEditing();
209    if (touch_selection_controller_)
210      touch_selection_controller_->SelectionChanged();
211  }
212
213  if (gesture_event_type == blink::WebInputEvent::GestureLongPress)
214    selection_gesture_in_process_ = false;
215  if (gesture_event_type == blink::WebInputEvent::GestureTap) {
216    if (tap_gesture_tap_count_queue_.front() > 1)
217      selection_gesture_in_process_ = false;
218    tap_gesture_tap_count_queue_.pop();
219  }
220}
221
222void TouchEditableImplAura::OnViewDestroyed() {
223  Cleanup();
224}
225
226////////////////////////////////////////////////////////////////////////////////
227// TouchEditableImplAura, ui::TouchEditable implementation:
228
229void TouchEditableImplAura::SelectRect(const gfx::Point& start,
230                                       const gfx::Point& end) {
231  RenderWidgetHost* host = rwhva_->GetRenderWidgetHost();
232  RenderViewHost* rvh = RenderViewHost::From(host);
233  WebContentsImpl* wc =
234      static_cast<WebContentsImpl*>(WebContents::FromRenderViewHost(rvh));
235  wc->SelectRange(start, end);
236}
237
238void TouchEditableImplAura::MoveCaretTo(const gfx::Point& point) {
239  if (!rwhva_)
240    return;
241
242  RenderWidgetHostImpl* host = RenderWidgetHostImpl::From(
243      rwhva_->GetRenderWidgetHost());
244  host->MoveCaret(point);
245}
246
247void TouchEditableImplAura::GetSelectionEndPoints(gfx::Rect* p1,
248                                                  gfx::Rect* p2) {
249  *p1 = selection_anchor_rect_;
250  *p2 = selection_focus_rect_;
251}
252
253gfx::Rect TouchEditableImplAura::GetBounds() {
254  return rwhva_ ? gfx::Rect(rwhva_->GetNativeView()->bounds().size()) :
255      gfx::Rect();
256}
257
258gfx::NativeView TouchEditableImplAura::GetNativeView() const {
259  return rwhva_ ? rwhva_->GetNativeView()->GetToplevelWindow() : NULL;
260}
261
262void TouchEditableImplAura::ConvertPointToScreen(gfx::Point* point) {
263  if (!rwhva_)
264    return;
265  aura::Window* window = rwhva_->GetNativeView();
266  aura::client::ScreenPositionClient* screen_position_client =
267      aura::client::GetScreenPositionClient(window->GetRootWindow());
268  if (screen_position_client)
269    screen_position_client->ConvertPointToScreen(window, point);
270}
271
272void TouchEditableImplAura::ConvertPointFromScreen(gfx::Point* point) {
273  if (!rwhva_)
274    return;
275  aura::Window* window = rwhva_->GetNativeView();
276  aura::client::ScreenPositionClient* screen_position_client =
277      aura::client::GetScreenPositionClient(window->GetRootWindow());
278  if (screen_position_client)
279    screen_position_client->ConvertPointFromScreen(window, point);
280}
281
282bool TouchEditableImplAura::DrawsHandles() {
283  return false;
284}
285
286void TouchEditableImplAura::OpenContextMenu(const gfx::Point& anchor) {
287  if (!rwhva_)
288    return;
289  gfx::Point point = anchor;
290  ConvertPointFromScreen(&point);
291  RenderWidgetHost* host = rwhva_->GetRenderWidgetHost();
292  host->Send(new ViewMsg_ShowContextMenu(host->GetRoutingID(), point));
293  EndTouchEditing(false);
294}
295
296bool TouchEditableImplAura::IsCommandIdChecked(int command_id) const {
297  NOTREACHED();
298  return false;
299}
300
301bool TouchEditableImplAura::IsCommandIdEnabled(int command_id) const {
302  if (!rwhva_)
303    return false;
304  bool editable = rwhva_->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE;
305  gfx::Range selection_range;
306  rwhva_->GetSelectionRange(&selection_range);
307  bool has_selection = !selection_range.is_empty();
308  switch (command_id) {
309    case IDS_APP_CUT:
310      return editable && has_selection;
311    case IDS_APP_COPY:
312      return has_selection;
313    case IDS_APP_PASTE: {
314      base::string16 result;
315      ui::Clipboard::GetForCurrentThread()->ReadText(
316          ui::CLIPBOARD_TYPE_COPY_PASTE, &result);
317      return editable && !result.empty();
318    }
319    case IDS_APP_DELETE:
320      return editable && has_selection;
321    case IDS_APP_SELECT_ALL:
322      return true;
323    default:
324      return false;
325  }
326}
327
328bool TouchEditableImplAura::GetAcceleratorForCommandId(
329    int command_id,
330    ui::Accelerator* accelerator) {
331  return false;
332}
333
334void TouchEditableImplAura::ExecuteCommand(int command_id, int event_flags) {
335  RenderWidgetHost* host = rwhva_->GetRenderWidgetHost();
336  RenderViewHost* rvh = RenderViewHost::From(host);
337  WebContents* wc = WebContents::FromRenderViewHost(rvh);
338
339  switch (command_id) {
340    case IDS_APP_CUT:
341      wc->Cut();
342      break;
343    case IDS_APP_COPY:
344      wc->Copy();
345      break;
346    case IDS_APP_PASTE:
347      wc->Paste();
348      break;
349    case IDS_APP_DELETE:
350      wc->Delete();
351      break;
352    case IDS_APP_SELECT_ALL:
353      wc->SelectAll();
354      break;
355    default:
356      NOTREACHED();
357      break;
358  }
359  EndTouchEditing(false);
360}
361
362////////////////////////////////////////////////////////////////////////////////
363// TouchEditableImplAura, private:
364
365TouchEditableImplAura::TouchEditableImplAura()
366    : text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
367      rwhva_(NULL),
368      selection_gesture_in_process_(false),
369      handles_hidden_due_to_scroll_(false),
370      scroll_in_progress_(false),
371      overscroll_in_progress_(false),
372      is_tap_on_focused_textfield_(false) {
373}
374
375void TouchEditableImplAura::Cleanup() {
376  if (rwhva_) {
377    rwhva_->set_touch_editing_client(NULL);
378    rwhva_ = NULL;
379  }
380  text_input_type_ = ui::TEXT_INPUT_TYPE_NONE;
381  EndTouchEditing(true);
382  handles_hidden_due_to_scroll_ = false;
383  scroll_in_progress_ = false;
384  overscroll_in_progress_ = false;
385}
386
387}  // namespace content
388