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.
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/test/views_test_base.h"
20 #include "ui/views/touchui/touch_selection_controller_impl.h"
21 #include "ui/views/views_touch_selection_controller_factory.h"
22 #include "ui/views/widget/widget.h"
24 using base::ASCIIToUTF16;
25 using base::UTF16ToUTF8;
26 using base::WideToUTF16;
29 // Should match kSelectionHandlePadding in touch_selection_controller.
30 const int kPadding = 10;
32 // Should match kSelectionHandleBarMinHeight in touch_selection_controller.
33 const int kBarMinHeight = 5;
35 // Should match kSelectionHandleBarBottomAllowance in
36 // touch_selection_controller.
37 const int kBarBottomAllowance = 3;
39 // Should match kMenuButtonWidth in touch_editing_menu.
40 const int kMenuButtonWidth = 63;
42 // Should match size of kMenuCommands array in touch_editing_menu.
43 const int kMenuCommandCount = 3;
45 gfx::Image* GetHandleImage() {
46 static gfx::Image* handle_image = NULL;
48 handle_image = &ui::ResourceBundle::GetSharedInstance().GetImageNamed(
49 IDR_TEXT_SELECTION_HANDLE);
54 gfx::Size GetHandleImageSize() {
55 return GetHandleImage()->Size();
61 class TouchSelectionControllerImplTest : public ViewsTestBase {
63 TouchSelectionControllerImplTest()
64 : textfield_widget_(NULL),
67 views_tsc_factory_(new ViewsTouchSelectionControllerFactory) {
68 CommandLine::ForCurrentProcess()->AppendSwitch(
69 switches::kEnableTouchEditing);
70 ui::TouchSelectionControllerFactory::SetInstance(views_tsc_factory_.get());
73 virtual ~TouchSelectionControllerImplTest() {
74 ui::TouchSelectionControllerFactory::SetInstance(NULL);
77 virtual void TearDown() {
78 if (textfield_widget_ && !textfield_widget_->IsClosed())
79 textfield_widget_->Close();
80 if (widget_ && !widget_->IsClosed())
82 ViewsTestBase::TearDown();
85 void CreateTextfield() {
86 textfield_ = new Textfield();
87 textfield_widget_ = new Widget;
88 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
89 params.bounds = gfx::Rect(0, 0, 200, 200);
90 textfield_widget_->Init(params);
91 View* container = new View();
92 textfield_widget_->SetContentsView(container);
93 container->AddChildView(textfield_);
95 textfield_->SetBoundsRect(gfx::Rect(0, 0, 200, 20));
96 textfield_->set_id(1);
97 textfield_widget_->Show();
99 textfield_->RequestFocus();
102 void CreateWidget() {
103 widget_ = new Widget;
104 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
105 params.bounds = gfx::Rect(0, 0, 200, 200);
106 widget_->Init(params);
111 static bool IsCursorHandleVisibleFor(
112 ui::TouchSelectionController* controller) {
113 TouchSelectionControllerImpl* impl =
114 static_cast<TouchSelectionControllerImpl*>(controller);
115 return impl->IsCursorHandleVisible();
118 gfx::Rect GetCursorRect(const gfx::SelectionModel& sel) {
119 return textfield_->GetRenderText()->GetCursorBounds(sel, true);
122 gfx::Point GetCursorPosition(const gfx::SelectionModel& sel) {
123 gfx::Rect cursor_bounds = GetCursorRect(sel);
124 return gfx::Point(cursor_bounds.x(), cursor_bounds.y());
127 TouchSelectionControllerImpl* GetSelectionController() {
128 return static_cast<TouchSelectionControllerImpl*>(
129 textfield_->touch_selection_controller_.get());
132 void StartTouchEditing() {
133 textfield_->CreateTouchSelectionControllerAndNotifyIt();
136 void EndTouchEditing() {
137 textfield_->touch_selection_controller_.reset();
140 void SimulateSelectionHandleDrag(gfx::Point p, int selection_handle) {
141 TouchSelectionControllerImpl* controller = GetSelectionController();
142 // Do the work of OnMousePressed().
143 if (selection_handle == 1)
144 controller->SetDraggingHandle(controller->selection_handle_1_.get());
146 controller->SetDraggingHandle(controller->selection_handle_2_.get());
148 // Offset the drag position by the selection handle radius since it is
149 // supposed to be in the coordinate system of the handle.
150 p.Offset(GetHandleImageSize().width() / 2 + kPadding, 0);
151 controller->SelectionHandleDragged(p);
153 // Do the work of OnMouseReleased().
154 controller->dragging_handle_ = NULL;
157 gfx::NativeView GetCursorHandleNativeView() {
158 return GetSelectionController()->GetCursorHandleNativeView();
161 gfx::Point GetSelectionHandle1Position() {
162 return GetSelectionController()->GetSelectionHandle1Position();
165 gfx::Point GetSelectionHandle2Position() {
166 return GetSelectionController()->GetSelectionHandle2Position();
169 gfx::Point GetCursorHandlePosition() {
170 return GetSelectionController()->GetCursorHandlePosition();
173 bool IsSelectionHandle1Visible() {
174 return GetSelectionController()->IsSelectionHandle1Visible();
177 bool IsSelectionHandle2Visible() {
178 return GetSelectionController()->IsSelectionHandle2Visible();
181 bool IsCursorHandleVisible() {
182 return GetSelectionController()->IsCursorHandleVisible();
185 gfx::RenderText* GetRenderText() {
186 return textfield_->GetRenderText();
189 gfx::Point GetCursorHandleDragPoint() {
190 gfx::Point point = GetCursorHandlePosition();
191 const gfx::SelectionModel& sel = textfield_->GetSelectionModel();
192 int cursor_height = GetCursorRect(sel).height();
193 point.Offset(GetHandleImageSize().width() / 2 + kPadding,
194 GetHandleImageSize().height() / 2 + cursor_height);
198 Widget* textfield_widget_;
201 Textfield* textfield_;
202 scoped_ptr<ViewsTouchSelectionControllerFactory> views_tsc_factory_;
205 DISALLOW_COPY_AND_ASSIGN(TouchSelectionControllerImplTest);
208 // If textfield has selection, this macro verifies that the selection handles
209 // are visible and at the correct positions (at the end points of selection).
210 // |cursor_at_selection_handle_1| is used to decide whether selection
211 // handle 1's position is matched against the start of selection or the end.
212 #define VERIFY_HANDLE_POSITIONS(cursor_at_selection_handle_1) \
214 gfx::SelectionModel sel = textfield_->GetSelectionModel(); \
215 if (textfield_->HasSelection()) { \
216 EXPECT_TRUE(IsSelectionHandle1Visible()); \
217 EXPECT_TRUE(IsSelectionHandle2Visible()); \
218 EXPECT_FALSE(IsCursorHandleVisible()); \
219 gfx::SelectionModel sel_start = GetRenderText()-> \
220 GetSelectionModelForSelectionStart(); \
221 gfx::Point selection_start = GetCursorPosition(sel_start); \
222 gfx::Point selection_end = GetCursorPosition(sel); \
223 gfx::Point sh1 = GetSelectionHandle1Position(); \
224 gfx::Point sh2 = GetSelectionHandle2Position(); \
225 sh1.Offset(GetHandleImageSize().width() / 2 + kPadding, 0); \
226 sh2.Offset(GetHandleImageSize().width() / 2 + kPadding, 0); \
227 if (cursor_at_selection_handle_1) { \
228 EXPECT_EQ(sh1, selection_end); \
229 EXPECT_EQ(sh2, selection_start); \
231 EXPECT_EQ(sh1, selection_start); \
232 EXPECT_EQ(sh2, selection_end); \
235 EXPECT_FALSE(IsSelectionHandle1Visible()); \
236 EXPECT_FALSE(IsSelectionHandle2Visible()); \
237 EXPECT_TRUE(IsCursorHandleVisible()); \
238 gfx::Point cursor_pos = GetCursorPosition(sel); \
239 gfx::Point ch_pos = GetCursorHandlePosition(); \
240 ch_pos.Offset(GetHandleImageSize().width() / 2 + kPadding, 0); \
241 EXPECT_EQ(ch_pos, cursor_pos); \
245 // Tests that the selection handles are placed appropriately when selection in
246 // a Textfield changes.
247 TEST_F(TouchSelectionControllerImplTest, SelectionInTextfieldTest) {
249 textfield_->SetText(ASCIIToUTF16("some text"));
250 // Tap the textfield to invoke touch selection.
251 ui::GestureEvent tap(ui::ET_GESTURE_TAP, 0, 0, 0, base::TimeDelta(),
252 ui::GestureEventDetails(ui::ET_GESTURE_TAP, 1.0f, 0.0f), 0);
253 textfield_->OnGestureEvent(&tap);
255 // Test selecting a range.
256 textfield_->SelectRange(gfx::Range(3, 7));
257 VERIFY_HANDLE_POSITIONS(false);
259 // Test selecting everything.
260 textfield_->SelectAll(false);
261 VERIFY_HANDLE_POSITIONS(false);
263 // Test with no selection.
264 textfield_->ClearSelection();
265 VERIFY_HANDLE_POSITIONS(false);
267 // Test with lost focus.
268 textfield_widget_->GetFocusManager()->ClearFocus();
269 EXPECT_FALSE(GetSelectionController());
271 // Test with focus re-gained.
272 textfield_widget_->GetFocusManager()->SetFocusedView(textfield_);
273 EXPECT_FALSE(GetSelectionController());
274 textfield_->OnGestureEvent(&tap);
275 VERIFY_HANDLE_POSITIONS(false);
278 // Tests that the selection handles are placed appropriately in bidi text.
279 TEST_F(TouchSelectionControllerImplTest, SelectionInBidiTextfieldTest) {
281 textfield_->SetText(WideToUTF16(L"abc\x05d0\x05d1\x05d2"));
282 // Tap the textfield to invoke touch selection.
283 ui::GestureEvent tap(ui::ET_GESTURE_TAP, 0, 0, 0, base::TimeDelta(),
284 ui::GestureEventDetails(ui::ET_GESTURE_TAP, 1.0f, 0.0f), 0);
285 textfield_->OnGestureEvent(&tap);
287 // Test cursor at run boundary and with empty selection.
288 textfield_->SelectSelectionModel(
289 gfx::SelectionModel(3, gfx::CURSOR_BACKWARD));
290 VERIFY_HANDLE_POSITIONS(false);
292 // Test selection range inside one run and starts or ends at run boundary.
293 textfield_->SelectRange(gfx::Range(2, 3));
294 VERIFY_HANDLE_POSITIONS(false);
296 textfield_->SelectRange(gfx::Range(3, 2));
297 VERIFY_HANDLE_POSITIONS(false);
299 textfield_->SelectRange(gfx::Range(3, 4));
300 VERIFY_HANDLE_POSITIONS(false);
302 textfield_->SelectRange(gfx::Range(4, 3));
303 VERIFY_HANDLE_POSITIONS(false);
305 textfield_->SelectRange(gfx::Range(3, 6));
306 VERIFY_HANDLE_POSITIONS(false);
308 textfield_->SelectRange(gfx::Range(6, 3));
309 VERIFY_HANDLE_POSITIONS(false);
311 // Test selection range accross runs.
312 textfield_->SelectRange(gfx::Range(0, 6));
313 VERIFY_HANDLE_POSITIONS(false);
315 textfield_->SelectRange(gfx::Range(6, 0));
316 VERIFY_HANDLE_POSITIONS(false);
318 textfield_->SelectRange(gfx::Range(1, 4));
319 VERIFY_HANDLE_POSITIONS(false);
321 textfield_->SelectRange(gfx::Range(4, 1));
322 VERIFY_HANDLE_POSITIONS(false);
325 // Tests if the SelectRect callback is called appropriately when selection
326 // handles are moved.
327 TEST_F(TouchSelectionControllerImplTest, SelectRectCallbackTest) {
329 textfield_->SetText(ASCIIToUTF16("textfield with selected text"));
330 // Tap the textfield to invoke touch selection.
331 ui::GestureEvent tap(ui::ET_GESTURE_TAP, 0, 0, 0, base::TimeDelta(),
332 ui::GestureEventDetails(ui::ET_GESTURE_TAP, 1.0f, 0.0f), 0);
333 textfield_->OnGestureEvent(&tap);
334 textfield_->SelectRange(gfx::Range(3, 7));
336 EXPECT_EQ(UTF16ToUTF8(textfield_->GetSelectedText()), "tfie");
337 VERIFY_HANDLE_POSITIONS(false);
339 // Drag selection handle 2 to right by 3 chars.
340 const gfx::FontList& font_list = textfield_->GetFontList();
341 int x = gfx::Canvas::GetStringWidth(ASCIIToUTF16("ld "), font_list);
342 SimulateSelectionHandleDrag(gfx::Point(x, 0), 2);
343 EXPECT_EQ(UTF16ToUTF8(textfield_->GetSelectedText()), "tfield ");
344 VERIFY_HANDLE_POSITIONS(false);
346 // Drag selection handle 1 to the left by a large amount (selection should
347 // just stick to the beginning of the textfield).
348 SimulateSelectionHandleDrag(gfx::Point(-50, 0), 1);
349 EXPECT_EQ(UTF16ToUTF8(textfield_->GetSelectedText()), "textfield ");
350 VERIFY_HANDLE_POSITIONS(true);
352 // Drag selection handle 1 across selection handle 2.
353 x = gfx::Canvas::GetStringWidth(ASCIIToUTF16("textfield with "), font_list);
354 SimulateSelectionHandleDrag(gfx::Point(x, 0), 1);
355 EXPECT_EQ(UTF16ToUTF8(textfield_->GetSelectedText()), "with ");
356 VERIFY_HANDLE_POSITIONS(true);
358 // Drag selection handle 2 across selection handle 1.
359 x = gfx::Canvas::GetStringWidth(ASCIIToUTF16("with selected "), font_list);
360 SimulateSelectionHandleDrag(gfx::Point(x, 0), 2);
361 EXPECT_EQ(UTF16ToUTF8(textfield_->GetSelectedText()), "selected ");
362 VERIFY_HANDLE_POSITIONS(false);
365 TEST_F(TouchSelectionControllerImplTest, SelectRectInBidiCallbackTest) {
367 textfield_->SetText(WideToUTF16(L"abc\x05e1\x05e2\x05e3" L"def"));
368 // Tap the textfield to invoke touch selection.
369 ui::GestureEvent tap(ui::ET_GESTURE_TAP, 0, 0, 0, base::TimeDelta(),
370 ui::GestureEventDetails(ui::ET_GESTURE_TAP, 1.0f, 0.0f), 0);
371 textfield_->OnGestureEvent(&tap);
373 // Select [c] from left to right.
374 textfield_->SelectRange(gfx::Range(2, 3));
375 EXPECT_EQ(WideToUTF16(L"c"), textfield_->GetSelectedText());
376 VERIFY_HANDLE_POSITIONS(false);
378 // Drag selection handle 2 to right by 1 char.
379 const gfx::FontList& font_list = textfield_->GetFontList();
380 int x = gfx::Canvas::GetStringWidth(WideToUTF16(L"\x05e3"), font_list);
381 SimulateSelectionHandleDrag(gfx::Point(x, 0), 2);
382 EXPECT_EQ(WideToUTF16(L"c\x05e1\x05e2"), textfield_->GetSelectedText());
383 VERIFY_HANDLE_POSITIONS(false);
385 // Drag selection handle 1 to left by 1 char.
386 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"b"), font_list);
387 SimulateSelectionHandleDrag(gfx::Point(-x, 0), 1);
388 EXPECT_EQ(WideToUTF16(L"bc\x05e1\x05e2"), textfield_->GetSelectedText());
389 VERIFY_HANDLE_POSITIONS(true);
391 // Select [c] from right to left.
392 textfield_->SelectRange(gfx::Range(3, 2));
393 EXPECT_EQ(WideToUTF16(L"c"), textfield_->GetSelectedText());
394 VERIFY_HANDLE_POSITIONS(false);
396 // Drag selection handle 1 to right by 1 char.
397 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"\x05e3"), font_list);
398 SimulateSelectionHandleDrag(gfx::Point(x, 0), 1);
399 EXPECT_EQ(WideToUTF16(L"c\x05e1\x05e2"), textfield_->GetSelectedText());
400 VERIFY_HANDLE_POSITIONS(true);
402 // Drag selection handle 2 to left by 1 char.
403 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"b"), font_list);
404 SimulateSelectionHandleDrag(gfx::Point(-x, 0), 2);
405 EXPECT_EQ(WideToUTF16(L"bc\x05e1\x05e2"), textfield_->GetSelectedText());
406 VERIFY_HANDLE_POSITIONS(false);
408 // Select [\x5e1] from right to left.
409 textfield_->SelectRange(gfx::Range(3, 4));
410 EXPECT_EQ(WideToUTF16(L"\x05e1"), textfield_->GetSelectedText());
411 VERIFY_HANDLE_POSITIONS(false);
413 /* TODO(xji): for bidi text "abcDEF" whose display is "abcFEDhij", when click
414 right of 'D' and select [D] then move the left selection handle to left
415 by one character, it should select [ED], instead it selects [F].
416 Reason: click right of 'D' and left of 'h' return the same x-axis position,
417 pass this position to FindCursorPosition() returns index of 'h'. which
418 means the selection start changed from 3 to 6.
419 Need further investigation on whether this is a bug in Pango and how to
421 // Drag selection handle 2 to left by 1 char.
422 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"\x05e2"), font_list);
423 SimulateSelectionHandleDrag(gfx::Point(-x, 0), 2);
424 EXPECT_EQ(WideToUTF16(L"\x05e1\x05e2"), textfield_->GetSelectedText());
425 VERIFY_HANDLE_POSITIONS(false);
428 // Drag selection handle 1 to right by 1 char.
429 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"d"), font_list);
430 SimulateSelectionHandleDrag(gfx::Point(x, 0), 1);
431 EXPECT_EQ(WideToUTF16(L"\x05e2\x05e3" L"d"), textfield_->GetSelectedText());
432 VERIFY_HANDLE_POSITIONS(true);
434 // Select [\x5e1] from left to right.
435 textfield_->SelectRange(gfx::Range(4, 3));
436 EXPECT_EQ(WideToUTF16(L"\x05e1"), textfield_->GetSelectedText());
437 VERIFY_HANDLE_POSITIONS(false);
439 /* TODO(xji): see detail of above commented out test case.
440 // Drag selection handle 1 to left by 1 char.
441 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"\x05e2"), font_list);
442 SimulateSelectionHandleDrag(gfx::Point(-x, 0), 1);
443 EXPECT_EQ(WideToUTF16(L"\x05e1\x05e2"), textfield_->GetSelectedText());
444 VERIFY_HANDLE_POSITIONS(true);
447 // Drag selection handle 2 to right by 1 char.
448 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"d"), font_list);
449 SimulateSelectionHandleDrag(gfx::Point(x, 0), 2);
450 EXPECT_EQ(WideToUTF16(L"\x05e2\x05e3" L"d"), textfield_->GetSelectedText());
451 VERIFY_HANDLE_POSITIONS(false);
453 // Select [\x05r3] from right to left.
454 textfield_->SelectRange(gfx::Range(5, 6));
455 EXPECT_EQ(WideToUTF16(L"\x05e3"), textfield_->GetSelectedText());
456 VERIFY_HANDLE_POSITIONS(false);
458 // Drag selection handle 2 to left by 1 char.
459 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"c"), font_list);
460 SimulateSelectionHandleDrag(gfx::Point(-x, 0), 2);
461 EXPECT_EQ(WideToUTF16(L"c\x05e1\x05e2"), textfield_->GetSelectedText());
462 VERIFY_HANDLE_POSITIONS(false);
464 // Drag selection handle 1 to right by 1 char.
465 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"\x05e2"), font_list);
466 SimulateSelectionHandleDrag(gfx::Point(x, 0), 1);
467 EXPECT_EQ(WideToUTF16(L"c\x05e1"), textfield_->GetSelectedText());
468 VERIFY_HANDLE_POSITIONS(true);
470 // Select [\x05r3] from left to right.
471 textfield_->SelectRange(gfx::Range(6, 5));
472 EXPECT_EQ(WideToUTF16(L"\x05e3"), textfield_->GetSelectedText());
473 VERIFY_HANDLE_POSITIONS(false);
475 // Drag selection handle 1 to left by 1 char.
476 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"c"), font_list);
477 SimulateSelectionHandleDrag(gfx::Point(-x, 0), 1);
478 EXPECT_EQ(WideToUTF16(L"c\x05e1\x05e2"), textfield_->GetSelectedText());
479 VERIFY_HANDLE_POSITIONS(true);
481 // Drag selection handle 2 to right by 1 char.
482 x = gfx::Canvas::GetStringWidth(WideToUTF16(L"\x05e2"), font_list);
483 SimulateSelectionHandleDrag(gfx::Point(x, 0), 2);
484 EXPECT_EQ(WideToUTF16(L"c\x05e1"), textfield_->GetSelectedText());
485 VERIFY_HANDLE_POSITIONS(false);
488 TEST_F(TouchSelectionControllerImplTest,
489 HiddenSelectionHandleRetainsCursorPosition) {
490 // Create a textfield with lots of text in it.
492 std::string textfield_text("some text");
493 for (int i = 0; i < 10; ++i)
494 textfield_text += textfield_text;
495 textfield_->SetText(ASCIIToUTF16(textfield_text));
497 // Tap the textfield to invoke selection.
498 ui::GestureEvent tap(ui::ET_GESTURE_TAP, 0, 0, 0, base::TimeDelta(),
499 ui::GestureEventDetails(ui::ET_GESTURE_TAP, 1.0f, 0.0f), 0);
500 textfield_->OnGestureEvent(&tap);
502 // Select some text such that one handle is hidden.
503 textfield_->SelectRange(gfx::Range(10, textfield_text.length()));
505 // Check that one selection handle is hidden.
506 EXPECT_FALSE(IsSelectionHandle1Visible());
507 EXPECT_TRUE(IsSelectionHandle2Visible());
508 EXPECT_EQ(gfx::Range(10, textfield_text.length()),
509 textfield_->GetSelectedRange());
511 // Drag the visible handle around and make sure the selection end point of the
512 // invisible handle does not change.
513 size_t visible_handle_position = textfield_->GetSelectedRange().end();
514 for (int i = 0; i < 10; ++i) {
515 SimulateSelectionHandleDrag(gfx::Point(-10, 0), 2);
516 // Make sure that the visible handle is being dragged.
517 EXPECT_NE(visible_handle_position, textfield_->GetSelectedRange().end());
518 visible_handle_position = textfield_->GetSelectedRange().end();
519 EXPECT_EQ((size_t) 10, textfield_->GetSelectedRange().start());
523 TEST_F(TouchSelectionControllerImplTest,
524 DoubleTapInTextfieldWithCursorHandleShouldSelectWord) {
526 textfield_->SetText(ASCIIToUTF16("some text"));
527 aura::test::EventGenerator generator(
528 textfield_->GetWidget()->GetNativeView()->GetRootWindow());
530 // Tap the textfield to invoke touch selection.
531 generator.GestureTapAt(gfx::Point(10, 10));
533 // Cursor handle should be visible.
534 EXPECT_FALSE(textfield_->HasSelection());
535 VERIFY_HANDLE_POSITIONS(false);
537 // Double tap on the cursor handle position. We want to check that the cursor
538 // handle is not eating the event and that the event is falling through to the
540 gfx::Point cursor_pos = GetCursorHandlePosition();
541 cursor_pos.Offset(GetHandleImageSize().width() / 2 + kPadding, 0);
542 generator.GestureTapAt(cursor_pos);
543 generator.GestureTapAt(cursor_pos);
544 EXPECT_TRUE(textfield_->HasSelection());
545 VERIFY_HANDLE_POSITIONS(false);
548 // A simple implementation of TouchEditable that allows faking cursor position
549 // inside its boundaries.
550 class TestTouchEditable : public ui::TouchEditable {
552 explicit TestTouchEditable(aura::Window* window)
557 void set_bounds(const gfx::Rect& bounds) {
561 void set_cursor_rect(const gfx::Rect& cursor_rect) {
562 cursor_rect_ = cursor_rect;
565 virtual ~TestTouchEditable() {}
568 // Overridden from ui::TouchEditable.
569 virtual void SelectRect(
570 const gfx::Point& start, const gfx::Point& end) OVERRIDE {
573 virtual void MoveCaretTo(const gfx::Point& point) OVERRIDE {
576 virtual void GetSelectionEndPoints(gfx::Rect* p1, gfx::Rect* p2) OVERRIDE {
577 *p1 = *p2 = cursor_rect_;
579 virtual gfx::Rect GetBounds() OVERRIDE {
580 return gfx::Rect(bounds_.size());
582 virtual gfx::NativeView GetNativeView() const OVERRIDE {
585 virtual void ConvertPointToScreen(gfx::Point* point) OVERRIDE {
586 aura::client::ScreenPositionClient* screen_position_client =
587 aura::client::GetScreenPositionClient(window_->GetRootWindow());
588 if (screen_position_client)
589 screen_position_client->ConvertPointToScreen(window_, point);
591 virtual void ConvertPointFromScreen(gfx::Point* point) OVERRIDE {
592 aura::client::ScreenPositionClient* screen_position_client =
593 aura::client::GetScreenPositionClient(window_->GetRootWindow());
594 if (screen_position_client)
595 screen_position_client->ConvertPointFromScreen(window_, point);
597 virtual bool DrawsHandles() OVERRIDE {
600 virtual void OpenContextMenu(const gfx::Point& anchor) OVERRIDE {
603 virtual void DestroyTouchSelection() OVERRIDE {
607 // Overridden from ui::SimpleMenuModel::Delegate.
608 virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
612 virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
616 virtual bool GetAcceleratorForCommandId(
618 ui::Accelerator* accelerator) OVERRIDE {
622 virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
626 aura::Window* window_;
628 // Boundaries of the client view.
631 // Cursor position inside the client view.
632 gfx::Rect cursor_rect_;
634 DISALLOW_COPY_AND_ASSIGN(TestTouchEditable);
637 // Tests if the touch editing handle is shown or hidden properly according to
638 // the cursor position relative to the client boundaries.
639 TEST_F(TouchSelectionControllerImplTest,
640 VisibilityOfHandleRegardingClientBounds) {
643 TestTouchEditable touch_editable(widget_->GetNativeView());
644 scoped_ptr<ui::TouchSelectionController> touch_selection_controller(
645 ui::TouchSelectionController::create(&touch_editable));
647 touch_editable.set_bounds(gfx::Rect(0, 0, 100, 20));
649 // Put the cursor completely inside the client bounds. Handle should be
651 touch_editable.set_cursor_rect(gfx::Rect(2, 0, 1, 20));
652 touch_selection_controller->SelectionChanged();
653 EXPECT_TRUE(IsCursorHandleVisibleFor(touch_selection_controller.get()));
655 // Move the cursor up such that |kBarMinHeight| pixels are still in the client
656 // bounds. Handle should still be visible.
657 touch_editable.set_cursor_rect(gfx::Rect(2, kBarMinHeight - 20, 1, 20));
658 touch_selection_controller->SelectionChanged();
659 EXPECT_TRUE(IsCursorHandleVisibleFor(touch_selection_controller.get()));
661 // Move the cursor up such that less than |kBarMinHeight| pixels are in the
662 // client bounds. Handle should be hidden.
663 touch_editable.set_cursor_rect(gfx::Rect(2, kBarMinHeight - 20 - 1, 1, 20));
664 touch_selection_controller->SelectionChanged();
665 EXPECT_FALSE(IsCursorHandleVisibleFor(touch_selection_controller.get()));
667 // Move the Cursor down such that |kBarBottomAllowance| pixels are out of the
668 // client bounds. Handle should be visible.
669 touch_editable.set_cursor_rect(gfx::Rect(2, kBarBottomAllowance, 1, 20));
670 touch_selection_controller->SelectionChanged();
671 EXPECT_TRUE(IsCursorHandleVisibleFor(touch_selection_controller.get()));
673 // Move the cursor down such that more than |kBarBottomAllowance| pixels are
674 // out of the client bounds. Handle should be hidden.
675 touch_editable.set_cursor_rect(gfx::Rect(2, kBarBottomAllowance + 1, 1, 20));
676 touch_selection_controller->SelectionChanged();
677 EXPECT_FALSE(IsCursorHandleVisibleFor(touch_selection_controller.get()));
679 touch_selection_controller.reset();
682 TEST_F(TouchSelectionControllerImplTest, HandlesStackAboveParent) {
683 ui::EventTarget* root = GetContext();
684 ui::EventTargeter* targeter = root->GetEventTargeter();
686 // Create the first window containing a Views::Textfield.
688 aura::Window* window1 = textfield_widget_->GetNativeView();
690 // Start touch editing, check that the handle is above the first window, and
691 // end touch editing.
693 gfx::Point test_point = GetCursorHandleDragPoint();
694 ui::MouseEvent test_event1(ui::ET_MOUSE_MOVED, test_point, test_point,
695 ui::EF_NONE, ui::EF_NONE);
696 EXPECT_EQ(GetCursorHandleNativeView(),
697 targeter->FindTargetForEvent(root, &test_event1));
700 // Create the second (empty) window over the first one.
702 aura::Window* window2 = widget_->GetNativeView();
704 // Start touch editing (in the first window) and check that the handle is not
705 // above the second window.
707 ui::MouseEvent test_event2(ui::ET_MOUSE_MOVED, test_point, test_point,
708 ui::EF_NONE, ui::EF_NONE);
709 EXPECT_EQ(window2, targeter->FindTargetForEvent(root, &test_event2));
711 // Move the first window to top and check that the handle is kept above the
713 window1->GetRootWindow()->StackChildAtTop(window1);
714 ui::MouseEvent test_event3(ui::ET_MOUSE_MOVED, test_point, test_point,
715 ui::EF_NONE, ui::EF_NONE);
716 EXPECT_EQ(GetCursorHandleNativeView(),
717 targeter->FindTargetForEvent(root, &test_event3));
720 // A simple implementation of TouchEditingMenuController that enables all
721 // available commands.
722 class TestTouchEditingMenuController : public TouchEditingMenuController {
724 TestTouchEditingMenuController() {}
725 virtual ~TestTouchEditingMenuController() {}
727 // Overriden from TouchEditingMenuController.
728 virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
729 // Return true, since we want the menu to have all |kMenuCommandCount|
730 // available commands.
733 virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
736 virtual void OpenContextMenu() OVERRIDE {
739 virtual void OnMenuClosed(TouchEditingMenuView* menu) OVERRIDE {}
742 DISALLOW_COPY_AND_ASSIGN(TestTouchEditingMenuController);
745 // Tests if anchor rect for touch editing quick menu is adjusted correctly based
746 // on the distance of handles.
747 TEST_F(TouchSelectionControllerImplTest, QuickMenuAdjustsAnchorRect) {
749 aura::Window* window = widget_->GetNativeView();
751 scoped_ptr<TestTouchEditingMenuController> quick_menu_controller(
752 new TestTouchEditingMenuController());
754 // Some arbitrary size for touch editing handle image.
755 gfx::Size handle_image_size(10, 10);
757 // Calculate the width of quick menu. In addition to |kMenuCommandCount|
758 // commands, there is an item for ellipsis.
759 int quick_menu_width = (kMenuCommandCount + 1) * kMenuButtonWidth +
762 // Set anchor rect's width a bit smaller than the quick menu width plus handle
763 // image width and check that anchor rect's height is adjusted.
764 gfx::Rect anchor_rect(
765 0, 0, quick_menu_width + handle_image_size.width() - 10, 20);
766 TouchEditingMenuView* quick_menu(TouchEditingMenuView::Create(
767 quick_menu_controller.get(), anchor_rect, handle_image_size, window));
768 anchor_rect.Inset(0, 0, 0, -handle_image_size.height());
769 EXPECT_EQ(anchor_rect.ToString(), quick_menu->GetAnchorRect().ToString());
771 // Set anchor rect's width a bit greater than the quick menu width plus handle
772 // image width and check that anchor rect's height is not adjusted.
774 gfx::Rect(0, 0, quick_menu_width + handle_image_size.width() + 10, 20);
775 quick_menu = TouchEditingMenuView::Create(
776 quick_menu_controller.get(), anchor_rect, handle_image_size, window);
777 EXPECT_EQ(anchor_rect.ToString(), quick_menu->GetAnchorRect().ToString());
779 // Close the widget, hence quick menus, before quick menu controller goes out
785 TEST_F(TouchSelectionControllerImplTest, MouseEventDeactivatesTouchSelection) {
787 EXPECT_FALSE(GetSelectionController());
789 aura::test::EventGenerator generator(
790 textfield_widget_->GetNativeView()->GetRootWindow());
792 generator.set_current_location(gfx::Point(5, 5));
793 RunPendingMessages();
795 // Start touch editing; then move mouse over the textfield and ensure it
796 // deactivates touch selection.
798 EXPECT_TRUE(GetSelectionController());
799 generator.MoveMouseTo(gfx::Point(5, 10));
800 RunPendingMessages();
801 EXPECT_FALSE(GetSelectionController());
803 generator.MoveMouseTo(gfx::Point(5, 50));
804 RunPendingMessages();
806 // Start touch editing; then move mouse out of the textfield, but inside the
807 // winow and ensure it deactivates touch selection.
809 EXPECT_TRUE(GetSelectionController());
810 generator.MoveMouseTo(gfx::Point(5, 55));
811 RunPendingMessages();
812 EXPECT_FALSE(GetSelectionController());
814 generator.MoveMouseTo(gfx::Point(5, 500));
815 RunPendingMessages();
817 // Start touch editing; then move mouse out of the textfield and window and
818 // ensure it deactivates touch selection.
820 EXPECT_TRUE(GetSelectionController());
821 generator.MoveMouseTo(5, 505);
822 RunPendingMessages();
823 EXPECT_FALSE(GetSelectionController());
826 TEST_F(TouchSelectionControllerImplTest, KeyEventDeactivatesTouchSelection) {
828 EXPECT_FALSE(GetSelectionController());
830 aura::test::EventGenerator generator(
831 textfield_widget_->GetNativeView()->GetRootWindow());
833 RunPendingMessages();
835 // Start touch editing; then press a key and ensure it deactivates touch
838 EXPECT_TRUE(GetSelectionController());
839 generator.PressKey(ui::VKEY_A, 0);
840 RunPendingMessages();
841 EXPECT_FALSE(GetSelectionController());