1 // Copyright (c) 2012 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 "base/command_line.h" 6 #include "base/strings/utf_string_conversions.h" 7 #include "grit/ui_resources.h" 8 #include "ui/aura/client/screen_position_client.h" 9 #include "ui/aura/test/event_generator.h" 10 #include "ui/aura/window.h" 11 #include "ui/base/resource/resource_bundle.h" 12 #include "ui/base/touch/touch_editing_controller.h" 13 #include "ui/base/ui_base_switches.h" 14 #include "ui/gfx/canvas.h" 15 #include "ui/gfx/point.h" 16 #include "ui/gfx/rect.h" 17 #include "ui/gfx/render_text.h" 18 #include "ui/views/controls/textfield/textfield.h" 19 #include "ui/views/controls/textfield/textfield_test_api.h" 20 #include "ui/views/test/views_test_base.h" 21 #include "ui/views/touchui/touch_selection_controller_impl.h" 22 #include "ui/views/views_touch_selection_controller_factory.h" 23 #include "ui/views/widget/widget.h" 24 25 using base::ASCIIToUTF16; 26 using base::UTF16ToUTF8; 27 using base::WideToUTF16; 28 29 namespace { 30 // Should match kSelectionHandlePadding in touch_selection_controller. 31 const int kPadding = 10; 32 33 // Should match kSelectionHandleBarMinHeight in touch_selection_controller. 34 const int kBarMinHeight = 5; 35 36 // Should match kSelectionHandleBarBottomAllowance in 37 // touch_selection_controller. 38 const int kBarBottomAllowance = 3; 39 40 // Should match kMenuButtonWidth in touch_editing_menu. 41 const int kMenuButtonWidth = 63; 42 43 // Should match size of kMenuCommands array in touch_editing_menu. 44 const int kMenuCommandCount = 3; 45 46 gfx::Image* GetHandleImage() { 47 static gfx::Image* handle_image = NULL; 48 if (!handle_image) { 49 handle_image = &ui::ResourceBundle::GetSharedInstance().GetImageNamed( 50 IDR_TEXT_SELECTION_HANDLE); 51 } 52 return handle_image; 53 } 54 55 gfx::Size GetHandleImageSize() { 56 return GetHandleImage()->Size(); 57 } 58 } // namespace 59 60 namespace views { 61 62 class TouchSelectionControllerImplTest : public ViewsTestBase { 63 public: 64 TouchSelectionControllerImplTest() 65 : textfield_widget_(NULL), 66 widget_(NULL), 67 textfield_(NULL), 68 views_tsc_factory_(new ViewsTouchSelectionControllerFactory) { 69 CommandLine::ForCurrentProcess()->AppendSwitch( 70 switches::kEnableTouchEditing); 71 ui::TouchSelectionControllerFactory::SetInstance(views_tsc_factory_.get()); 72 } 73 74 virtual ~TouchSelectionControllerImplTest() { 75 ui::TouchSelectionControllerFactory::SetInstance(NULL); 76 } 77 78 virtual void TearDown() { 79 if (textfield_widget_ && !textfield_widget_->IsClosed()) 80 textfield_widget_->Close(); 81 if (widget_ && !widget_->IsClosed()) 82 widget_->Close(); 83 ViewsTestBase::TearDown(); 84 } 85 86 void CreateTextfield() { 87 textfield_ = new Textfield(); 88 textfield_widget_ = new Widget; 89 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP); 90 params.bounds = gfx::Rect(0, 0, 200, 200); 91 textfield_widget_->Init(params); 92 View* container = new View(); 93 textfield_widget_->SetContentsView(container); 94 container->AddChildView(textfield_); 95 96 textfield_->SetBoundsRect(gfx::Rect(0, 0, 200, 20)); 97 textfield_->set_id(1); 98 textfield_widget_->Show(); 99 100 textfield_->RequestFocus(); 101 102 textfield_test_api_.reset(new TextfieldTestApi(textfield_)); 103 } 104 105 void CreateWidget() { 106 widget_ = new Widget; 107 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP); 108 params.bounds = gfx::Rect(0, 0, 200, 200); 109 widget_->Init(params); 110 widget_->Show(); 111 } 112 113 protected: 114 static bool IsCursorHandleVisibleFor( 115 ui::TouchSelectionController* controller) { 116 TouchSelectionControllerImpl* impl = 117 static_cast<TouchSelectionControllerImpl*>(controller); 118 return impl->IsCursorHandleVisible(); 119 } 120 121 gfx::Rect GetCursorRect(const gfx::SelectionModel& sel) { 122 return textfield_test_api_->GetRenderText()->GetCursorBounds(sel, true); 123 } 124 125 gfx::Point GetCursorPosition(const gfx::SelectionModel& sel) { 126 gfx::Rect cursor_bounds = GetCursorRect(sel); 127 return gfx::Point(cursor_bounds.x(), cursor_bounds.y()); 128 } 129 130 TouchSelectionControllerImpl* GetSelectionController() { 131 return static_cast<TouchSelectionControllerImpl*>( 132 textfield_test_api_->touch_selection_controller()); 133 } 134 135 void StartTouchEditing() { 136 textfield_test_api_->CreateTouchSelectionControllerAndNotifyIt(); 137 } 138 139 void EndTouchEditing() { 140 textfield_test_api_->ResetTouchSelectionController(); 141 } 142 143 void SimulateSelectionHandleDrag(gfx::Point p, int selection_handle) { 144 TouchSelectionControllerImpl* controller = GetSelectionController(); 145 // Do the work of OnMousePressed(). 146 if (selection_handle == 1) 147 controller->SetDraggingHandle(controller->selection_handle_1_.get()); 148 else 149 controller->SetDraggingHandle(controller->selection_handle_2_.get()); 150 151 // Offset the drag position by the selection handle radius since it is 152 // supposed to be in the coordinate system of the handle. 153 p.Offset(GetHandleImageSize().width() / 2 + kPadding, 0); 154 controller->SelectionHandleDragged(p); 155 156 // Do the work of OnMouseReleased(). 157 controller->dragging_handle_ = NULL; 158 } 159 160 gfx::NativeView GetCursorHandleNativeView() { 161 return GetSelectionController()->GetCursorHandleNativeView(); 162 } 163 164 gfx::Point GetSelectionHandle1Position() { 165 return GetSelectionController()->GetSelectionHandle1Position(); 166 } 167 168 gfx::Point GetSelectionHandle2Position() { 169 return GetSelectionController()->GetSelectionHandle2Position(); 170 } 171 172 gfx::Point GetCursorHandlePosition() { 173 return GetSelectionController()->GetCursorHandlePosition(); 174 } 175 176 bool IsSelectionHandle1Visible() { 177 return GetSelectionController()->IsSelectionHandle1Visible(); 178 } 179 180 bool IsSelectionHandle2Visible() { 181 return GetSelectionController()->IsSelectionHandle2Visible(); 182 } 183 184 bool IsCursorHandleVisible() { 185 return GetSelectionController()->IsCursorHandleVisible(); 186 } 187 188 gfx::RenderText* GetRenderText() { 189 return textfield_test_api_->GetRenderText(); 190 } 191 192 gfx::Point GetCursorHandleDragPoint() { 193 gfx::Point point = GetCursorHandlePosition(); 194 const gfx::SelectionModel& sel = textfield_->GetSelectionModel(); 195 int cursor_height = GetCursorRect(sel).height(); 196 point.Offset(GetHandleImageSize().width() / 2 + kPadding, 197 GetHandleImageSize().height() / 2 + cursor_height); 198 return point; 199 } 200 201 Widget* textfield_widget_; 202 Widget* widget_; 203 204 Textfield* textfield_; 205 scoped_ptr<TextfieldTestApi> textfield_test_api_; 206 scoped_ptr<ViewsTouchSelectionControllerFactory> views_tsc_factory_; 207 208 private: 209 DISALLOW_COPY_AND_ASSIGN(TouchSelectionControllerImplTest); 210 }; 211 212 // If textfield has selection, this macro verifies that the selection handles 213 // are visible and at the correct positions (at the end points of selection). 214 // |cursor_at_selection_handle_1| is used to decide whether selection 215 // handle 1's position is matched against the start of selection or the end. 216 #define VERIFY_HANDLE_POSITIONS(cursor_at_selection_handle_1) \ 217 { \ 218 gfx::SelectionModel sel = textfield_->GetSelectionModel(); \ 219 if (textfield_->HasSelection()) { \ 220 EXPECT_TRUE(IsSelectionHandle1Visible()); \ 221 EXPECT_TRUE(IsSelectionHandle2Visible()); \ 222 EXPECT_FALSE(IsCursorHandleVisible()); \ 223 gfx::SelectionModel sel_start = GetRenderText()-> \ 224 GetSelectionModelForSelectionStart(); \ 225 gfx::Point selection_start = GetCursorPosition(sel_start); \ 226 gfx::Point selection_end = GetCursorPosition(sel); \ 227 gfx::Point sh1 = GetSelectionHandle1Position(); \ 228 gfx::Point sh2 = GetSelectionHandle2Position(); \ 229 sh1.Offset(GetHandleImageSize().width() / 2 + kPadding, 0); \ 230 sh2.Offset(GetHandleImageSize().width() / 2 + kPadding, 0); \ 231 if (cursor_at_selection_handle_1) { \ 232 EXPECT_EQ(sh1, selection_end); \ 233 EXPECT_EQ(sh2, selection_start); \ 234 } else { \ 235 EXPECT_EQ(sh1, selection_start); \ 236 EXPECT_EQ(sh2, selection_end); \ 237 } \ 238 } else { \ 239 EXPECT_FALSE(IsSelectionHandle1Visible()); \ 240 EXPECT_FALSE(IsSelectionHandle2Visible()); \ 241 EXPECT_TRUE(IsCursorHandleVisible()); \ 242 gfx::Point cursor_pos = GetCursorPosition(sel); \ 243 gfx::Point ch_pos = GetCursorHandlePosition(); \ 244 ch_pos.Offset(GetHandleImageSize().width() / 2 + kPadding, 0); \ 245 EXPECT_EQ(ch_pos, cursor_pos); \ 246 } \ 247 } 248 249 // Tests that the selection handles are placed appropriately when selection in 250 // a Textfield changes. 251 TEST_F(TouchSelectionControllerImplTest, SelectionInTextfieldTest) { 252 CreateTextfield(); 253 textfield_->SetText(ASCIIToUTF16("some text")); 254 // Tap the textfield to invoke touch selection. 255 ui::GestureEvent tap(ui::ET_GESTURE_TAP, 0, 0, 0, base::TimeDelta(), 256 ui::GestureEventDetails(ui::ET_GESTURE_TAP, 1.0f, 0.0f), 0); 257 textfield_->OnGestureEvent(&tap); 258 259 // Test selecting a range. 260 textfield_->SelectRange(gfx::Range(3, 7)); 261 VERIFY_HANDLE_POSITIONS(false); 262 263 // Test selecting everything. 264 textfield_->SelectAll(false); 265 VERIFY_HANDLE_POSITIONS(false); 266 267 // Test with no selection. 268 textfield_->ClearSelection(); 269 VERIFY_HANDLE_POSITIONS(false); 270 271 // Test with lost focus. 272 textfield_widget_->GetFocusManager()->ClearFocus(); 273 EXPECT_FALSE(GetSelectionController()); 274 275 // Test with focus re-gained. 276 textfield_widget_->GetFocusManager()->SetFocusedView(textfield_); 277 EXPECT_FALSE(GetSelectionController()); 278 textfield_->OnGestureEvent(&tap); 279 VERIFY_HANDLE_POSITIONS(false); 280 } 281 282 // Tests that the selection handles are placed appropriately in bidi text. 283 TEST_F(TouchSelectionControllerImplTest, SelectionInBidiTextfieldTest) { 284 CreateTextfield(); 285 textfield_->SetText(WideToUTF16(L"abc\x05d0\x05d1\x05d2")); 286 // Tap the textfield to invoke touch selection. 287 ui::GestureEvent tap(ui::ET_GESTURE_TAP, 0, 0, 0, base::TimeDelta(), 288 ui::GestureEventDetails(ui::ET_GESTURE_TAP, 1.0f, 0.0f), 0); 289 textfield_->OnGestureEvent(&tap); 290 291 // Test cursor at run boundary and with empty selection. 292 textfield_->SelectSelectionModel( 293 gfx::SelectionModel(3, gfx::CURSOR_BACKWARD)); 294 VERIFY_HANDLE_POSITIONS(false); 295 296 // Test selection range inside one run and starts or ends at run boundary. 297 textfield_->SelectRange(gfx::Range(2, 3)); 298 VERIFY_HANDLE_POSITIONS(false); 299 300 textfield_->SelectRange(gfx::Range(3, 2)); 301 VERIFY_HANDLE_POSITIONS(false); 302 303 textfield_->SelectRange(gfx::Range(3, 4)); 304 VERIFY_HANDLE_POSITIONS(false); 305 306 textfield_->SelectRange(gfx::Range(4, 3)); 307 VERIFY_HANDLE_POSITIONS(false); 308 309 textfield_->SelectRange(gfx::Range(3, 6)); 310 VERIFY_HANDLE_POSITIONS(false); 311 312 textfield_->SelectRange(gfx::Range(6, 3)); 313 VERIFY_HANDLE_POSITIONS(false); 314 315 // Test selection range accross runs. 316 textfield_->SelectRange(gfx::Range(0, 6)); 317 VERIFY_HANDLE_POSITIONS(false); 318 319 textfield_->SelectRange(gfx::Range(6, 0)); 320 VERIFY_HANDLE_POSITIONS(false); 321 322 textfield_->SelectRange(gfx::Range(1, 4)); 323 VERIFY_HANDLE_POSITIONS(false); 324 325 textfield_->SelectRange(gfx::Range(4, 1)); 326 VERIFY_HANDLE_POSITIONS(false); 327 } 328 329 // Tests if the SelectRect callback is called appropriately when selection 330 // handles are moved. 331 TEST_F(TouchSelectionControllerImplTest, SelectRectCallbackTest) { 332 CreateTextfield(); 333 textfield_->SetText(ASCIIToUTF16("textfield with selected text")); 334 // Tap the textfield to invoke touch selection. 335 ui::GestureEvent tap(ui::ET_GESTURE_TAP, 0, 0, 0, base::TimeDelta(), 336 ui::GestureEventDetails(ui::ET_GESTURE_TAP, 1.0f, 0.0f), 0); 337 textfield_->OnGestureEvent(&tap); 338 textfield_->SelectRange(gfx::Range(3, 7)); 339 340 EXPECT_EQ(UTF16ToUTF8(textfield_->GetSelectedText()), "tfie"); 341 VERIFY_HANDLE_POSITIONS(false); 342 343 // Drag selection handle 2 to right by 3 chars. 344 const gfx::FontList& font_list = textfield_->GetFontList(); 345 int x = gfx::Canvas::GetStringWidth(ASCIIToUTF16("ld "), font_list); 346 SimulateSelectionHandleDrag(gfx::Point(x, 0), 2); 347 EXPECT_EQ(UTF16ToUTF8(textfield_->GetSelectedText()), "tfield "); 348 VERIFY_HANDLE_POSITIONS(false); 349 350 // Drag selection handle 1 to the left by a large amount (selection should 351 // just stick to the beginning of the textfield). 352 SimulateSelectionHandleDrag(gfx::Point(-50, 0), 1); 353 EXPECT_EQ(UTF16ToUTF8(textfield_->GetSelectedText()), "textfield "); 354 VERIFY_HANDLE_POSITIONS(true); 355 356 // Drag selection handle 1 across selection handle 2. 357 x = gfx::Canvas::GetStringWidth(ASCIIToUTF16("textfield with "), font_list); 358 SimulateSelectionHandleDrag(gfx::Point(x, 0), 1); 359 EXPECT_EQ(UTF16ToUTF8(textfield_->GetSelectedText()), "with "); 360 VERIFY_HANDLE_POSITIONS(true); 361 362 // Drag selection handle 2 across selection handle 1. 363 x = gfx::Canvas::GetStringWidth(ASCIIToUTF16("with selected "), font_list); 364 SimulateSelectionHandleDrag(gfx::Point(x, 0), 2); 365 EXPECT_EQ(UTF16ToUTF8(textfield_->GetSelectedText()), "selected "); 366 VERIFY_HANDLE_POSITIONS(false); 367 } 368 369 TEST_F(TouchSelectionControllerImplTest, SelectRectInBidiCallbackTest) { 370 CreateTextfield(); 371 textfield_->SetText(WideToUTF16(L"abc\x05e1\x05e2\x05e3" L"def")); 372 // Tap the textfield to invoke touch selection. 373 ui::GestureEvent tap(ui::ET_GESTURE_TAP, 0, 0, 0, base::TimeDelta(), 374 ui::GestureEventDetails(ui::ET_GESTURE_TAP, 1.0f, 0.0f), 0); 375 textfield_->OnGestureEvent(&tap); 376 377 // Select [c] from left to right. 378 textfield_->SelectRange(gfx::Range(2, 3)); 379 EXPECT_EQ(WideToUTF16(L"c"), textfield_->GetSelectedText()); 380 VERIFY_HANDLE_POSITIONS(false); 381 382 // Drag selection handle 2 to right by 1 char. 383 const gfx::FontList& font_list = textfield_->GetFontList(); 384 int x = gfx::Canvas::GetStringWidth(WideToUTF16(L"\x05e3"), font_list); 385 SimulateSelectionHandleDrag(gfx::Point(x, 0), 2); 386 EXPECT_EQ(WideToUTF16(L"c\x05e1\x05e2"), textfield_->GetSelectedText()); 387 VERIFY_HANDLE_POSITIONS(false); 388 389 // Drag selection handle 1 to left by 1 char. 390 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"b"), font_list); 391 SimulateSelectionHandleDrag(gfx::Point(-x, 0), 1); 392 EXPECT_EQ(WideToUTF16(L"bc\x05e1\x05e2"), textfield_->GetSelectedText()); 393 VERIFY_HANDLE_POSITIONS(true); 394 395 // Select [c] from right to left. 396 textfield_->SelectRange(gfx::Range(3, 2)); 397 EXPECT_EQ(WideToUTF16(L"c"), textfield_->GetSelectedText()); 398 VERIFY_HANDLE_POSITIONS(false); 399 400 // Drag selection handle 1 to right by 1 char. 401 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"\x05e3"), font_list); 402 SimulateSelectionHandleDrag(gfx::Point(x, 0), 1); 403 EXPECT_EQ(WideToUTF16(L"c\x05e1\x05e2"), textfield_->GetSelectedText()); 404 VERIFY_HANDLE_POSITIONS(true); 405 406 // Drag selection handle 2 to left by 1 char. 407 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"b"), font_list); 408 SimulateSelectionHandleDrag(gfx::Point(-x, 0), 2); 409 EXPECT_EQ(WideToUTF16(L"bc\x05e1\x05e2"), textfield_->GetSelectedText()); 410 VERIFY_HANDLE_POSITIONS(false); 411 412 // Select [\x5e1] from right to left. 413 textfield_->SelectRange(gfx::Range(3, 4)); 414 EXPECT_EQ(WideToUTF16(L"\x05e1"), textfield_->GetSelectedText()); 415 VERIFY_HANDLE_POSITIONS(false); 416 417 /* TODO(xji): for bidi text "abcDEF" whose display is "abcFEDhij", when click 418 right of 'D' and select [D] then move the left selection handle to left 419 by one character, it should select [ED], instead it selects [F]. 420 Reason: click right of 'D' and left of 'h' return the same x-axis position, 421 pass this position to FindCursorPosition() returns index of 'h'. which 422 means the selection start changed from 3 to 6. 423 Need further investigation on whether this is a bug in Pango and how to 424 work around it. 425 // Drag selection handle 2 to left by 1 char. 426 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"\x05e2"), font_list); 427 SimulateSelectionHandleDrag(gfx::Point(-x, 0), 2); 428 EXPECT_EQ(WideToUTF16(L"\x05e1\x05e2"), textfield_->GetSelectedText()); 429 VERIFY_HANDLE_POSITIONS(false); 430 */ 431 432 // Drag selection handle 1 to right by 1 char. 433 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"d"), font_list); 434 SimulateSelectionHandleDrag(gfx::Point(x, 0), 1); 435 EXPECT_EQ(WideToUTF16(L"\x05e2\x05e3" L"d"), textfield_->GetSelectedText()); 436 VERIFY_HANDLE_POSITIONS(true); 437 438 // Select [\x5e1] from left to right. 439 textfield_->SelectRange(gfx::Range(4, 3)); 440 EXPECT_EQ(WideToUTF16(L"\x05e1"), textfield_->GetSelectedText()); 441 VERIFY_HANDLE_POSITIONS(false); 442 443 /* TODO(xji): see detail of above commented out test case. 444 // Drag selection handle 1 to left by 1 char. 445 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"\x05e2"), font_list); 446 SimulateSelectionHandleDrag(gfx::Point(-x, 0), 1); 447 EXPECT_EQ(WideToUTF16(L"\x05e1\x05e2"), textfield_->GetSelectedText()); 448 VERIFY_HANDLE_POSITIONS(true); 449 */ 450 451 // Drag selection handle 2 to right by 1 char. 452 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"d"), font_list); 453 SimulateSelectionHandleDrag(gfx::Point(x, 0), 2); 454 EXPECT_EQ(WideToUTF16(L"\x05e2\x05e3" L"d"), textfield_->GetSelectedText()); 455 VERIFY_HANDLE_POSITIONS(false); 456 457 // Select [\x05r3] from right to left. 458 textfield_->SelectRange(gfx::Range(5, 6)); 459 EXPECT_EQ(WideToUTF16(L"\x05e3"), textfield_->GetSelectedText()); 460 VERIFY_HANDLE_POSITIONS(false); 461 462 // Drag selection handle 2 to left by 1 char. 463 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"c"), font_list); 464 SimulateSelectionHandleDrag(gfx::Point(-x, 0), 2); 465 EXPECT_EQ(WideToUTF16(L"c\x05e1\x05e2"), textfield_->GetSelectedText()); 466 VERIFY_HANDLE_POSITIONS(false); 467 468 // Drag selection handle 1 to right by 1 char. 469 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"\x05e2"), font_list); 470 SimulateSelectionHandleDrag(gfx::Point(x, 0), 1); 471 EXPECT_EQ(WideToUTF16(L"c\x05e1"), textfield_->GetSelectedText()); 472 VERIFY_HANDLE_POSITIONS(true); 473 474 // Select [\x05r3] from left to right. 475 textfield_->SelectRange(gfx::Range(6, 5)); 476 EXPECT_EQ(WideToUTF16(L"\x05e3"), textfield_->GetSelectedText()); 477 VERIFY_HANDLE_POSITIONS(false); 478 479 // Drag selection handle 1 to left by 1 char. 480 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"c"), font_list); 481 SimulateSelectionHandleDrag(gfx::Point(-x, 0), 1); 482 EXPECT_EQ(WideToUTF16(L"c\x05e1\x05e2"), textfield_->GetSelectedText()); 483 VERIFY_HANDLE_POSITIONS(true); 484 485 // Drag selection handle 2 to right by 1 char. 486 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"\x05e2"), font_list); 487 SimulateSelectionHandleDrag(gfx::Point(x, 0), 2); 488 EXPECT_EQ(WideToUTF16(L"c\x05e1"), textfield_->GetSelectedText()); 489 VERIFY_HANDLE_POSITIONS(false); 490 } 491 492 TEST_F(TouchSelectionControllerImplTest, 493 HiddenSelectionHandleRetainsCursorPosition) { 494 // Create a textfield with lots of text in it. 495 CreateTextfield(); 496 std::string textfield_text("some text"); 497 for (int i = 0; i < 10; ++i) 498 textfield_text += textfield_text; 499 textfield_->SetText(ASCIIToUTF16(textfield_text)); 500 501 // Tap the textfield to invoke selection. 502 ui::GestureEvent tap(ui::ET_GESTURE_TAP, 0, 0, 0, base::TimeDelta(), 503 ui::GestureEventDetails(ui::ET_GESTURE_TAP, 1.0f, 0.0f), 0); 504 textfield_->OnGestureEvent(&tap); 505 506 // Select some text such that one handle is hidden. 507 textfield_->SelectRange(gfx::Range(10, textfield_text.length())); 508 509 // Check that one selection handle is hidden. 510 EXPECT_FALSE(IsSelectionHandle1Visible()); 511 EXPECT_TRUE(IsSelectionHandle2Visible()); 512 EXPECT_EQ(gfx::Range(10, textfield_text.length()), 513 textfield_->GetSelectedRange()); 514 515 // Drag the visible handle around and make sure the selection end point of the 516 // invisible handle does not change. 517 size_t visible_handle_position = textfield_->GetSelectedRange().end(); 518 for (int i = 0; i < 10; ++i) { 519 SimulateSelectionHandleDrag(gfx::Point(-10, 0), 2); 520 // Make sure that the visible handle is being dragged. 521 EXPECT_NE(visible_handle_position, textfield_->GetSelectedRange().end()); 522 visible_handle_position = textfield_->GetSelectedRange().end(); 523 EXPECT_EQ((size_t) 10, textfield_->GetSelectedRange().start()); 524 } 525 } 526 527 TEST_F(TouchSelectionControllerImplTest, 528 DoubleTapInTextfieldWithCursorHandleShouldSelectText) { 529 CreateTextfield(); 530 textfield_->SetText(ASCIIToUTF16("some text")); 531 aura::test::EventGenerator generator( 532 textfield_->GetWidget()->GetNativeView()->GetRootWindow()); 533 534 // Tap the textfield to invoke touch selection. 535 generator.GestureTapAt(gfx::Point(10, 10)); 536 537 // Cursor handle should be visible. 538 EXPECT_FALSE(textfield_->HasSelection()); 539 VERIFY_HANDLE_POSITIONS(false); 540 541 // Double tap on the cursor handle position. We want to check that the cursor 542 // handle is not eating the event and that the event is falling through to the 543 // textfield. 544 gfx::Point cursor_pos = GetCursorHandlePosition(); 545 cursor_pos.Offset(GetHandleImageSize().width() / 2 + kPadding, 0); 546 generator.GestureTapAt(cursor_pos); 547 generator.GestureTapAt(cursor_pos); 548 EXPECT_TRUE(textfield_->HasSelection()); 549 } 550 551 // A simple implementation of TouchEditable that allows faking cursor position 552 // inside its boundaries. 553 class TestTouchEditable : public ui::TouchEditable { 554 public: 555 explicit TestTouchEditable(aura::Window* window) 556 : window_(window) { 557 DCHECK(window); 558 } 559 560 void set_bounds(const gfx::Rect& bounds) { 561 bounds_ = bounds; 562 } 563 564 void set_cursor_rect(const gfx::Rect& cursor_rect) { 565 cursor_rect_ = cursor_rect; 566 } 567 568 virtual ~TestTouchEditable() {} 569 570 private: 571 // Overridden from ui::TouchEditable. 572 virtual void SelectRect( 573 const gfx::Point& start, const gfx::Point& end) OVERRIDE { 574 NOTREACHED(); 575 } 576 virtual void MoveCaretTo(const gfx::Point& point) OVERRIDE { 577 NOTREACHED(); 578 } 579 virtual void GetSelectionEndPoints(gfx::Rect* p1, gfx::Rect* p2) OVERRIDE { 580 *p1 = *p2 = cursor_rect_; 581 } 582 virtual gfx::Rect GetBounds() OVERRIDE { 583 return gfx::Rect(bounds_.size()); 584 } 585 virtual gfx::NativeView GetNativeView() const OVERRIDE { 586 return window_; 587 } 588 virtual void ConvertPointToScreen(gfx::Point* point) OVERRIDE { 589 aura::client::ScreenPositionClient* screen_position_client = 590 aura::client::GetScreenPositionClient(window_->GetRootWindow()); 591 if (screen_position_client) 592 screen_position_client->ConvertPointToScreen(window_, point); 593 } 594 virtual void ConvertPointFromScreen(gfx::Point* point) OVERRIDE { 595 aura::client::ScreenPositionClient* screen_position_client = 596 aura::client::GetScreenPositionClient(window_->GetRootWindow()); 597 if (screen_position_client) 598 screen_position_client->ConvertPointFromScreen(window_, point); 599 } 600 virtual bool DrawsHandles() OVERRIDE { 601 return false; 602 } 603 virtual void OpenContextMenu(const gfx::Point& anchor) OVERRIDE { 604 NOTREACHED(); 605 } 606 virtual void DestroyTouchSelection() OVERRIDE { 607 NOTREACHED(); 608 } 609 610 // Overridden from ui::SimpleMenuModel::Delegate. 611 virtual bool IsCommandIdChecked(int command_id) const OVERRIDE { 612 NOTREACHED(); 613 return false; 614 } 615 virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE { 616 NOTREACHED(); 617 return false; 618 } 619 virtual bool GetAcceleratorForCommandId( 620 int command_id, 621 ui::Accelerator* accelerator) OVERRIDE { 622 NOTREACHED(); 623 return false; 624 } 625 virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE { 626 NOTREACHED(); 627 } 628 629 aura::Window* window_; 630 631 // Boundaries of the client view. 632 gfx::Rect bounds_; 633 634 // Cursor position inside the client view. 635 gfx::Rect cursor_rect_; 636 637 DISALLOW_COPY_AND_ASSIGN(TestTouchEditable); 638 }; 639 640 // Tests if the touch editing handle is shown or hidden properly according to 641 // the cursor position relative to the client boundaries. 642 TEST_F(TouchSelectionControllerImplTest, 643 VisibilityOfHandleRegardingClientBounds) { 644 CreateWidget(); 645 646 TestTouchEditable touch_editable(widget_->GetNativeView()); 647 scoped_ptr<ui::TouchSelectionController> touch_selection_controller( 648 ui::TouchSelectionController::create(&touch_editable)); 649 650 touch_editable.set_bounds(gfx::Rect(0, 0, 100, 20)); 651 652 // Put the cursor completely inside the client bounds. Handle should be 653 // visible. 654 touch_editable.set_cursor_rect(gfx::Rect(2, 0, 1, 20)); 655 touch_selection_controller->SelectionChanged(); 656 EXPECT_TRUE(IsCursorHandleVisibleFor(touch_selection_controller.get())); 657 658 // Move the cursor up such that |kBarMinHeight| pixels are still in the client 659 // bounds. Handle should still be visible. 660 touch_editable.set_cursor_rect(gfx::Rect(2, kBarMinHeight - 20, 1, 20)); 661 touch_selection_controller->SelectionChanged(); 662 EXPECT_TRUE(IsCursorHandleVisibleFor(touch_selection_controller.get())); 663 664 // Move the cursor up such that less than |kBarMinHeight| pixels are in the 665 // client bounds. Handle should be hidden. 666 touch_editable.set_cursor_rect(gfx::Rect(2, kBarMinHeight - 20 - 1, 1, 20)); 667 touch_selection_controller->SelectionChanged(); 668 EXPECT_FALSE(IsCursorHandleVisibleFor(touch_selection_controller.get())); 669 670 // Move the Cursor down such that |kBarBottomAllowance| pixels are out of the 671 // client bounds. Handle should be visible. 672 touch_editable.set_cursor_rect(gfx::Rect(2, kBarBottomAllowance, 1, 20)); 673 touch_selection_controller->SelectionChanged(); 674 EXPECT_TRUE(IsCursorHandleVisibleFor(touch_selection_controller.get())); 675 676 // Move the cursor down such that more than |kBarBottomAllowance| pixels are 677 // out of the client bounds. Handle should be hidden. 678 touch_editable.set_cursor_rect(gfx::Rect(2, kBarBottomAllowance + 1, 1, 20)); 679 touch_selection_controller->SelectionChanged(); 680 EXPECT_FALSE(IsCursorHandleVisibleFor(touch_selection_controller.get())); 681 682 touch_selection_controller.reset(); 683 } 684 685 TEST_F(TouchSelectionControllerImplTest, HandlesStackAboveParent) { 686 ui::EventTarget* root = GetContext(); 687 ui::EventTargeter* targeter = root->GetEventTargeter(); 688 689 // Create the first window containing a Views::Textfield. 690 CreateTextfield(); 691 aura::Window* window1 = textfield_widget_->GetNativeView(); 692 693 // Start touch editing, check that the handle is above the first window, and 694 // end touch editing. 695 StartTouchEditing(); 696 gfx::Point test_point = GetCursorHandleDragPoint(); 697 ui::MouseEvent test_event1(ui::ET_MOUSE_MOVED, test_point, test_point, 698 ui::EF_NONE, ui::EF_NONE); 699 EXPECT_EQ(GetCursorHandleNativeView(), 700 targeter->FindTargetForEvent(root, &test_event1)); 701 EndTouchEditing(); 702 703 // Create the second (empty) window over the first one. 704 CreateWidget(); 705 aura::Window* window2 = widget_->GetNativeView(); 706 707 // Start touch editing (in the first window) and check that the handle is not 708 // above the second window. 709 StartTouchEditing(); 710 ui::MouseEvent test_event2(ui::ET_MOUSE_MOVED, test_point, test_point, 711 ui::EF_NONE, ui::EF_NONE); 712 EXPECT_EQ(window2, targeter->FindTargetForEvent(root, &test_event2)); 713 714 // Move the first window to top and check that the handle is kept above the 715 // first window. 716 window1->GetRootWindow()->StackChildAtTop(window1); 717 ui::MouseEvent test_event3(ui::ET_MOUSE_MOVED, test_point, test_point, 718 ui::EF_NONE, ui::EF_NONE); 719 EXPECT_EQ(GetCursorHandleNativeView(), 720 targeter->FindTargetForEvent(root, &test_event3)); 721 } 722 723 // A simple implementation of TouchEditingMenuController that enables all 724 // available commands. 725 class TestTouchEditingMenuController : public TouchEditingMenuController { 726 public: 727 TestTouchEditingMenuController() {} 728 virtual ~TestTouchEditingMenuController() {} 729 730 // Overriden from TouchEditingMenuController. 731 virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE { 732 // Return true, since we want the menu to have all |kMenuCommandCount| 733 // available commands. 734 return true; 735 } 736 virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE { 737 NOTREACHED(); 738 } 739 virtual void OpenContextMenu() OVERRIDE { 740 NOTREACHED(); 741 } 742 virtual void OnMenuClosed(TouchEditingMenuView* menu) OVERRIDE {} 743 744 private: 745 DISALLOW_COPY_AND_ASSIGN(TestTouchEditingMenuController); 746 }; 747 748 // Tests if anchor rect for touch editing quick menu is adjusted correctly based 749 // on the distance of handles. 750 TEST_F(TouchSelectionControllerImplTest, QuickMenuAdjustsAnchorRect) { 751 CreateWidget(); 752 aura::Window* window = widget_->GetNativeView(); 753 754 scoped_ptr<TestTouchEditingMenuController> quick_menu_controller( 755 new TestTouchEditingMenuController()); 756 757 // Some arbitrary size for touch editing handle image. 758 gfx::Size handle_image_size(10, 10); 759 760 // Calculate the width of quick menu. In addition to |kMenuCommandCount| 761 // commands, there is an item for ellipsis. 762 int quick_menu_width = (kMenuCommandCount + 1) * kMenuButtonWidth + 763 kMenuCommandCount; 764 765 // Set anchor rect's width a bit smaller than the quick menu width plus handle 766 // image width and check that anchor rect's height is adjusted. 767 gfx::Rect anchor_rect( 768 0, 0, quick_menu_width + handle_image_size.width() - 10, 20); 769 TouchEditingMenuView* quick_menu(TouchEditingMenuView::Create( 770 quick_menu_controller.get(), anchor_rect, handle_image_size, window)); 771 anchor_rect.Inset(0, 0, 0, -handle_image_size.height()); 772 EXPECT_EQ(anchor_rect.ToString(), quick_menu->GetAnchorRect().ToString()); 773 774 // Set anchor rect's width a bit greater than the quick menu width plus handle 775 // image width and check that anchor rect's height is not adjusted. 776 anchor_rect = 777 gfx::Rect(0, 0, quick_menu_width + handle_image_size.width() + 10, 20); 778 quick_menu = TouchEditingMenuView::Create( 779 quick_menu_controller.get(), anchor_rect, handle_image_size, window); 780 EXPECT_EQ(anchor_rect.ToString(), quick_menu->GetAnchorRect().ToString()); 781 782 // Close the widget, hence quick menus, before quick menu controller goes out 783 // of scope. 784 widget_->CloseNow(); 785 widget_ = NULL; 786 } 787 788 TEST_F(TouchSelectionControllerImplTest, MouseEventDeactivatesTouchSelection) { 789 CreateTextfield(); 790 EXPECT_FALSE(GetSelectionController()); 791 792 aura::test::EventGenerator generator( 793 textfield_widget_->GetNativeView()->GetRootWindow()); 794 795 generator.set_current_location(gfx::Point(5, 5)); 796 RunPendingMessages(); 797 798 // Start touch editing; then move mouse over the textfield and ensure it 799 // deactivates touch selection. 800 StartTouchEditing(); 801 EXPECT_TRUE(GetSelectionController()); 802 generator.MoveMouseTo(gfx::Point(5, 10)); 803 RunPendingMessages(); 804 EXPECT_FALSE(GetSelectionController()); 805 806 generator.MoveMouseTo(gfx::Point(5, 50)); 807 RunPendingMessages(); 808 809 // Start touch editing; then move mouse out of the textfield, but inside the 810 // winow and ensure it deactivates touch selection. 811 StartTouchEditing(); 812 EXPECT_TRUE(GetSelectionController()); 813 generator.MoveMouseTo(gfx::Point(5, 55)); 814 RunPendingMessages(); 815 EXPECT_FALSE(GetSelectionController()); 816 817 generator.MoveMouseTo(gfx::Point(5, 500)); 818 RunPendingMessages(); 819 820 // Start touch editing; then move mouse out of the textfield and window and 821 // ensure it deactivates touch selection. 822 StartTouchEditing(); 823 EXPECT_TRUE(GetSelectionController()); 824 generator.MoveMouseTo(5, 505); 825 RunPendingMessages(); 826 EXPECT_FALSE(GetSelectionController()); 827 } 828 829 TEST_F(TouchSelectionControllerImplTest, KeyEventDeactivatesTouchSelection) { 830 CreateTextfield(); 831 EXPECT_FALSE(GetSelectionController()); 832 833 aura::test::EventGenerator generator( 834 textfield_widget_->GetNativeView()->GetRootWindow()); 835 836 RunPendingMessages(); 837 838 // Start touch editing; then press a key and ensure it deactivates touch 839 // selection. 840 StartTouchEditing(); 841 EXPECT_TRUE(GetSelectionController()); 842 generator.PressKey(ui::VKEY_A, 0); 843 RunPendingMessages(); 844 EXPECT_FALSE(GetSelectionController()); 845 } 846 847 } // namespace views 848