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