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