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 "ui/aura/client/screen_position_client.h" 14 #include "ui/aura/window.h" 15 #include "ui/aura/window_tree_host.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 #include "ui/strings/grit/ui_strings.h" 20 #include "ui/wm/public/activation_client.h" 21 22 namespace content { 23 24 //////////////////////////////////////////////////////////////////////////////// 25 // TouchEditableImplAura, public: 26 27 TouchEditableImplAura::~TouchEditableImplAura() { 28 Cleanup(); 29 } 30 31 // static 32 TouchEditableImplAura* TouchEditableImplAura::Create() { 33 if (switches::IsTouchEditingEnabled()) 34 return new TouchEditableImplAura(); 35 return NULL; 36 } 37 38 void 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 50 void 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 63 void TouchEditableImplAura::OverscrollStarted() { 64 scrolls_in_progress_++; 65 } 66 67 void TouchEditableImplAura::OverscrollCompleted() { 68 ScrollEnded(); 69 } 70 71 //////////////////////////////////////////////////////////////////////////////// 72 // TouchEditableImplAura, RenderWidgetHostViewAura::TouchEditingClient 73 // implementation: 74 75 void TouchEditableImplAura::StartTouchEditing() { 76 if (!rwhva_ || !rwhva_->HasFocus()) 77 return; 78 79 if (!touch_selection_controller_) { 80 touch_selection_controller_.reset( 81 ui::TouchSelectionController::create(this)); 82 } 83 if (touch_selection_controller_) 84 touch_selection_controller_->SelectionChanged(); 85 } 86 87 void TouchEditableImplAura::EndTouchEditing(bool quick) { 88 if (touch_selection_controller_) { 89 if (touch_selection_controller_->IsHandleDragInProgress()) { 90 touch_selection_controller_->SelectionChanged(); 91 } else { 92 selection_gesture_in_process_ = false; 93 touch_selection_controller_->HideHandles(quick); 94 touch_selection_controller_.reset(); 95 } 96 } 97 } 98 99 void TouchEditableImplAura::OnSelectionOrCursorChanged(const gfx::Rect& anchor, 100 const gfx::Rect& focus) { 101 selection_anchor_rect_ = anchor; 102 selection_focus_rect_ = focus; 103 104 // If touch editing handles were not visible, we bring them up only if the 105 // current event is a gesture event, no scroll/fling/overscoll is in progress, 106 // and there is non-zero selection on the page 107 if (selection_gesture_in_process_ && !scrolls_in_progress_ && 108 selection_anchor_rect_ != selection_focus_rect_) { 109 StartTouchEditing(); 110 selection_gesture_in_process_ = false; 111 } 112 113 UpdateEditingController(); 114 } 115 116 void TouchEditableImplAura::OnTextInputTypeChanged(ui::TextInputType type) { 117 text_input_type_ = type; 118 } 119 120 bool TouchEditableImplAura::HandleInputEvent(const ui::Event* event) { 121 DCHECK(rwhva_); 122 if (!event->IsGestureEvent()) { 123 // Ignore all non-gesture events. Non-gesture events that can deactivate 124 // touch editing are handled in TouchSelectionControllerImpl. 125 return false; 126 } 127 128 const ui::GestureEvent* gesture_event = 129 static_cast<const ui::GestureEvent*>(event); 130 switch (event->type()) { 131 case ui::ET_GESTURE_TAP: 132 // When the user taps, we want to show touch editing handles if user 133 // tapped on selected text. 134 if (gesture_event->details().tap_count() == 1 && 135 selection_anchor_rect_ != selection_focus_rect_) { 136 // UnionRects only works for rects with non-zero width. 137 gfx::Rect anchor(selection_anchor_rect_.origin(), 138 gfx::Size(1, selection_anchor_rect_.height())); 139 gfx::Rect focus(selection_focus_rect_.origin(), 140 gfx::Size(1, selection_focus_rect_.height())); 141 gfx::Rect selection_rect = gfx::UnionRects(anchor, focus); 142 if (selection_rect.Contains(gesture_event->location())) { 143 StartTouchEditing(); 144 return true; 145 } 146 } 147 // For single taps, not inside selected region, we want to show handles 148 // only when the tap is on an already focused textfield. 149 textfield_was_focused_on_tap_ = 150 gesture_event->details().tap_count() == 1 && 151 text_input_type_ != ui::TEXT_INPUT_TYPE_NONE; 152 break; 153 case ui::ET_GESTURE_LONG_PRESS: 154 selection_gesture_in_process_ = true; 155 break; 156 case ui::ET_GESTURE_SCROLL_BEGIN: 157 scrolls_in_progress_++; 158 // We need to hide selection handles during scroll (including fling and 159 // overscroll), but they should be re-activated after scrolling if: 160 // - an existing scroll decided that handles should be shown after 161 // scrolling; or 162 // - the gesture in progress is going to end in selection; or 163 // - selection handles are currently active. 164 handles_hidden_due_to_scroll_ = handles_hidden_due_to_scroll_ || 165 selection_gesture_in_process_ || 166 touch_selection_controller_ != NULL; 167 selection_gesture_in_process_ = false; 168 EndTouchEditing(true); 169 break; 170 case ui::ET_GESTURE_SCROLL_END: 171 ScrollEnded(); 172 break; 173 default: 174 break; 175 } 176 return false; 177 } 178 179 void TouchEditableImplAura::GestureEventAck(int gesture_event_type) { 180 DCHECK(rwhva_); 181 if (gesture_event_type == blink::WebInputEvent::GestureTap && 182 text_input_type_ != ui::TEXT_INPUT_TYPE_NONE && 183 textfield_was_focused_on_tap_) { 184 StartTouchEditing(); 185 UpdateEditingController(); 186 } 187 } 188 189 void TouchEditableImplAura::DidStopFlinging() { 190 ScrollEnded(); 191 } 192 193 void TouchEditableImplAura::OnViewDestroyed() { 194 Cleanup(); 195 } 196 197 //////////////////////////////////////////////////////////////////////////////// 198 // TouchEditableImplAura, ui::TouchEditable implementation: 199 200 void TouchEditableImplAura::SelectRect(const gfx::Point& start, 201 const gfx::Point& end) { 202 RenderWidgetHost* host = rwhva_->GetRenderWidgetHost(); 203 RenderViewHost* rvh = RenderViewHost::From(host); 204 WebContentsImpl* wc = 205 static_cast<WebContentsImpl*>(WebContents::FromRenderViewHost(rvh)); 206 wc->SelectRange(start, end); 207 } 208 209 void TouchEditableImplAura::MoveCaretTo(const gfx::Point& point) { 210 if (!rwhva_) 211 return; 212 213 RenderWidgetHostImpl* host = RenderWidgetHostImpl::From( 214 rwhva_->GetRenderWidgetHost()); 215 host->MoveCaret(point); 216 } 217 218 void TouchEditableImplAura::GetSelectionEndPoints(gfx::Rect* p1, 219 gfx::Rect* p2) { 220 *p1 = selection_anchor_rect_; 221 *p2 = selection_focus_rect_; 222 } 223 224 gfx::Rect TouchEditableImplAura::GetBounds() { 225 return rwhva_ ? gfx::Rect(rwhva_->GetNativeView()->bounds().size()) : 226 gfx::Rect(); 227 } 228 229 gfx::NativeView TouchEditableImplAura::GetNativeView() const { 230 return rwhva_ ? rwhva_->GetNativeView()->GetToplevelWindow() : NULL; 231 } 232 233 void TouchEditableImplAura::ConvertPointToScreen(gfx::Point* point) { 234 if (!rwhva_) 235 return; 236 aura::Window* window = rwhva_->GetNativeView(); 237 aura::client::ScreenPositionClient* screen_position_client = 238 aura::client::GetScreenPositionClient(window->GetRootWindow()); 239 if (screen_position_client) 240 screen_position_client->ConvertPointToScreen(window, point); 241 } 242 243 void TouchEditableImplAura::ConvertPointFromScreen(gfx::Point* point) { 244 if (!rwhva_) 245 return; 246 aura::Window* window = rwhva_->GetNativeView(); 247 aura::client::ScreenPositionClient* screen_position_client = 248 aura::client::GetScreenPositionClient(window->GetRootWindow()); 249 if (screen_position_client) 250 screen_position_client->ConvertPointFromScreen(window, point); 251 } 252 253 bool TouchEditableImplAura::DrawsHandles() { 254 return false; 255 } 256 257 void TouchEditableImplAura::OpenContextMenu(const gfx::Point& anchor) { 258 if (!rwhva_) 259 return; 260 gfx::Point point = anchor; 261 ConvertPointFromScreen(&point); 262 RenderWidgetHost* host = rwhva_->GetRenderWidgetHost(); 263 host->Send(new ViewMsg_ShowContextMenu( 264 host->GetRoutingID(), ui::MENU_SOURCE_TOUCH_EDIT_MENU, point)); 265 EndTouchEditing(false); 266 } 267 268 bool TouchEditableImplAura::IsCommandIdChecked(int command_id) const { 269 NOTREACHED(); 270 return false; 271 } 272 273 bool TouchEditableImplAura::IsCommandIdEnabled(int command_id) const { 274 if (!rwhva_) 275 return false; 276 bool editable = rwhva_->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE; 277 bool readable = rwhva_->GetTextInputType() != ui::TEXT_INPUT_TYPE_PASSWORD; 278 gfx::Range selection_range; 279 rwhva_->GetSelectionRange(&selection_range); 280 bool has_selection = !selection_range.is_empty(); 281 switch (command_id) { 282 case IDS_APP_CUT: 283 return editable && readable && has_selection; 284 case IDS_APP_COPY: 285 return readable && has_selection; 286 case IDS_APP_PASTE: { 287 base::string16 result; 288 ui::Clipboard::GetForCurrentThread()->ReadText( 289 ui::CLIPBOARD_TYPE_COPY_PASTE, &result); 290 return editable && !result.empty(); 291 } 292 case IDS_APP_DELETE: 293 return editable && has_selection; 294 case IDS_APP_SELECT_ALL: 295 return true; 296 default: 297 return false; 298 } 299 } 300 301 bool TouchEditableImplAura::GetAcceleratorForCommandId( 302 int command_id, 303 ui::Accelerator* accelerator) { 304 return false; 305 } 306 307 void TouchEditableImplAura::ExecuteCommand(int command_id, int event_flags) { 308 RenderWidgetHost* host = rwhva_->GetRenderWidgetHost(); 309 RenderViewHost* rvh = RenderViewHost::From(host); 310 WebContents* wc = WebContents::FromRenderViewHost(rvh); 311 312 switch (command_id) { 313 case IDS_APP_CUT: 314 wc->Cut(); 315 break; 316 case IDS_APP_COPY: 317 wc->Copy(); 318 break; 319 case IDS_APP_PASTE: 320 wc->Paste(); 321 break; 322 case IDS_APP_DELETE: 323 wc->Delete(); 324 break; 325 case IDS_APP_SELECT_ALL: 326 wc->SelectAll(); 327 break; 328 default: 329 NOTREACHED(); 330 break; 331 } 332 EndTouchEditing(false); 333 } 334 335 void TouchEditableImplAura::DestroyTouchSelection() { 336 EndTouchEditing(false); 337 } 338 339 //////////////////////////////////////////////////////////////////////////////// 340 // TouchEditableImplAura, private: 341 342 TouchEditableImplAura::TouchEditableImplAura() 343 : text_input_type_(ui::TEXT_INPUT_TYPE_NONE), 344 rwhva_(NULL), 345 selection_gesture_in_process_(false), 346 handles_hidden_due_to_scroll_(false), 347 scrolls_in_progress_(0), 348 textfield_was_focused_on_tap_(false) { 349 } 350 351 void TouchEditableImplAura::ScrollEnded() { 352 scrolls_in_progress_--; 353 // If there is no scrolling left in progress, show selection handles if they 354 // were hidden due to scroll and there is a selection. 355 if (!scrolls_in_progress_ && handles_hidden_due_to_scroll_ && 356 (selection_anchor_rect_ != selection_focus_rect_ || 357 text_input_type_ != ui::TEXT_INPUT_TYPE_NONE)) { 358 StartTouchEditing(); 359 UpdateEditingController(); 360 handles_hidden_due_to_scroll_ = false; 361 } 362 } 363 364 void TouchEditableImplAura::Cleanup() { 365 if (rwhva_) { 366 rwhva_->set_touch_editing_client(NULL); 367 rwhva_ = NULL; 368 } 369 text_input_type_ = ui::TEXT_INPUT_TYPE_NONE; 370 EndTouchEditing(true); 371 selection_gesture_in_process_ = false; 372 handles_hidden_due_to_scroll_ = false; 373 scrolls_in_progress_ = 0; 374 } 375 376 } // namespace content 377