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/controls/button/checkbox.h"
12 #include "ui/views/controls/button/label_button.h"
13 #include "ui/views/controls/button/radio_button.h"
14 #include "ui/views/controls/combobox/combobox.h"
15 #include "ui/views/controls/label.h"
16 #include "ui/views/controls/link.h"
17 #include "ui/views/controls/native/native_view_host.h"
18 #include "ui/views/controls/scroll_view.h"
19 #include "ui/views/controls/tabbed_pane/tabbed_pane.h"
20 #include "ui/views/controls/textfield/textfield.h"
21 #include "ui/views/focus/accelerator_handler.h"
22 #include "ui/views/focus/focus_manager_test.h"
23 #include "ui/views/widget/root_view.h"
24 #include "ui/views/widget/widget.h"
32 const int kTopCheckBoxID = count++; // 1
33 const int kLeftContainerID = count++;
34 const int kAppleLabelID = count++;
35 const int kAppleTextfieldID = count++;
36 const int kOrangeLabelID = count++; // 5
37 const int kOrangeTextfieldID = count++;
38 const int kBananaLabelID = count++;
39 const int kBananaTextfieldID = count++;
40 const int kKiwiLabelID = count++;
41 const int kKiwiTextfieldID = count++; // 10
42 const int kFruitButtonID = count++;
43 const int kFruitCheckBoxID = count++;
44 const int kComboboxID = count++;
46 const int kRightContainerID = count++;
47 const int kAsparagusButtonID = count++; // 15
48 const int kBroccoliButtonID = count++;
49 const int kCauliflowerButtonID = count++;
51 const int kInnerContainerID = count++;
52 const int kScrollViewID = count++;
53 const int kRosettaLinkID = count++; // 20
54 const int kStupeurEtTremblementLinkID = count++;
55 const int kDinerGameLinkID = count++;
56 const int kRidiculeLinkID = count++;
57 const int kClosetLinkID = count++;
58 const int kVisitingLinkID = count++; // 25
59 const int kAmelieLinkID = count++;
60 const int kJoyeuxNoelLinkID = count++;
61 const int kCampingLinkID = count++;
62 const int kBriceDeNiceLinkID = count++;
63 const int kTaxiLinkID = count++; // 30
64 const int kAsterixLinkID = count++;
66 const int kOKButtonID = count++;
67 const int kCancelButtonID = count++;
68 const int kHelpButtonID = count++;
70 const int kStyleContainerID = count++; // 35
71 const int kBoldCheckBoxID = count++;
72 const int kItalicCheckBoxID = count++;
73 const int kUnderlinedCheckBoxID = count++;
74 const int kStyleHelpLinkID = count++;
75 const int kStyleTextEditID = count++; // 40
77 const int kSearchContainerID = count++;
78 const int kSearchTextfieldID = count++;
79 const int kSearchButtonID = count++;
80 const int kHelpLinkID = count++;
82 const int kThumbnailContainerID = count++; // 45
83 const int kThumbnailStarID = count++;
84 const int kThumbnailSuperStarID = count++;
86 class DummyComboboxModel : public ui::ComboboxModel {
88 // Overridden from ui::ComboboxModel:
89 virtual int GetItemCount() const OVERRIDE { return 10; }
90 virtual string16 GetItemAt(int index) OVERRIDE {
91 return ASCIIToUTF16("Item ") + base::IntToString16(index);
95 // A View that can act as a pane.
96 class PaneView : public View, public FocusTraversable {
98 PaneView() : focus_search_(NULL) {}
100 // If this method is called, this view will use GetPaneFocusTraversable to
101 // have this provided FocusSearch used instead of the default one, allowing
102 // you to trap focus within the pane.
103 void EnablePaneFocus(FocusSearch* focus_search) {
104 focus_search_ = focus_search;
107 // Overridden from View:
108 virtual FocusTraversable* GetPaneFocusTraversable() OVERRIDE {
115 // Overridden from FocusTraversable:
116 virtual views::FocusSearch* GetFocusSearch() OVERRIDE {
117 return focus_search_;
119 virtual FocusTraversable* GetFocusTraversableParent() OVERRIDE {
122 virtual View* GetFocusTraversableParentView() OVERRIDE {
127 FocusSearch* focus_search_;
130 // BorderView is a view containing a native window with its own view hierarchy.
131 // It is interesting to test focus traversal from a view hierarchy to an inner
133 class BorderView : public NativeViewHost {
135 explicit BorderView(View* child) : child_(child), widget_(NULL) {
137 set_focusable(false);
140 virtual ~BorderView() {}
142 virtual internal::RootView* GetContentsRootView() {
143 return static_cast<internal::RootView*>(widget_->GetRootView());
146 virtual FocusTraversable* GetFocusTraversable() OVERRIDE {
147 return static_cast<internal::RootView*>(widget_->GetRootView());
150 virtual void ViewHierarchyChanged(
151 const ViewHierarchyChangedDetails& details) OVERRIDE {
152 NativeViewHost::ViewHierarchyChanged(details);
154 if (details.child == this && details.is_add) {
156 widget_ = new Widget;
157 Widget::InitParams params(Widget::InitParams::TYPE_CONTROL);
158 #if defined(OS_WIN) || defined(USE_AURA)
159 params.parent = details.parent->GetWidget()->GetNativeView();
163 widget_->Init(params);
164 widget_->SetFocusTraversableParentView(this);
165 widget_->SetContentsView(child_);
168 // We have been added to a view hierarchy, attach the native view.
169 Attach(widget_->GetNativeView());
170 // Also update the FocusTraversable parent so the focus traversal works.
171 static_cast<internal::RootView*>(widget_->GetRootView())->
172 SetFocusTraversableParent(GetWidget()->GetFocusTraversable());
180 DISALLOW_COPY_AND_ASSIGN(BorderView);
185 class FocusTraversalTest : public FocusManagerTest {
187 virtual ~FocusTraversalTest();
189 virtual void InitContentView() OVERRIDE;
192 FocusTraversalTest();
194 View* FindViewByID(int id) {
195 View* view = GetContentsView()->GetViewByID(id);
199 view = style_tab_->GetSelectedTab()->GetViewByID(id);
202 view = search_border_view_->GetContentsRootView()->GetViewByID(id);
209 TabbedPane* style_tab_;
210 BorderView* search_border_view_;
211 DummyComboboxModel combobox_model_;
212 PaneView* left_container_;
213 PaneView* right_container_;
215 DISALLOW_COPY_AND_ASSIGN(FocusTraversalTest);
218 FocusTraversalTest::FocusTraversalTest()
220 search_border_view_(NULL) {
223 FocusTraversalTest::~FocusTraversalTest() {
226 void FocusTraversalTest::InitContentView() {
227 // Create a complicated view hierarchy with lots of control types for
228 // use by all of the focus traversal tests.
230 // Class name, ID, and asterisk next to focusable views:
233 // Checkbox * kTopCheckBoxID
234 // PaneView kLeftContainerID
235 // Label kAppleLabelID
236 // Textfield * kAppleTextfieldID
237 // Label kOrangeLabelID
238 // Textfield * kOrangeTextfieldID
239 // Label kBananaLabelID
240 // Textfield * kBananaTextfieldID
241 // Label kKiwiLabelID
242 // Textfield * kKiwiTextfieldID
243 // NativeButton * kFruitButtonID
244 // Checkbox * kFruitCheckBoxID
245 // Combobox * kComboboxID
246 // PaneView kRightContainerID
247 // RadioButton * kAsparagusButtonID
248 // RadioButton * kBroccoliButtonID
249 // RadioButton * kCauliflowerButtonID
250 // View kInnerContainerID
251 // ScrollView kScrollViewID
253 // Link * kRosettaLinkID
254 // Link * kStupeurEtTremblementLinkID
255 // Link * kDinerGameLinkID
256 // Link * kRidiculeLinkID
257 // Link * kClosetLinkID
258 // Link * kVisitingLinkID
259 // Link * kAmelieLinkID
260 // Link * kJoyeuxNoelLinkID
261 // Link * kCampingLinkID
262 // Link * kBriceDeNiceLinkID
263 // Link * kTaxiLinkID
264 // Link * kAsterixLinkID
265 // NativeButton * kOKButtonID
266 // NativeButton * kCancelButtonID
267 // NativeButton * kHelpButtonID
268 // TabbedPane * kStyleContainerID
270 // Checkbox * kBoldCheckBoxID
271 // Checkbox * kItalicCheckBoxID
272 // Checkbox * kUnderlinedCheckBoxID
273 // Link * kStyleHelpLinkID
274 // Textfield * kStyleTextEditID
276 // BorderView kSearchContainerID
278 // Textfield * kSearchTextfieldID
279 // NativeButton * kSearchButtonID
280 // Link * kHelpLinkID
281 // View * kThumbnailContainerID
282 // NativeButton * kThumbnailStarID
283 // NativeButton * kThumbnailSuperStarID
285 GetContentsView()->set_background(
286 Background::CreateSolidBackground(SK_ColorWHITE));
288 Checkbox* cb = new Checkbox(ASCIIToUTF16("This is a checkbox"));
289 GetContentsView()->AddChildView(cb);
290 // In this fast paced world, who really has time for non hard-coded layout?
291 cb->SetBounds(10, 10, 200, 20);
292 cb->set_id(kTopCheckBoxID);
294 left_container_ = new PaneView();
295 left_container_->set_border(Border::CreateSolidBorder(1, SK_ColorBLACK));
296 left_container_->set_background(
297 Background::CreateSolidBackground(240, 240, 240));
298 left_container_->set_id(kLeftContainerID);
299 GetContentsView()->AddChildView(left_container_);
300 left_container_->SetBounds(10, 35, 250, 200);
303 int label_width = 50;
304 int label_height = 15;
305 int text_field_width = 150;
307 int gap_between_labels = 10;
309 Label* label = new Label(ASCIIToUTF16("Apple:"));
310 label->set_id(kAppleLabelID);
311 left_container_->AddChildView(label);
312 label->SetBounds(label_x, y, label_width, label_height);
314 Textfield* text_field = new Textfield();
315 text_field->set_id(kAppleTextfieldID);
316 left_container_->AddChildView(text_field);
317 text_field->SetBounds(label_x + label_width + 5, y,
318 text_field_width, label_height);
320 y += label_height + gap_between_labels;
322 label = new Label(ASCIIToUTF16("Orange:"));
323 label->set_id(kOrangeLabelID);
324 left_container_->AddChildView(label);
325 label->SetBounds(label_x, y, label_width, label_height);
327 text_field = new Textfield();
328 text_field->set_id(kOrangeTextfieldID);
329 left_container_->AddChildView(text_field);
330 text_field->SetBounds(label_x + label_width + 5, y,
331 text_field_width, label_height);
333 y += label_height + gap_between_labels;
335 label = new Label(ASCIIToUTF16("Banana:"));
336 label->set_id(kBananaLabelID);
337 left_container_->AddChildView(label);
338 label->SetBounds(label_x, y, label_width, label_height);
340 text_field = new Textfield();
341 text_field->set_id(kBananaTextfieldID);
342 left_container_->AddChildView(text_field);
343 text_field->SetBounds(label_x + label_width + 5, y,
344 text_field_width, label_height);
346 y += label_height + gap_between_labels;
348 label = new Label(ASCIIToUTF16("Kiwi:"));
349 label->set_id(kKiwiLabelID);
350 left_container_->AddChildView(label);
351 label->SetBounds(label_x, y, label_width, label_height);
353 text_field = new Textfield();
354 text_field->set_id(kKiwiTextfieldID);
355 left_container_->AddChildView(text_field);
356 text_field->SetBounds(label_x + label_width + 5, y,
357 text_field_width, label_height);
359 y += label_height + gap_between_labels;
361 LabelButton* button = new LabelButton(NULL, ASCIIToUTF16("Click me"));
362 button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON);
363 button->SetBounds(label_x, y + 10, 80, 30);
364 button->set_id(kFruitButtonID);
365 left_container_->AddChildView(button);
368 cb = new Checkbox(ASCIIToUTF16("This is another check box"));
369 cb->SetBounds(label_x + label_width + 5, y, 180, 20);
370 cb->set_id(kFruitCheckBoxID);
371 left_container_->AddChildView(cb);
374 Combobox* combobox = new Combobox(&combobox_model_);
375 combobox->SetBounds(label_x + label_width + 5, y, 150, 30);
376 combobox->set_id(kComboboxID);
377 left_container_->AddChildView(combobox);
379 right_container_ = new PaneView();
380 right_container_->set_border(Border::CreateSolidBorder(1, SK_ColorBLACK));
381 right_container_->set_background(
382 Background::CreateSolidBackground(240, 240, 240));
383 right_container_->set_id(kRightContainerID);
384 GetContentsView()->AddChildView(right_container_);
385 right_container_->SetBounds(270, 35, 300, 200);
388 int radio_button_height = 18;
389 int gap_between_radio_buttons = 10;
390 RadioButton* radio_button = new RadioButton(ASCIIToUTF16("Asparagus"), 1);
391 radio_button->set_id(kAsparagusButtonID);
392 right_container_->AddChildView(radio_button);
393 radio_button->SetBounds(5, y, 70, radio_button_height);
394 radio_button->SetGroup(1);
395 y += radio_button_height + gap_between_radio_buttons;
396 radio_button = new RadioButton(ASCIIToUTF16("Broccoli"), 1);
397 radio_button->set_id(kBroccoliButtonID);
398 right_container_->AddChildView(radio_button);
399 radio_button->SetBounds(5, y, 70, radio_button_height);
400 radio_button->SetGroup(1);
401 RadioButton* radio_button_to_check = radio_button;
402 y += radio_button_height + gap_between_radio_buttons;
403 radio_button = new RadioButton(ASCIIToUTF16("Cauliflower"), 1);
404 radio_button->set_id(kCauliflowerButtonID);
405 right_container_->AddChildView(radio_button);
406 radio_button->SetBounds(5, y, 70, radio_button_height);
407 radio_button->SetGroup(1);
408 y += radio_button_height + gap_between_radio_buttons;
410 View* inner_container = new View();
411 inner_container->set_border(Border::CreateSolidBorder(1, SK_ColorBLACK));
412 inner_container->set_background(
413 Background::CreateSolidBackground(230, 230, 230));
414 inner_container->set_id(kInnerContainerID);
415 right_container_->AddChildView(inner_container);
416 inner_container->SetBounds(100, 10, 150, 180);
418 ScrollView* scroll_view = new ScrollView();
419 scroll_view->set_id(kScrollViewID);
420 inner_container->AddChildView(scroll_view);
421 scroll_view->SetBounds(1, 1, 148, 178);
423 View* scroll_content = new View();
424 scroll_content->SetBounds(0, 0, 200, 200);
425 scroll_content->set_background(
426 Background::CreateSolidBackground(200, 200, 200));
427 scroll_view->SetContents(scroll_content);
429 static const char* const kTitles[] = {
430 "Rosetta", "Stupeur et tremblement", "The diner game",
431 "Ridicule", "Le placard", "Les Visiteurs", "Amelie",
432 "Joyeux Noel", "Camping", "Brice de Nice",
436 static const int kIDs[] = {
437 kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID,
438 kRidiculeLinkID, kClosetLinkID, kVisitingLinkID, kAmelieLinkID,
439 kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID,
440 kTaxiLinkID, kAsterixLinkID
443 DCHECK(arraysize(kTitles) == arraysize(kIDs));
446 for (size_t i = 0; i < arraysize(kTitles); ++i) {
447 Link* link = new Link(ASCIIToUTF16(kTitles[i]));
448 link->SetHorizontalAlignment(gfx::ALIGN_LEFT);
449 link->set_id(kIDs[i]);
450 scroll_content->AddChildView(link);
451 link->SetBounds(5, y, 300, 15);
457 button = new LabelButton(NULL, ASCIIToUTF16("OK"));
458 button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON);
459 button->set_id(kOKButtonID);
460 button->SetIsDefault(true);
462 GetContentsView()->AddChildView(button);
463 button->SetBounds(150, y, width, 30);
465 button = new LabelButton(NULL, ASCIIToUTF16("Cancel"));
466 button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON);
467 button->set_id(kCancelButtonID);
468 GetContentsView()->AddChildView(button);
469 button->SetBounds(220, y, width, 30);
471 button = new LabelButton(NULL, ASCIIToUTF16("Help"));
472 button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON);
473 button->set_id(kHelpButtonID);
474 GetContentsView()->AddChildView(button);
475 button->SetBounds(290, y, width, 30);
479 View* contents = NULL;
482 // Left bottom box with style checkboxes.
483 contents = new View();
484 contents->set_background(Background::CreateSolidBackground(SK_ColorWHITE));
485 cb = new Checkbox(ASCIIToUTF16("Bold"));
486 contents->AddChildView(cb);
487 cb->SetBounds(10, 10, 50, 20);
488 cb->set_id(kBoldCheckBoxID);
490 cb = new Checkbox(ASCIIToUTF16("Italic"));
491 contents->AddChildView(cb);
492 cb->SetBounds(70, 10, 50, 20);
493 cb->set_id(kItalicCheckBoxID);
495 cb = new Checkbox(ASCIIToUTF16("Underlined"));
496 contents->AddChildView(cb);
497 cb->SetBounds(130, 10, 70, 20);
498 cb->set_id(kUnderlinedCheckBoxID);
500 link = new Link(ASCIIToUTF16("Help"));
501 contents->AddChildView(link);
502 link->SetBounds(10, 35, 70, 10);
503 link->set_id(kStyleHelpLinkID);
505 text_field = new Textfield();
506 contents->AddChildView(text_field);
507 text_field->SetBounds(10, 50, 100, 20);
508 text_field->set_id(kStyleTextEditID);
510 style_tab_ = new TabbedPane(false);
511 style_tab_->set_id(kStyleContainerID);
512 GetContentsView()->AddChildView(style_tab_);
513 style_tab_->SetBounds(10, y, 210, 100);
514 style_tab_->AddTab(ASCIIToUTF16("Style"), contents);
515 style_tab_->AddTab(ASCIIToUTF16("Other"), new View());
517 // Right bottom box with search.
518 contents = new View();
519 contents->set_background(Background::CreateSolidBackground(SK_ColorWHITE));
520 text_field = new Textfield();
521 contents->AddChildView(text_field);
522 text_field->SetBounds(10, 10, 100, 20);
523 text_field->set_id(kSearchTextfieldID);
525 button = new LabelButton(NULL, ASCIIToUTF16("Search"));
526 button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON);
527 contents->AddChildView(button);
528 button->SetBounds(112, 5, 60, 30);
529 button->set_id(kSearchButtonID);
531 link = new Link(ASCIIToUTF16("Help"));
532 link->SetHorizontalAlignment(gfx::ALIGN_LEFT);
533 link->set_id(kHelpLinkID);
534 contents->AddChildView(link);
535 link->SetBounds(175, 10, 30, 20);
537 search_border_view_ = new BorderView(contents);
538 search_border_view_->set_id(kSearchContainerID);
540 GetContentsView()->AddChildView(search_border_view_);
541 search_border_view_->SetBounds(300, y, 240, 50);
545 contents = new View();
546 contents->set_focusable(true);
547 contents->set_background(Background::CreateSolidBackground(SK_ColorBLUE));
548 contents->set_id(kThumbnailContainerID);
549 button = new LabelButton(NULL, ASCIIToUTF16("Star"));
550 button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON);
551 contents->AddChildView(button);
552 button->SetBounds(5, 5, 50, 30);
553 button->set_id(kThumbnailStarID);
554 button = new LabelButton(NULL, ASCIIToUTF16("SuperStar"));
555 button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON);
556 contents->AddChildView(button);
557 button->SetBounds(60, 5, 100, 30);
558 button->set_id(kThumbnailSuperStarID);
560 GetContentsView()->AddChildView(contents);
561 contents->SetBounds(250, y, 200, 50);
562 // We can only call RadioButton::SetChecked() on the radio-button is part of
563 // the view hierarchy.
564 radio_button_to_check->SetChecked(true);
567 TEST_F(FocusTraversalTest, NormalTraversal) {
568 const int kTraversalIDs[] = { kTopCheckBoxID, kAppleTextfieldID,
569 kOrangeTextfieldID, kBananaTextfieldID, kKiwiTextfieldID,
570 kFruitButtonID, kFruitCheckBoxID, kComboboxID, kBroccoliButtonID,
571 kRosettaLinkID, kStupeurEtTremblementLinkID,
572 kDinerGameLinkID, kRidiculeLinkID, kClosetLinkID, kVisitingLinkID,
573 kAmelieLinkID, kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID,
574 kTaxiLinkID, kAsterixLinkID, kOKButtonID, kCancelButtonID, kHelpButtonID,
575 kStyleContainerID, kBoldCheckBoxID, kItalicCheckBoxID,
576 kUnderlinedCheckBoxID, kStyleHelpLinkID, kStyleTextEditID,
577 kSearchTextfieldID, kSearchButtonID, kHelpLinkID,
578 kThumbnailContainerID, kThumbnailStarID, kThumbnailSuperStarID };
580 // Uncomment the following line to manually test the UI of this test.
581 // base::RunLoop(new AcceleratorHandler()).Run();
583 // Let's traverse the whole focus hierarchy (several times, to make sure it
585 GetFocusManager()->ClearFocus();
586 for (int i = 0; i < 3; ++i) {
587 for (size_t j = 0; j < arraysize(kTraversalIDs); j++) {
588 GetFocusManager()->AdvanceFocus(false);
589 View* focused_view = GetFocusManager()->GetFocusedView();
590 EXPECT_TRUE(focused_view != NULL);
592 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
596 // Let's traverse in reverse order.
597 GetFocusManager()->ClearFocus();
598 for (int i = 0; i < 3; ++i) {
599 for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
600 GetFocusManager()->AdvanceFocus(true);
601 View* focused_view = GetFocusManager()->GetFocusedView();
602 EXPECT_TRUE(focused_view != NULL);
604 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
609 TEST_F(FocusTraversalTest, TraversalWithNonEnabledViews) {
610 const int kDisabledIDs[] = {
611 kBananaTextfieldID, kFruitCheckBoxID, kComboboxID, kAsparagusButtonID,
612 kCauliflowerButtonID, kClosetLinkID, kVisitingLinkID, kBriceDeNiceLinkID,
613 kTaxiLinkID, kAsterixLinkID, kHelpButtonID, kBoldCheckBoxID,
614 kSearchTextfieldID, kHelpLinkID };
616 const int kTraversalIDs[] = { kTopCheckBoxID, kAppleTextfieldID,
617 kOrangeTextfieldID, kKiwiTextfieldID, kFruitButtonID, kBroccoliButtonID,
618 kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID,
619 kRidiculeLinkID, kAmelieLinkID, kJoyeuxNoelLinkID, kCampingLinkID,
620 kOKButtonID, kCancelButtonID, kStyleContainerID, kItalicCheckBoxID,
621 kUnderlinedCheckBoxID, kStyleHelpLinkID, kStyleTextEditID,
622 kSearchButtonID, kThumbnailContainerID, kThumbnailStarID,
623 kThumbnailSuperStarID };
625 // Let's disable some views.
626 for (size_t i = 0; i < arraysize(kDisabledIDs); i++) {
627 View* v = FindViewByID(kDisabledIDs[i]);
628 ASSERT_TRUE(v != NULL);
629 v->SetEnabled(false);
632 // Uncomment the following line to manually test the UI of this test.
633 // base::RunLoop(new AcceleratorHandler()).Run();
636 // Let's do one traversal (several times, to make sure it loops ok).
637 GetFocusManager()->ClearFocus();
638 for (int i = 0; i < 3; ++i) {
639 for (size_t j = 0; j < arraysize(kTraversalIDs); j++) {
640 GetFocusManager()->AdvanceFocus(false);
641 focused_view = GetFocusManager()->GetFocusedView();
642 EXPECT_TRUE(focused_view != NULL);
644 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
648 // Same thing in reverse.
649 GetFocusManager()->ClearFocus();
650 for (int i = 0; i < 3; ++i) {
651 for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
652 GetFocusManager()->AdvanceFocus(true);
653 focused_view = GetFocusManager()->GetFocusedView();
654 EXPECT_TRUE(focused_view != NULL);
656 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
661 TEST_F(FocusTraversalTest, TraversalWithInvisibleViews) {
662 const int kInvisibleIDs[] = { kTopCheckBoxID, kOKButtonID,
663 kThumbnailContainerID };
665 const int kTraversalIDs[] = { kAppleTextfieldID, kOrangeTextfieldID,
666 kBananaTextfieldID, kKiwiTextfieldID, kFruitButtonID, kFruitCheckBoxID,
667 kComboboxID, kBroccoliButtonID, kRosettaLinkID,
668 kStupeurEtTremblementLinkID, kDinerGameLinkID, kRidiculeLinkID,
669 kClosetLinkID, kVisitingLinkID, kAmelieLinkID, kJoyeuxNoelLinkID,
670 kCampingLinkID, kBriceDeNiceLinkID, kTaxiLinkID, kAsterixLinkID,
671 kCancelButtonID, kHelpButtonID, kStyleContainerID, kBoldCheckBoxID,
672 kItalicCheckBoxID, kUnderlinedCheckBoxID, kStyleHelpLinkID,
673 kStyleTextEditID, kSearchTextfieldID, kSearchButtonID, kHelpLinkID };
676 // Let's make some views invisible.
677 for (size_t i = 0; i < arraysize(kInvisibleIDs); i++) {
678 View* v = FindViewByID(kInvisibleIDs[i]);
679 ASSERT_TRUE(v != NULL);
680 v->SetVisible(false);
683 // Uncomment the following line to manually test the UI of this test.
684 // base::RunLoop(new AcceleratorHandler()).Run();
687 // Let's do one traversal (several times, to make sure it loops ok).
688 GetFocusManager()->ClearFocus();
689 for (int i = 0; i < 3; ++i) {
690 for (size_t j = 0; j < arraysize(kTraversalIDs); j++) {
691 GetFocusManager()->AdvanceFocus(false);
692 focused_view = GetFocusManager()->GetFocusedView();
693 EXPECT_TRUE(focused_view != NULL);
695 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
699 // Same thing in reverse.
700 GetFocusManager()->ClearFocus();
701 for (int i = 0; i < 3; ++i) {
702 for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
703 GetFocusManager()->AdvanceFocus(true);
704 focused_view = GetFocusManager()->GetFocusedView();
705 EXPECT_TRUE(focused_view != NULL);
707 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
712 TEST_F(FocusTraversalTest, PaneTraversal) {
713 // Tests trapping the traversal within a pane - useful for full
714 // keyboard accessibility for toolbars.
716 // First test the left container.
717 const int kLeftTraversalIDs[] = {
719 kOrangeTextfieldID, kBananaTextfieldID, kKiwiTextfieldID,
720 kFruitButtonID, kFruitCheckBoxID, kComboboxID };
722 FocusSearch focus_search_left(left_container_, true, false);
723 left_container_->EnablePaneFocus(&focus_search_left);
724 FindViewByID(kComboboxID)->RequestFocus();
726 // Traverse the focus hierarchy within the pane several times.
727 for (int i = 0; i < 3; ++i) {
728 for (size_t j = 0; j < arraysize(kLeftTraversalIDs); j++) {
729 GetFocusManager()->AdvanceFocus(false);
730 View* focused_view = GetFocusManager()->GetFocusedView();
731 EXPECT_TRUE(focused_view != NULL);
733 EXPECT_EQ(kLeftTraversalIDs[j], focused_view->id());
737 // Traverse in reverse order.
738 FindViewByID(kAppleTextfieldID)->RequestFocus();
739 for (int i = 0; i < 3; ++i) {
740 for (int j = arraysize(kLeftTraversalIDs) - 1; j >= 0; --j) {
741 GetFocusManager()->AdvanceFocus(true);
742 View* focused_view = GetFocusManager()->GetFocusedView();
743 EXPECT_TRUE(focused_view != NULL);
745 EXPECT_EQ(kLeftTraversalIDs[j], focused_view->id());
749 // Now test the right container, but this time with accessibility mode.
750 // Make some links not focusable, but mark one of them as
751 // "accessibility focusable", so it should show up in the traversal.
752 const int kRightTraversalIDs[] = {
753 kBroccoliButtonID, kDinerGameLinkID, kRidiculeLinkID,
754 kClosetLinkID, kVisitingLinkID, kAmelieLinkID, kJoyeuxNoelLinkID,
755 kCampingLinkID, kBriceDeNiceLinkID, kTaxiLinkID, kAsterixLinkID };
757 FocusSearch focus_search_right(right_container_, true, true);
758 right_container_->EnablePaneFocus(&focus_search_right);
759 FindViewByID(kRosettaLinkID)->set_focusable(false);
760 FindViewByID(kStupeurEtTremblementLinkID)->set_focusable(false);
761 FindViewByID(kDinerGameLinkID)->set_accessibility_focusable(true);
762 FindViewByID(kDinerGameLinkID)->set_focusable(false);
763 FindViewByID(kAsterixLinkID)->RequestFocus();
765 // Traverse the focus hierarchy within the pane several times.
766 for (int i = 0; i < 3; ++i) {
767 for (size_t j = 0; j < arraysize(kRightTraversalIDs); j++) {
768 GetFocusManager()->AdvanceFocus(false);
769 View* focused_view = GetFocusManager()->GetFocusedView();
770 EXPECT_TRUE(focused_view != NULL);
772 EXPECT_EQ(kRightTraversalIDs[j], focused_view->id());
776 // Traverse in reverse order.
777 FindViewByID(kBroccoliButtonID)->RequestFocus();
778 for (int i = 0; i < 3; ++i) {
779 for (int j = arraysize(kRightTraversalIDs) - 1; j >= 0; --j) {
780 GetFocusManager()->AdvanceFocus(true);
781 View* focused_view = GetFocusManager()->GetFocusedView();
782 EXPECT_TRUE(focused_view != NULL);
784 EXPECT_EQ(kRightTraversalIDs[j], focused_view->id());