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/range/range.h" 18 #include "ui/base/ui_base_switches_util.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 (!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 105 void 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 114 void 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 121 void TouchEditableImplAura::OnTextInputTypeChanged(ui::TextInputType type) { 122 text_input_type_ = type; 123 } 124 125 bool 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 196 void 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 215 void TouchEditableImplAura::OnViewDestroyed() { 216 Cleanup(); 217 } 218 219 //////////////////////////////////////////////////////////////////////////////// 220 // TouchEditableImplAura, ui::TouchEditable implementation: 221 222 void 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 232 void 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 241 void TouchEditableImplAura::GetSelectionEndPoints(gfx::Rect* p1, 242 gfx::Rect* p2) { 243 *p1 = selection_anchor_rect_; 244 *p2 = selection_focus_rect_; 245 } 246 247 gfx::Rect TouchEditableImplAura::GetBounds() { 248 return rwhva_ ? rwhva_->GetNativeView()->bounds() : gfx::Rect(); 249 } 250 251 gfx::NativeView TouchEditableImplAura::GetNativeView() { 252 return rwhva_ ? rwhva_->GetNativeView()->GetRootWindow() : NULL; 253 } 254 255 void 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 265 void 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 275 bool TouchEditableImplAura::DrawsHandles() { 276 return false; 277 } 278 279 void 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 289 bool TouchEditableImplAura::IsCommandIdChecked(int command_id) const { 290 NOTREACHED(); 291 return false; 292 } 293 294 bool TouchEditableImplAura::IsCommandIdEnabled(int command_id) const { 295 if (!rwhva_) 296 return false; 297 bool editable = rwhva_->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE; 298 ui::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::BUFFER_STANDARD, &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 321 bool TouchEditableImplAura::GetAcceleratorForCommandId( 322 int command_id, 323 ui::Accelerator* accelerator) { 324 return false; 325 } 326 327 void 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 357 TouchEditableImplAura::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 367 void 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