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