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 20 namespace content { 21 22 //////////////////////////////////////////////////////////////////////////////// 23 // TouchEditableImplAura, public: 24 25 TouchEditableImplAura::~TouchEditableImplAura() { 26 Cleanup(); 27 } 28 29 // static 30 TouchEditableImplAura* TouchEditableImplAura::Create() { 31 if (switches::IsTouchEditingEnabled()) 32 return new TouchEditableImplAura(); 33 return NULL; 34 } 35 36 void 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 48 void 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 69 void TouchEditableImplAura::OverscrollStarted() { 70 overscroll_in_progress_ = true; 71 } 72 73 void 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 96 void 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 108 void 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 117 void 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 124 void TouchEditableImplAura::OnTextInputTypeChanged(ui::TextInputType type) { 125 text_input_type_ = type; 126 } 127 128 bool 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 199 void 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 218 void TouchEditableImplAura::OnViewDestroyed() { 219 Cleanup(); 220 } 221 222 //////////////////////////////////////////////////////////////////////////////// 223 // TouchEditableImplAura, ui::TouchEditable implementation: 224 225 void 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 235 void 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 244 void TouchEditableImplAura::GetSelectionEndPoints(gfx::Rect* p1, 245 gfx::Rect* p2) { 246 *p1 = selection_anchor_rect_; 247 *p2 = selection_focus_rect_; 248 } 249 250 gfx::Rect TouchEditableImplAura::GetBounds() { 251 return rwhva_ ? rwhva_->GetNativeView()->bounds() : gfx::Rect(); 252 } 253 254 gfx::NativeView TouchEditableImplAura::GetNativeView() { 255 return rwhva_ ? rwhva_->GetNativeView()->GetRootWindow() : NULL; 256 } 257 258 void 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 268 void 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 278 bool TouchEditableImplAura::DrawsHandles() { 279 return false; 280 } 281 282 void 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 292 bool TouchEditableImplAura::IsCommandIdChecked(int command_id) const { 293 NOTREACHED(); 294 return false; 295 } 296 297 bool 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 324 bool TouchEditableImplAura::GetAcceleratorForCommandId( 325 int command_id, 326 ui::Accelerator* accelerator) { 327 return false; 328 } 329 330 void 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 360 TouchEditableImplAura::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 370 void 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