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 "ui/views/focus/focus_manager.h"
7 #include "base/run_loop.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "ui/base/models/combobox_model.h"
11 #include "ui/views/background.h"
12 #include "ui/views/border.h"
13 #include "ui/views/controls/button/checkbox.h"
14 #include "ui/views/controls/button/label_button.h"
15 #include "ui/views/controls/button/radio_button.h"
16 #include "ui/views/controls/combobox/combobox.h"
17 #include "ui/views/controls/label.h"
18 #include "ui/views/controls/link.h"
19 #include "ui/views/controls/native/native_view_host.h"
20 #include "ui/views/controls/scroll_view.h"
21 #include "ui/views/controls/tabbed_pane/tabbed_pane.h"
22 #include "ui/views/controls/textfield/textfield.h"
23 #include "ui/views/test/focus_manager_test.h"
24 #include "ui/views/widget/root_view.h"
25 #include "ui/views/widget/widget.h"
27 using base::ASCIIToUTF16
;
35 const int kTopCheckBoxID
= count
++; // 1
36 const int kLeftContainerID
= count
++;
37 const int kAppleLabelID
= count
++;
38 const int kAppleTextfieldID
= count
++;
39 const int kOrangeLabelID
= count
++; // 5
40 const int kOrangeTextfieldID
= count
++;
41 const int kBananaLabelID
= count
++;
42 const int kBananaTextfieldID
= count
++;
43 const int kKiwiLabelID
= count
++;
44 const int kKiwiTextfieldID
= count
++; // 10
45 const int kFruitButtonID
= count
++;
46 const int kFruitCheckBoxID
= count
++;
47 const int kComboboxID
= count
++;
49 const int kRightContainerID
= count
++;
50 const int kAsparagusButtonID
= count
++; // 15
51 const int kBroccoliButtonID
= count
++;
52 const int kCauliflowerButtonID
= count
++;
54 const int kInnerContainerID
= count
++;
55 const int kScrollViewID
= count
++;
56 const int kRosettaLinkID
= count
++; // 20
57 const int kStupeurEtTremblementLinkID
= count
++;
58 const int kDinerGameLinkID
= count
++;
59 const int kRidiculeLinkID
= count
++;
60 const int kClosetLinkID
= count
++;
61 const int kVisitingLinkID
= count
++; // 25
62 const int kAmelieLinkID
= count
++;
63 const int kJoyeuxNoelLinkID
= count
++;
64 const int kCampingLinkID
= count
++;
65 const int kBriceDeNiceLinkID
= count
++;
66 const int kTaxiLinkID
= count
++; // 30
67 const int kAsterixLinkID
= count
++;
69 const int kOKButtonID
= count
++;
70 const int kCancelButtonID
= count
++;
71 const int kHelpButtonID
= count
++;
73 const int kStyleContainerID
= count
++; // 35
74 const int kBoldCheckBoxID
= count
++;
75 const int kItalicCheckBoxID
= count
++;
76 const int kUnderlinedCheckBoxID
= count
++;
77 const int kStyleHelpLinkID
= count
++;
78 const int kStyleTextEditID
= count
++; // 40
80 const int kSearchContainerID
= count
++;
81 const int kSearchTextfieldID
= count
++;
82 const int kSearchButtonID
= count
++;
83 const int kHelpLinkID
= count
++;
85 const int kThumbnailContainerID
= count
++; // 45
86 const int kThumbnailStarID
= count
++;
87 const int kThumbnailSuperStarID
= count
++;
89 class DummyComboboxModel
: public ui::ComboboxModel
{
91 // Overridden from ui::ComboboxModel:
92 virtual int GetItemCount() const OVERRIDE
{ return 10; }
93 virtual base::string16
GetItemAt(int index
) OVERRIDE
{
94 return ASCIIToUTF16("Item ") + base::IntToString16(index
);
98 // A View that can act as a pane.
99 class PaneView
: public View
, public FocusTraversable
{
101 PaneView() : focus_search_(NULL
) {}
103 // If this method is called, this view will use GetPaneFocusTraversable to
104 // have this provided FocusSearch used instead of the default one, allowing
105 // you to trap focus within the pane.
106 void EnablePaneFocus(FocusSearch
* focus_search
) {
107 focus_search_
= focus_search
;
110 // Overridden from View:
111 virtual FocusTraversable
* GetPaneFocusTraversable() OVERRIDE
{
118 // Overridden from FocusTraversable:
119 virtual views::FocusSearch
* GetFocusSearch() OVERRIDE
{
120 return focus_search_
;
122 virtual FocusTraversable
* GetFocusTraversableParent() OVERRIDE
{
125 virtual View
* GetFocusTraversableParentView() OVERRIDE
{
130 FocusSearch
* focus_search_
;
133 // BorderView is a view containing a native window with its own view hierarchy.
134 // It is interesting to test focus traversal from a view hierarchy to an inner
136 class BorderView
: public NativeViewHost
{
138 explicit BorderView(View
* child
) : child_(child
), widget_(NULL
) {
143 virtual ~BorderView() {}
145 virtual internal::RootView
* GetContentsRootView() {
146 return static_cast<internal::RootView
*>(widget_
->GetRootView());
149 virtual FocusTraversable
* GetFocusTraversable() OVERRIDE
{
150 return static_cast<internal::RootView
*>(widget_
->GetRootView());
153 virtual void ViewHierarchyChanged(
154 const ViewHierarchyChangedDetails
& details
) OVERRIDE
{
155 NativeViewHost::ViewHierarchyChanged(details
);
157 if (details
.child
== this && details
.is_add
) {
159 widget_
= new Widget
;
160 Widget::InitParams
params(Widget::InitParams::TYPE_CONTROL
);
161 params
.parent
= details
.parent
->GetWidget()->GetNativeView();
162 widget_
->Init(params
);
163 widget_
->SetFocusTraversableParentView(this);
164 widget_
->SetContentsView(child_
);
167 // We have been added to a view hierarchy, attach the native view.
168 Attach(widget_
->GetNativeView());
169 // Also update the FocusTraversable parent so the focus traversal works.
170 static_cast<internal::RootView
*>(widget_
->GetRootView())->
171 SetFocusTraversableParent(GetWidget()->GetFocusTraversable());
179 DISALLOW_COPY_AND_ASSIGN(BorderView
);
184 class FocusTraversalTest
: public FocusManagerTest
{
186 virtual ~FocusTraversalTest();
188 virtual void InitContentView() OVERRIDE
;
191 FocusTraversalTest();
193 View
* FindViewByID(int id
) {
194 View
* view
= GetContentsView()->GetViewByID(id
);
198 view
= style_tab_
->GetSelectedTab()->GetViewByID(id
);
201 view
= search_border_view_
->GetContentsRootView()->GetViewByID(id
);
208 TabbedPane
* style_tab_
;
209 BorderView
* search_border_view_
;
210 DummyComboboxModel combobox_model_
;
211 PaneView
* left_container_
;
212 PaneView
* right_container_
;
214 DISALLOW_COPY_AND_ASSIGN(FocusTraversalTest
);
217 FocusTraversalTest::FocusTraversalTest()
219 search_border_view_(NULL
) {
222 FocusTraversalTest::~FocusTraversalTest() {
225 void FocusTraversalTest::InitContentView() {
226 // Create a complicated view hierarchy with lots of control types for
227 // use by all of the focus traversal tests.
229 // Class name, ID, and asterisk next to focusable views:
232 // Checkbox * kTopCheckBoxID
233 // PaneView kLeftContainerID
234 // Label kAppleLabelID
235 // Textfield * kAppleTextfieldID
236 // Label kOrangeLabelID
237 // Textfield * kOrangeTextfieldID
238 // Label kBananaLabelID
239 // Textfield * kBananaTextfieldID
240 // Label kKiwiLabelID
241 // Textfield * kKiwiTextfieldID
242 // NativeButton * kFruitButtonID
243 // Checkbox * kFruitCheckBoxID
244 // Combobox * kComboboxID
245 // PaneView kRightContainerID
246 // RadioButton * kAsparagusButtonID
247 // RadioButton * kBroccoliButtonID
248 // RadioButton * kCauliflowerButtonID
249 // View kInnerContainerID
250 // ScrollView kScrollViewID
252 // Link * kRosettaLinkID
253 // Link * kStupeurEtTremblementLinkID
254 // Link * kDinerGameLinkID
255 // Link * kRidiculeLinkID
256 // Link * kClosetLinkID
257 // Link * kVisitingLinkID
258 // Link * kAmelieLinkID
259 // Link * kJoyeuxNoelLinkID
260 // Link * kCampingLinkID
261 // Link * kBriceDeNiceLinkID
262 // Link * kTaxiLinkID
263 // Link * kAsterixLinkID
264 // NativeButton * kOKButtonID
265 // NativeButton * kCancelButtonID
266 // NativeButton * kHelpButtonID
267 // TabbedPane * kStyleContainerID
269 // Checkbox * kBoldCheckBoxID
270 // Checkbox * kItalicCheckBoxID
271 // Checkbox * kUnderlinedCheckBoxID
272 // Link * kStyleHelpLinkID
273 // Textfield * kStyleTextEditID
275 // BorderView kSearchContainerID
277 // Textfield * kSearchTextfieldID
278 // NativeButton * kSearchButtonID
279 // Link * kHelpLinkID
280 // View * kThumbnailContainerID
281 // NativeButton * kThumbnailStarID
282 // NativeButton * kThumbnailSuperStarID
284 GetContentsView()->set_background(
285 Background::CreateSolidBackground(SK_ColorWHITE
));
287 Checkbox
* cb
= new Checkbox(ASCIIToUTF16("This is a checkbox"));
288 GetContentsView()->AddChildView(cb
);
289 // In this fast paced world, who really has time for non hard-coded layout?
290 cb
->SetBounds(10, 10, 200, 20);
291 cb
->set_id(kTopCheckBoxID
);
293 left_container_
= new PaneView();
294 left_container_
->SetBorder(Border::CreateSolidBorder(1, SK_ColorBLACK
));
295 left_container_
->set_background(
296 Background::CreateSolidBackground(240, 240, 240));
297 left_container_
->set_id(kLeftContainerID
);
298 GetContentsView()->AddChildView(left_container_
);
299 left_container_
->SetBounds(10, 35, 250, 200);
302 int label_width
= 50;
303 int label_height
= 15;
304 int text_field_width
= 150;
306 int gap_between_labels
= 10;
308 Label
* label
= new Label(ASCIIToUTF16("Apple:"));
309 label
->set_id(kAppleLabelID
);
310 left_container_
->AddChildView(label
);
311 label
->SetBounds(label_x
, y
, label_width
, label_height
);
313 Textfield
* text_field
= new Textfield();
314 text_field
->set_id(kAppleTextfieldID
);
315 left_container_
->AddChildView(text_field
);
316 text_field
->SetBounds(label_x
+ label_width
+ 5, y
,
317 text_field_width
, label_height
);
319 y
+= label_height
+ gap_between_labels
;
321 label
= new Label(ASCIIToUTF16("Orange:"));
322 label
->set_id(kOrangeLabelID
);
323 left_container_
->AddChildView(label
);
324 label
->SetBounds(label_x
, y
, label_width
, label_height
);
326 text_field
= new Textfield();
327 text_field
->set_id(kOrangeTextfieldID
);
328 left_container_
->AddChildView(text_field
);
329 text_field
->SetBounds(label_x
+ label_width
+ 5, y
,
330 text_field_width
, label_height
);
332 y
+= label_height
+ gap_between_labels
;
334 label
= new Label(ASCIIToUTF16("Banana:"));
335 label
->set_id(kBananaLabelID
);
336 left_container_
->AddChildView(label
);
337 label
->SetBounds(label_x
, y
, label_width
, label_height
);
339 text_field
= new Textfield();
340 text_field
->set_id(kBananaTextfieldID
);
341 left_container_
->AddChildView(text_field
);
342 text_field
->SetBounds(label_x
+ label_width
+ 5, y
,
343 text_field_width
, label_height
);
345 y
+= label_height
+ gap_between_labels
;
347 label
= new Label(ASCIIToUTF16("Kiwi:"));
348 label
->set_id(kKiwiLabelID
);
349 left_container_
->AddChildView(label
);
350 label
->SetBounds(label_x
, y
, label_width
, label_height
);
352 text_field
= new Textfield();
353 text_field
->set_id(kKiwiTextfieldID
);
354 left_container_
->AddChildView(text_field
);
355 text_field
->SetBounds(label_x
+ label_width
+ 5, y
,
356 text_field_width
, label_height
);
358 y
+= label_height
+ gap_between_labels
;
360 LabelButton
* button
= new LabelButton(NULL
, ASCIIToUTF16("Click me"));
361 button
->SetStyle(Button::STYLE_BUTTON
);
362 button
->SetBounds(label_x
, y
+ 10, 80, 30);
363 button
->set_id(kFruitButtonID
);
364 left_container_
->AddChildView(button
);
367 cb
= new Checkbox(ASCIIToUTF16("This is another check box"));
368 cb
->SetBounds(label_x
+ label_width
+ 5, y
, 180, 20);
369 cb
->set_id(kFruitCheckBoxID
);
370 left_container_
->AddChildView(cb
);
373 Combobox
* combobox
= new Combobox(&combobox_model_
);
374 combobox
->SetBounds(label_x
+ label_width
+ 5, y
, 150, 30);
375 combobox
->set_id(kComboboxID
);
376 left_container_
->AddChildView(combobox
);
378 right_container_
= new PaneView();
379 right_container_
->SetBorder(Border::CreateSolidBorder(1, SK_ColorBLACK
));
380 right_container_
->set_background(
381 Background::CreateSolidBackground(240, 240, 240));
382 right_container_
->set_id(kRightContainerID
);
383 GetContentsView()->AddChildView(right_container_
);
384 right_container_
->SetBounds(270, 35, 300, 200);
387 int radio_button_height
= 18;
388 int gap_between_radio_buttons
= 10;
389 RadioButton
* radio_button
= new RadioButton(ASCIIToUTF16("Asparagus"), 1);
390 radio_button
->set_id(kAsparagusButtonID
);
391 right_container_
->AddChildView(radio_button
);
392 radio_button
->SetBounds(5, y
, 70, radio_button_height
);
393 radio_button
->SetGroup(1);
394 y
+= radio_button_height
+ gap_between_radio_buttons
;
395 radio_button
= new RadioButton(ASCIIToUTF16("Broccoli"), 1);
396 radio_button
->set_id(kBroccoliButtonID
);
397 right_container_
->AddChildView(radio_button
);
398 radio_button
->SetBounds(5, y
, 70, radio_button_height
);
399 radio_button
->SetGroup(1);
400 RadioButton
* radio_button_to_check
= radio_button
;
401 y
+= radio_button_height
+ gap_between_radio_buttons
;
402 radio_button
= new RadioButton(ASCIIToUTF16("Cauliflower"), 1);
403 radio_button
->set_id(kCauliflowerButtonID
);
404 right_container_
->AddChildView(radio_button
);
405 radio_button
->SetBounds(5, y
, 70, radio_button_height
);
406 radio_button
->SetGroup(1);
407 y
+= radio_button_height
+ gap_between_radio_buttons
;
409 View
* inner_container
= new View();
410 inner_container
->SetBorder(Border::CreateSolidBorder(1, SK_ColorBLACK
));
411 inner_container
->set_background(
412 Background::CreateSolidBackground(230, 230, 230));
413 inner_container
->set_id(kInnerContainerID
);
414 right_container_
->AddChildView(inner_container
);
415 inner_container
->SetBounds(100, 10, 150, 180);
417 ScrollView
* scroll_view
= new ScrollView();
418 scroll_view
->set_id(kScrollViewID
);
419 inner_container
->AddChildView(scroll_view
);
420 scroll_view
->SetBounds(1, 1, 148, 178);
422 View
* scroll_content
= new View();
423 scroll_content
->SetBounds(0, 0, 200, 200);
424 scroll_content
->set_background(
425 Background::CreateSolidBackground(200, 200, 200));
426 scroll_view
->SetContents(scroll_content
);
428 static const char* const kTitles
[] = {
429 "Rosetta", "Stupeur et tremblement", "The diner game",
430 "Ridicule", "Le placard", "Les Visiteurs", "Amelie",
431 "Joyeux Noel", "Camping", "Brice de Nice",
435 static const int kIDs
[] = {
436 kRosettaLinkID
, kStupeurEtTremblementLinkID
, kDinerGameLinkID
,
437 kRidiculeLinkID
, kClosetLinkID
, kVisitingLinkID
, kAmelieLinkID
,
438 kJoyeuxNoelLinkID
, kCampingLinkID
, kBriceDeNiceLinkID
,
439 kTaxiLinkID
, kAsterixLinkID
442 DCHECK(arraysize(kTitles
) == arraysize(kIDs
));
445 for (size_t i
= 0; i
< arraysize(kTitles
); ++i
) {
446 Link
* link
= new Link(ASCIIToUTF16(kTitles
[i
]));
447 link
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
448 link
->set_id(kIDs
[i
]);
449 scroll_content
->AddChildView(link
);
450 link
->SetBounds(5, y
, 300, 15);
456 button
= new LabelButton(NULL
, ASCIIToUTF16("OK"));
457 button
->SetStyle(Button::STYLE_BUTTON
);
458 button
->set_id(kOKButtonID
);
459 button
->SetIsDefault(true);
461 GetContentsView()->AddChildView(button
);
462 button
->SetBounds(150, y
, width
, 30);
464 button
= new LabelButton(NULL
, ASCIIToUTF16("Cancel"));
465 button
->SetStyle(Button::STYLE_BUTTON
);
466 button
->set_id(kCancelButtonID
);
467 GetContentsView()->AddChildView(button
);
468 button
->SetBounds(220, y
, width
, 30);
470 button
= new LabelButton(NULL
, ASCIIToUTF16("Help"));
471 button
->SetStyle(Button::STYLE_BUTTON
);
472 button
->set_id(kHelpButtonID
);
473 GetContentsView()->AddChildView(button
);
474 button
->SetBounds(290, y
, width
, 30);
478 View
* contents
= NULL
;
481 // Left bottom box with style checkboxes.
482 contents
= new View();
483 contents
->set_background(Background::CreateSolidBackground(SK_ColorWHITE
));
484 cb
= new Checkbox(ASCIIToUTF16("Bold"));
485 contents
->AddChildView(cb
);
486 cb
->SetBounds(10, 10, 50, 20);
487 cb
->set_id(kBoldCheckBoxID
);
489 cb
= new Checkbox(ASCIIToUTF16("Italic"));
490 contents
->AddChildView(cb
);
491 cb
->SetBounds(70, 10, 50, 20);
492 cb
->set_id(kItalicCheckBoxID
);
494 cb
= new Checkbox(ASCIIToUTF16("Underlined"));
495 contents
->AddChildView(cb
);
496 cb
->SetBounds(130, 10, 70, 20);
497 cb
->set_id(kUnderlinedCheckBoxID
);
499 link
= new Link(ASCIIToUTF16("Help"));
500 contents
->AddChildView(link
);
501 link
->SetBounds(10, 35, 70, 10);
502 link
->set_id(kStyleHelpLinkID
);
504 text_field
= new Textfield();
505 contents
->AddChildView(text_field
);
506 text_field
->SetBounds(10, 50, 100, 20);
507 text_field
->set_id(kStyleTextEditID
);
509 style_tab_
= new TabbedPane();
510 style_tab_
->set_id(kStyleContainerID
);
511 GetContentsView()->AddChildView(style_tab_
);
512 style_tab_
->SetBounds(10, y
, 210, 100);
513 style_tab_
->AddTab(ASCIIToUTF16("Style"), contents
);
514 style_tab_
->AddTab(ASCIIToUTF16("Other"), new View());
516 // Right bottom box with search.
517 contents
= new View();
518 contents
->set_background(Background::CreateSolidBackground(SK_ColorWHITE
));
519 text_field
= new Textfield();
520 contents
->AddChildView(text_field
);
521 text_field
->SetBounds(10, 10, 100, 20);
522 text_field
->set_id(kSearchTextfieldID
);
524 button
= new LabelButton(NULL
, ASCIIToUTF16("Search"));
525 button
->SetStyle(Button::STYLE_BUTTON
);
526 contents
->AddChildView(button
);
527 button
->SetBounds(112, 5, 60, 30);
528 button
->set_id(kSearchButtonID
);
530 link
= new Link(ASCIIToUTF16("Help"));
531 link
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
532 link
->set_id(kHelpLinkID
);
533 contents
->AddChildView(link
);
534 link
->SetBounds(175, 10, 30, 20);
536 search_border_view_
= new BorderView(contents
);
537 search_border_view_
->set_id(kSearchContainerID
);
539 GetContentsView()->AddChildView(search_border_view_
);
540 search_border_view_
->SetBounds(300, y
, 240, 50);
544 contents
= new View();
545 contents
->SetFocusable(true);
546 contents
->set_background(Background::CreateSolidBackground(SK_ColorBLUE
));
547 contents
->set_id(kThumbnailContainerID
);
548 button
= new LabelButton(NULL
, ASCIIToUTF16("Star"));
549 button
->SetStyle(Button::STYLE_BUTTON
);
550 contents
->AddChildView(button
);
551 button
->SetBounds(5, 5, 50, 30);
552 button
->set_id(kThumbnailStarID
);
553 button
= new LabelButton(NULL
, ASCIIToUTF16("SuperStar"));
554 button
->SetStyle(Button::STYLE_BUTTON
);
555 contents
->AddChildView(button
);
556 button
->SetBounds(60, 5, 100, 30);
557 button
->set_id(kThumbnailSuperStarID
);
559 GetContentsView()->AddChildView(contents
);
560 contents
->SetBounds(250, y
, 200, 50);
561 // We can only call RadioButton::SetChecked() on the radio-button is part of
562 // the view hierarchy.
563 radio_button_to_check
->SetChecked(true);
566 TEST_F(FocusTraversalTest
, NormalTraversal
) {
567 const int kTraversalIDs
[] = { kTopCheckBoxID
, kAppleTextfieldID
,
568 kOrangeTextfieldID
, kBananaTextfieldID
, kKiwiTextfieldID
,
569 kFruitButtonID
, kFruitCheckBoxID
, kComboboxID
, kBroccoliButtonID
,
570 kRosettaLinkID
, kStupeurEtTremblementLinkID
,
571 kDinerGameLinkID
, kRidiculeLinkID
, kClosetLinkID
, kVisitingLinkID
,
572 kAmelieLinkID
, kJoyeuxNoelLinkID
, kCampingLinkID
, kBriceDeNiceLinkID
,
573 kTaxiLinkID
, kAsterixLinkID
, kOKButtonID
, kCancelButtonID
, kHelpButtonID
,
574 kStyleContainerID
, kBoldCheckBoxID
, kItalicCheckBoxID
,
575 kUnderlinedCheckBoxID
, kStyleHelpLinkID
, kStyleTextEditID
,
576 kSearchTextfieldID
, kSearchButtonID
, kHelpLinkID
,
577 kThumbnailContainerID
, kThumbnailStarID
, kThumbnailSuperStarID
};
579 // Let's traverse the whole focus hierarchy (several times, to make sure it
581 GetFocusManager()->ClearFocus();
582 for (int i
= 0; i
< 3; ++i
) {
583 for (size_t j
= 0; j
< arraysize(kTraversalIDs
); j
++) {
584 GetFocusManager()->AdvanceFocus(false);
585 View
* focused_view
= GetFocusManager()->GetFocusedView();
586 EXPECT_TRUE(focused_view
!= NULL
);
588 EXPECT_EQ(kTraversalIDs
[j
], focused_view
->id());
592 // Let's traverse in reverse order.
593 GetFocusManager()->ClearFocus();
594 for (int i
= 0; i
< 3; ++i
) {
595 for (int j
= arraysize(kTraversalIDs
) - 1; j
>= 0; --j
) {
596 GetFocusManager()->AdvanceFocus(true);
597 View
* focused_view
= GetFocusManager()->GetFocusedView();
598 EXPECT_TRUE(focused_view
!= NULL
);
600 EXPECT_EQ(kTraversalIDs
[j
], focused_view
->id());
605 TEST_F(FocusTraversalTest
, TraversalWithNonEnabledViews
) {
606 const int kDisabledIDs
[] = {
607 kBananaTextfieldID
, kFruitCheckBoxID
, kComboboxID
, kAsparagusButtonID
,
608 kCauliflowerButtonID
, kClosetLinkID
, kVisitingLinkID
, kBriceDeNiceLinkID
,
609 kTaxiLinkID
, kAsterixLinkID
, kHelpButtonID
, kBoldCheckBoxID
,
610 kSearchTextfieldID
, kHelpLinkID
};
612 const int kTraversalIDs
[] = { kTopCheckBoxID
, kAppleTextfieldID
,
613 kOrangeTextfieldID
, kKiwiTextfieldID
, kFruitButtonID
, kBroccoliButtonID
,
614 kRosettaLinkID
, kStupeurEtTremblementLinkID
, kDinerGameLinkID
,
615 kRidiculeLinkID
, kAmelieLinkID
, kJoyeuxNoelLinkID
, kCampingLinkID
,
616 kOKButtonID
, kCancelButtonID
, kStyleContainerID
, kItalicCheckBoxID
,
617 kUnderlinedCheckBoxID
, kStyleHelpLinkID
, kStyleTextEditID
,
618 kSearchButtonID
, kThumbnailContainerID
, kThumbnailStarID
,
619 kThumbnailSuperStarID
};
621 // Let's disable some views.
622 for (size_t i
= 0; i
< arraysize(kDisabledIDs
); i
++) {
623 View
* v
= FindViewByID(kDisabledIDs
[i
]);
624 ASSERT_TRUE(v
!= NULL
);
625 v
->SetEnabled(false);
629 // Let's do one traversal (several times, to make sure it loops ok).
630 GetFocusManager()->ClearFocus();
631 for (int i
= 0; i
< 3; ++i
) {
632 for (size_t j
= 0; j
< arraysize(kTraversalIDs
); j
++) {
633 GetFocusManager()->AdvanceFocus(false);
634 focused_view
= GetFocusManager()->GetFocusedView();
635 EXPECT_TRUE(focused_view
!= NULL
);
637 EXPECT_EQ(kTraversalIDs
[j
], focused_view
->id());
641 // Same thing in reverse.
642 GetFocusManager()->ClearFocus();
643 for (int i
= 0; i
< 3; ++i
) {
644 for (int j
= arraysize(kTraversalIDs
) - 1; j
>= 0; --j
) {
645 GetFocusManager()->AdvanceFocus(true);
646 focused_view
= GetFocusManager()->GetFocusedView();
647 EXPECT_TRUE(focused_view
!= NULL
);
649 EXPECT_EQ(kTraversalIDs
[j
], focused_view
->id());
654 TEST_F(FocusTraversalTest
, TraversalWithInvisibleViews
) {
655 const int kInvisibleIDs
[] = { kTopCheckBoxID
, kOKButtonID
,
656 kThumbnailContainerID
};
658 const int kTraversalIDs
[] = { kAppleTextfieldID
, kOrangeTextfieldID
,
659 kBananaTextfieldID
, kKiwiTextfieldID
, kFruitButtonID
, kFruitCheckBoxID
,
660 kComboboxID
, kBroccoliButtonID
, kRosettaLinkID
,
661 kStupeurEtTremblementLinkID
, kDinerGameLinkID
, kRidiculeLinkID
,
662 kClosetLinkID
, kVisitingLinkID
, kAmelieLinkID
, kJoyeuxNoelLinkID
,
663 kCampingLinkID
, kBriceDeNiceLinkID
, kTaxiLinkID
, kAsterixLinkID
,
664 kCancelButtonID
, kHelpButtonID
, kStyleContainerID
, kBoldCheckBoxID
,
665 kItalicCheckBoxID
, kUnderlinedCheckBoxID
, kStyleHelpLinkID
,
666 kStyleTextEditID
, kSearchTextfieldID
, kSearchButtonID
, kHelpLinkID
};
669 // Let's make some views invisible.
670 for (size_t i
= 0; i
< arraysize(kInvisibleIDs
); i
++) {
671 View
* v
= FindViewByID(kInvisibleIDs
[i
]);
672 ASSERT_TRUE(v
!= NULL
);
673 v
->SetVisible(false);
677 // Let's do one traversal (several times, to make sure it loops ok).
678 GetFocusManager()->ClearFocus();
679 for (int i
= 0; i
< 3; ++i
) {
680 for (size_t j
= 0; j
< arraysize(kTraversalIDs
); j
++) {
681 GetFocusManager()->AdvanceFocus(false);
682 focused_view
= GetFocusManager()->GetFocusedView();
683 EXPECT_TRUE(focused_view
!= NULL
);
685 EXPECT_EQ(kTraversalIDs
[j
], focused_view
->id());
689 // Same thing in reverse.
690 GetFocusManager()->ClearFocus();
691 for (int i
= 0; i
< 3; ++i
) {
692 for (int j
= arraysize(kTraversalIDs
) - 1; j
>= 0; --j
) {
693 GetFocusManager()->AdvanceFocus(true);
694 focused_view
= GetFocusManager()->GetFocusedView();
695 EXPECT_TRUE(focused_view
!= NULL
);
697 EXPECT_EQ(kTraversalIDs
[j
], focused_view
->id());
702 TEST_F(FocusTraversalTest
, PaneTraversal
) {
703 // Tests trapping the traversal within a pane - useful for full
704 // keyboard accessibility for toolbars.
706 // First test the left container.
707 const int kLeftTraversalIDs
[] = {
709 kOrangeTextfieldID
, kBananaTextfieldID
, kKiwiTextfieldID
,
710 kFruitButtonID
, kFruitCheckBoxID
, kComboboxID
};
712 FocusSearch
focus_search_left(left_container_
, true, false);
713 left_container_
->EnablePaneFocus(&focus_search_left
);
714 FindViewByID(kComboboxID
)->RequestFocus();
716 // Traverse the focus hierarchy within the pane several times.
717 for (int i
= 0; i
< 3; ++i
) {
718 for (size_t j
= 0; j
< arraysize(kLeftTraversalIDs
); j
++) {
719 GetFocusManager()->AdvanceFocus(false);
720 View
* focused_view
= GetFocusManager()->GetFocusedView();
721 EXPECT_TRUE(focused_view
!= NULL
);
723 EXPECT_EQ(kLeftTraversalIDs
[j
], focused_view
->id());
727 // Traverse in reverse order.
728 FindViewByID(kAppleTextfieldID
)->RequestFocus();
729 for (int i
= 0; i
< 3; ++i
) {
730 for (int j
= arraysize(kLeftTraversalIDs
) - 1; j
>= 0; --j
) {
731 GetFocusManager()->AdvanceFocus(true);
732 View
* focused_view
= GetFocusManager()->GetFocusedView();
733 EXPECT_TRUE(focused_view
!= NULL
);
735 EXPECT_EQ(kLeftTraversalIDs
[j
], focused_view
->id());
739 // Now test the right container, but this time with accessibility mode.
740 // Make some links not focusable, but mark one of them as
741 // "accessibility focusable", so it should show up in the traversal.
742 const int kRightTraversalIDs
[] = {
743 kBroccoliButtonID
, kDinerGameLinkID
, kRidiculeLinkID
,
744 kClosetLinkID
, kVisitingLinkID
, kAmelieLinkID
, kJoyeuxNoelLinkID
,
745 kCampingLinkID
, kBriceDeNiceLinkID
, kTaxiLinkID
, kAsterixLinkID
};
747 FocusSearch
focus_search_right(right_container_
, true, true);
748 right_container_
->EnablePaneFocus(&focus_search_right
);
749 FindViewByID(kRosettaLinkID
)->SetFocusable(false);
750 FindViewByID(kStupeurEtTremblementLinkID
)->SetFocusable(false);
751 FindViewByID(kDinerGameLinkID
)->SetAccessibilityFocusable(true);
752 FindViewByID(kDinerGameLinkID
)->SetFocusable(false);
753 FindViewByID(kAsterixLinkID
)->RequestFocus();
755 // Traverse the focus hierarchy within the pane several times.
756 for (int i
= 0; i
< 3; ++i
) {
757 for (size_t j
= 0; j
< arraysize(kRightTraversalIDs
); j
++) {
758 GetFocusManager()->AdvanceFocus(false);
759 View
* focused_view
= GetFocusManager()->GetFocusedView();
760 EXPECT_TRUE(focused_view
!= NULL
);
762 EXPECT_EQ(kRightTraversalIDs
[j
], focused_view
->id());
766 // Traverse in reverse order.
767 FindViewByID(kBroccoliButtonID
)->RequestFocus();
768 for (int i
= 0; i
< 3; ++i
) {
769 for (int j
= arraysize(kRightTraversalIDs
) - 1; j
>= 0; --j
) {
770 GetFocusManager()->AdvanceFocus(true);
771 View
* focused_view
= GetFocusManager()->GetFocusedView();
772 EXPECT_TRUE(focused_view
!= NULL
);
774 EXPECT_EQ(kRightTraversalIDs
[j
], focused_view
->id());