Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / ui / views / touchui / touch_selection_controller_impl_unittest.cc
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/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"
23
24 using base::ASCIIToUTF16;
25 using base::UTF16ToUTF8;
26 using base::WideToUTF16;
27
28 namespace {
29 // Should match kSelectionHandlePadding in touch_selection_controller.
30 const int kPadding = 10;
31
32 // Should match kSelectionHandleBarMinHeight in touch_selection_controller.
33 const int kBarMinHeight = 5;
34
35 // Should match kSelectionHandleBarBottomAllowance in
36 // touch_selection_controller.
37 const int kBarBottomAllowance = 3;
38
39 // Should match kMenuButtonWidth in touch_editing_menu.
40 const int kMenuButtonWidth = 63;
41
42 // Should match size of kMenuCommands array in touch_editing_menu.
43 const int kMenuCommandCount = 3;
44
45 gfx::Image* GetHandleImage() {
46   static gfx::Image* handle_image = NULL;
47   if (!handle_image) {
48     handle_image = &ui::ResourceBundle::GetSharedInstance().GetImageNamed(
49         IDR_TEXT_SELECTION_HANDLE);
50   }
51   return handle_image;
52 }
53
54 gfx::Size GetHandleImageSize() {
55   return GetHandleImage()->Size();
56 }
57 }  // namespace
58
59 namespace views {
60
61 class TouchSelectionControllerImplTest : public ViewsTestBase {
62  public:
63   TouchSelectionControllerImplTest()
64       : textfield_widget_(NULL),
65         widget_(NULL),
66         textfield_(NULL),
67         views_tsc_factory_(new ViewsTouchSelectionControllerFactory) {
68     CommandLine::ForCurrentProcess()->AppendSwitch(
69         switches::kEnableTouchEditing);
70     ui::TouchSelectionControllerFactory::SetInstance(views_tsc_factory_.get());
71   }
72
73   virtual ~TouchSelectionControllerImplTest() {
74     ui::TouchSelectionControllerFactory::SetInstance(NULL);
75   }
76
77   virtual void TearDown() {
78     if (textfield_widget_ && !textfield_widget_->IsClosed())
79       textfield_widget_->Close();
80     if (widget_ && !widget_->IsClosed())
81       widget_->Close();
82     ViewsTestBase::TearDown();
83   }
84
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_);
94
95     textfield_->SetBoundsRect(gfx::Rect(0, 0, 200, 20));
96     textfield_->set_id(1);
97     textfield_widget_->Show();
98
99     textfield_->RequestFocus();
100   }
101
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);
107     widget_->Show();
108   }
109
110  protected:
111   static bool IsCursorHandleVisibleFor(
112       ui::TouchSelectionController* controller) {
113     TouchSelectionControllerImpl* impl =
114         static_cast<TouchSelectionControllerImpl*>(controller);
115     return impl->IsCursorHandleVisible();
116   }
117
118   gfx::Rect GetCursorRect(const gfx::SelectionModel& sel) {
119     return textfield_->GetRenderText()->GetCursorBounds(sel, true);
120   }
121
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());
125   }
126
127   TouchSelectionControllerImpl* GetSelectionController() {
128     return static_cast<TouchSelectionControllerImpl*>(
129         textfield_->touch_selection_controller_.get());
130   }
131
132   void StartTouchEditing() {
133     textfield_->CreateTouchSelectionControllerAndNotifyIt();
134   }
135
136   void EndTouchEditing() {
137     textfield_->touch_selection_controller_.reset();
138   }
139
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());
145     else
146       controller->SetDraggingHandle(controller->selection_handle_2_.get());
147
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);
152
153     // Do the work of OnMouseReleased().
154     controller->dragging_handle_ = NULL;
155   }
156
157   gfx::NativeView GetCursorHandleNativeView() {
158     return GetSelectionController()->GetCursorHandleNativeView();
159   }
160
161   gfx::Point GetSelectionHandle1Position() {
162     return GetSelectionController()->GetSelectionHandle1Position();
163   }
164
165   gfx::Point GetSelectionHandle2Position() {
166     return GetSelectionController()->GetSelectionHandle2Position();
167   }
168
169   gfx::Point GetCursorHandlePosition() {
170     return GetSelectionController()->GetCursorHandlePosition();
171   }
172
173   bool IsSelectionHandle1Visible() {
174     return GetSelectionController()->IsSelectionHandle1Visible();
175   }
176
177   bool IsSelectionHandle2Visible() {
178     return GetSelectionController()->IsSelectionHandle2Visible();
179   }
180
181   bool IsCursorHandleVisible() {
182     return GetSelectionController()->IsCursorHandleVisible();
183   }
184
185   gfx::RenderText* GetRenderText() {
186     return textfield_->GetRenderText();
187   }
188
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);
195     return point;
196   }
197
198   Widget* textfield_widget_;
199   Widget* widget_;
200
201   Textfield* textfield_;
202   scoped_ptr<ViewsTouchSelectionControllerFactory> views_tsc_factory_;
203
204  private:
205   DISALLOW_COPY_AND_ASSIGN(TouchSelectionControllerImplTest);
206 };
207
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)                  \
213 {                                                                              \
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);                                       \
230       } else {                                                                 \
231         EXPECT_EQ(sh1, selection_start);                                       \
232         EXPECT_EQ(sh2, selection_end);                                         \
233       }                                                                        \
234     } else {                                                                   \
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);                                           \
242     }                                                                          \
243 }
244
245 // Tests that the selection handles are placed appropriately when selection in
246 // a Textfield changes.
247 TEST_F(TouchSelectionControllerImplTest, SelectionInTextfieldTest) {
248   CreateTextfield();
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);
254
255   // Test selecting a range.
256   textfield_->SelectRange(gfx::Range(3, 7));
257   VERIFY_HANDLE_POSITIONS(false);
258
259   // Test selecting everything.
260   textfield_->SelectAll(false);
261   VERIFY_HANDLE_POSITIONS(false);
262
263   // Test with no selection.
264   textfield_->ClearSelection();
265   VERIFY_HANDLE_POSITIONS(false);
266
267   // Test with lost focus.
268   textfield_widget_->GetFocusManager()->ClearFocus();
269   EXPECT_FALSE(GetSelectionController());
270
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);
276 }
277
278 // Tests that the selection handles are placed appropriately in bidi text.
279 TEST_F(TouchSelectionControllerImplTest, SelectionInBidiTextfieldTest) {
280   CreateTextfield();
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);
286
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);
291
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);
295
296   textfield_->SelectRange(gfx::Range(3, 2));
297   VERIFY_HANDLE_POSITIONS(false);
298
299   textfield_->SelectRange(gfx::Range(3, 4));
300   VERIFY_HANDLE_POSITIONS(false);
301
302   textfield_->SelectRange(gfx::Range(4, 3));
303   VERIFY_HANDLE_POSITIONS(false);
304
305   textfield_->SelectRange(gfx::Range(3, 6));
306   VERIFY_HANDLE_POSITIONS(false);
307
308   textfield_->SelectRange(gfx::Range(6, 3));
309   VERIFY_HANDLE_POSITIONS(false);
310
311   // Test selection range accross runs.
312   textfield_->SelectRange(gfx::Range(0, 6));
313   VERIFY_HANDLE_POSITIONS(false);
314
315   textfield_->SelectRange(gfx::Range(6, 0));
316   VERIFY_HANDLE_POSITIONS(false);
317
318   textfield_->SelectRange(gfx::Range(1, 4));
319   VERIFY_HANDLE_POSITIONS(false);
320
321   textfield_->SelectRange(gfx::Range(4, 1));
322   VERIFY_HANDLE_POSITIONS(false);
323 }
324
325 // Tests if the SelectRect callback is called appropriately when selection
326 // handles are moved.
327 TEST_F(TouchSelectionControllerImplTest, SelectRectCallbackTest) {
328   CreateTextfield();
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));
335
336   EXPECT_EQ(UTF16ToUTF8(textfield_->GetSelectedText()), "tfie");
337   VERIFY_HANDLE_POSITIONS(false);
338
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);
345
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);
351
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);
357
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);
363 }
364
365 TEST_F(TouchSelectionControllerImplTest, SelectRectInBidiCallbackTest) {
366   CreateTextfield();
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);
372
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);
377
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);
384
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);
390
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);
395
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);
401
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);
407
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);
412
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
420      work around it.
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);
426   */
427
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);
433
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);
438
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);
445   */
446
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);
452
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);
457
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);
463
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);
469
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);
474
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);
480
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);
486 }
487
488 TEST_F(TouchSelectionControllerImplTest,
489        HiddenSelectionHandleRetainsCursorPosition) {
490   // Create a textfield with lots of text in it.
491   CreateTextfield();
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));
496
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);
501
502   // Select some text such that one handle is hidden.
503   textfield_->SelectRange(gfx::Range(10, textfield_text.length()));
504
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());
510
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());
520   }
521 }
522
523 TEST_F(TouchSelectionControllerImplTest,
524        DoubleTapInTextfieldWithCursorHandleShouldSelectWord) {
525   CreateTextfield();
526   textfield_->SetText(ASCIIToUTF16("some text"));
527   aura::test::EventGenerator generator(
528       textfield_->GetWidget()->GetNativeView()->GetRootWindow());
529
530   // Tap the textfield to invoke touch selection.
531   generator.GestureTapAt(gfx::Point(10, 10));
532
533   // Cursor handle should be visible.
534   EXPECT_FALSE(textfield_->HasSelection());
535   VERIFY_HANDLE_POSITIONS(false);
536
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
539   // textfield.
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);
546 }
547
548 // A simple implementation of TouchEditable that allows faking cursor position
549 // inside its boundaries.
550 class TestTouchEditable : public ui::TouchEditable {
551  public:
552   explicit TestTouchEditable(aura::Window* window)
553       : window_(window) {
554     DCHECK(window);
555   }
556
557   void set_bounds(const gfx::Rect& bounds) {
558     bounds_ = bounds;
559   }
560
561   void set_cursor_rect(const gfx::Rect& cursor_rect) {
562     cursor_rect_ = cursor_rect;
563   }
564
565   virtual ~TestTouchEditable() {}
566
567  private:
568   // Overridden from ui::TouchEditable.
569   virtual void SelectRect(
570       const gfx::Point& start, const gfx::Point& end) OVERRIDE {
571     NOTREACHED();
572   }
573   virtual void MoveCaretTo(const gfx::Point& point) OVERRIDE {
574     NOTREACHED();
575   }
576   virtual void GetSelectionEndPoints(gfx::Rect* p1, gfx::Rect* p2) OVERRIDE {
577     *p1 = *p2 = cursor_rect_;
578   }
579   virtual gfx::Rect GetBounds() OVERRIDE {
580     return gfx::Rect(bounds_.size());
581   }
582   virtual gfx::NativeView GetNativeView() const OVERRIDE {
583     return window_;
584   }
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);
590   }
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);
596   }
597   virtual bool DrawsHandles() OVERRIDE {
598     return false;
599   }
600   virtual void OpenContextMenu(const gfx::Point& anchor) OVERRIDE {
601     NOTREACHED();
602   }
603   virtual void DestroyTouchSelection() OVERRIDE {
604     NOTREACHED();
605   }
606
607   // Overridden from ui::SimpleMenuModel::Delegate.
608   virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
609     NOTREACHED();
610     return false;
611   }
612   virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
613     NOTREACHED();
614     return false;
615   }
616   virtual bool GetAcceleratorForCommandId(
617       int command_id,
618       ui::Accelerator* accelerator) OVERRIDE {
619     NOTREACHED();
620     return false;
621   }
622   virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
623     NOTREACHED();
624   }
625
626   aura::Window* window_;
627
628   // Boundaries of the client view.
629   gfx::Rect bounds_;
630
631   // Cursor position inside the client view.
632   gfx::Rect cursor_rect_;
633
634   DISALLOW_COPY_AND_ASSIGN(TestTouchEditable);
635 };
636
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) {
641   CreateWidget();
642
643   TestTouchEditable touch_editable(widget_->GetNativeView());
644   scoped_ptr<ui::TouchSelectionController> touch_selection_controller(
645       ui::TouchSelectionController::create(&touch_editable));
646
647   touch_editable.set_bounds(gfx::Rect(0, 0, 100, 20));
648
649   // Put the cursor completely inside the client bounds. Handle should be
650   // visible.
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()));
654
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()));
660
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()));
666
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()));
672
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()));
678
679   touch_selection_controller.reset();
680 }
681
682 TEST_F(TouchSelectionControllerImplTest, HandlesStackAboveParent) {
683   ui::EventTarget* root = GetContext();
684   ui::EventTargeter* targeter = root->GetEventTargeter();
685
686   // Create the first window containing a Views::Textfield.
687   CreateTextfield();
688   aura::Window* window1 = textfield_widget_->GetNativeView();
689
690   // Start touch editing, check that the handle is above the first window, and
691   // end touch editing.
692   StartTouchEditing();
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));
698   EndTouchEditing();
699
700   // Create the second (empty) window over the first one.
701   CreateWidget();
702   aura::Window* window2 = widget_->GetNativeView();
703
704   // Start touch editing (in the first window) and check that the handle is not
705   // above the second window.
706   StartTouchEditing();
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));
710
711   // Move the first window to top and check that the handle is kept above the
712   // first window.
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));
718 }
719
720 // A simple implementation of TouchEditingMenuController that enables all
721 // available commands.
722 class TestTouchEditingMenuController : public TouchEditingMenuController {
723  public:
724   TestTouchEditingMenuController() {}
725   virtual ~TestTouchEditingMenuController() {}
726
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.
731     return true;
732   }
733   virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
734     NOTREACHED();
735   }
736   virtual void OpenContextMenu() OVERRIDE {
737     NOTREACHED();
738   }
739   virtual void OnMenuClosed(TouchEditingMenuView* menu) OVERRIDE {}
740
741  private:
742   DISALLOW_COPY_AND_ASSIGN(TestTouchEditingMenuController);
743 };
744
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) {
748   CreateWidget();
749   aura::Window* window = widget_->GetNativeView();
750
751   scoped_ptr<TestTouchEditingMenuController> quick_menu_controller(
752       new TestTouchEditingMenuController());
753
754   // Some arbitrary size for touch editing handle image.
755   gfx::Size handle_image_size(10, 10);
756
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 +
760                          kMenuCommandCount;
761
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());
770
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.
773   anchor_rect =
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());
778
779   // Close the widget, hence quick menus, before quick menu controller goes out
780   // of scope.
781   widget_->CloseNow();
782   widget_ = NULL;
783 }
784
785 TEST_F(TouchSelectionControllerImplTest, MouseEventDeactivatesTouchSelection) {
786   CreateTextfield();
787   EXPECT_FALSE(GetSelectionController());
788
789   aura::test::EventGenerator generator(
790       textfield_widget_->GetNativeView()->GetRootWindow());
791
792   generator.set_current_location(gfx::Point(5, 5));
793   RunPendingMessages();
794
795   // Start touch editing; then move mouse over the textfield and ensure it
796   // deactivates touch selection.
797   StartTouchEditing();
798   EXPECT_TRUE(GetSelectionController());
799   generator.MoveMouseTo(gfx::Point(5, 10));
800   RunPendingMessages();
801   EXPECT_FALSE(GetSelectionController());
802
803   generator.MoveMouseTo(gfx::Point(5, 50));
804   RunPendingMessages();
805
806   // Start touch editing; then move mouse out of the textfield, but inside the
807   // winow and ensure it deactivates touch selection.
808   StartTouchEditing();
809   EXPECT_TRUE(GetSelectionController());
810   generator.MoveMouseTo(gfx::Point(5, 55));
811   RunPendingMessages();
812   EXPECT_FALSE(GetSelectionController());
813
814   generator.MoveMouseTo(gfx::Point(5, 500));
815   RunPendingMessages();
816
817   // Start touch editing; then move mouse out of the textfield and window and
818   // ensure it deactivates touch selection.
819   StartTouchEditing();
820   EXPECT_TRUE(GetSelectionController());
821   generator.MoveMouseTo(5, 505);
822   RunPendingMessages();
823   EXPECT_FALSE(GetSelectionController());
824 }
825
826 TEST_F(TouchSelectionControllerImplTest, KeyEventDeactivatesTouchSelection) {
827   CreateTextfield();
828   EXPECT_FALSE(GetSelectionController());
829
830   aura::test::EventGenerator generator(
831       textfield_widget_->GetNativeView()->GetRootWindow());
832
833   RunPendingMessages();
834
835   // Start touch editing; then press a key and ensure it deactivates touch
836   // selection.
837   StartTouchEditing();
838   EXPECT_TRUE(GetSelectionController());
839   generator.PressKey(ui::VKEY_A, 0);
840   RunPendingMessages();
841   EXPECT_FALSE(GetSelectionController());
842 }
843
844 }  // namespace views