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