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