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 "ui/views/focus/focus_manager.h" 6 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" 26 27 using base::ASCIIToUTF16; 28 29 namespace views { 30 31 namespace { 32 33 int count = 1; 34 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++; 48 49 const int kRightContainerID = count++; 50 const int kAsparagusButtonID = count++; // 15 51 const int kBroccoliButtonID = count++; 52 const int kCauliflowerButtonID = count++; 53 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++; 68 69 const int kOKButtonID = count++; 70 const int kCancelButtonID = count++; 71 const int kHelpButtonID = count++; 72 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 79 80 const int kSearchContainerID = count++; 81 const int kSearchTextfieldID = count++; 82 const int kSearchButtonID = count++; 83 const int kHelpLinkID = count++; 84 85 const int kThumbnailContainerID = count++; // 45 86 const int kThumbnailStarID = count++; 87 const int kThumbnailSuperStarID = count++; 88 89 class DummyComboboxModel : public ui::ComboboxModel { 90 public: 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); 95 } 96 }; 97 98 // A View that can act as a pane. 99 class PaneView : public View, public FocusTraversable { 100 public: 101 PaneView() : focus_search_(NULL) {} 102 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; 108 } 109 110 // Overridden from View: 111 virtual FocusTraversable* GetPaneFocusTraversable() OVERRIDE { 112 if (focus_search_) 113 return this; 114 else 115 return NULL; 116 } 117 118 // Overridden from FocusTraversable: 119 virtual views::FocusSearch* GetFocusSearch() OVERRIDE { 120 return focus_search_; 121 } 122 virtual FocusTraversable* GetFocusTraversableParent() OVERRIDE { 123 return NULL; 124 } 125 virtual View* GetFocusTraversableParentView() OVERRIDE { 126 return NULL; 127 } 128 129 private: 130 FocusSearch* focus_search_; 131 }; 132 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 135 // view hierarchy. 136 class BorderView : public NativeViewHost { 137 public: 138 explicit BorderView(View* child) : child_(child), widget_(NULL) { 139 DCHECK(child); 140 SetFocusable(false); 141 } 142 143 virtual ~BorderView() {} 144 145 virtual internal::RootView* GetContentsRootView() { 146 return static_cast<internal::RootView*>(widget_->GetRootView()); 147 } 148 149 virtual FocusTraversable* GetFocusTraversable() OVERRIDE { 150 return static_cast<internal::RootView*>(widget_->GetRootView()); 151 } 152 153 virtual void ViewHierarchyChanged( 154 const ViewHierarchyChangedDetails& details) OVERRIDE { 155 NativeViewHost::ViewHierarchyChanged(details); 156 157 if (details.child == this && details.is_add) { 158 if (!widget_) { 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_); 165 } 166 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()); 172 } 173 } 174 175 private: 176 View* child_; 177 Widget* widget_; 178 179 DISALLOW_COPY_AND_ASSIGN(BorderView); 180 }; 181 182 } // namespace 183 184 class FocusTraversalTest : public FocusManagerTest { 185 public: 186 virtual ~FocusTraversalTest(); 187 188 virtual void InitContentView() OVERRIDE; 189 190 protected: 191 FocusTraversalTest(); 192 193 View* FindViewByID(int id) { 194 View* view = GetContentsView()->GetViewByID(id); 195 if (view) 196 return view; 197 if (style_tab_) 198 view = style_tab_->GetSelectedTab()->GetViewByID(id); 199 if (view) 200 return view; 201 view = search_border_view_->GetContentsRootView()->GetViewByID(id); 202 if (view) 203 return view; 204 return NULL; 205 } 206 207 protected: 208 TabbedPane* style_tab_; 209 BorderView* search_border_view_; 210 DummyComboboxModel combobox_model_; 211 PaneView* left_container_; 212 PaneView* right_container_; 213 214 DISALLOW_COPY_AND_ASSIGN(FocusTraversalTest); 215 }; 216 217 FocusTraversalTest::FocusTraversalTest() 218 : style_tab_(NULL), 219 search_border_view_(NULL) { 220 } 221 222 FocusTraversalTest::~FocusTraversalTest() { 223 } 224 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. 228 // 229 // Class name, ID, and asterisk next to focusable views: 230 // 231 // View 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 251 // View 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 268 // View 269 // Checkbox * kBoldCheckBoxID 270 // Checkbox * kItalicCheckBoxID 271 // Checkbox * kUnderlinedCheckBoxID 272 // Link * kStyleHelpLinkID 273 // Textfield * kStyleTextEditID 274 // Other 275 // BorderView kSearchContainerID 276 // View 277 // Textfield * kSearchTextfieldID 278 // NativeButton * kSearchButtonID 279 // Link * kHelpLinkID 280 // View * kThumbnailContainerID 281 // NativeButton * kThumbnailStarID 282 // NativeButton * kThumbnailSuperStarID 283 284 GetContentsView()->set_background( 285 Background::CreateSolidBackground(SK_ColorWHITE)); 286 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); 292 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); 300 301 int label_x = 5; 302 int label_width = 50; 303 int label_height = 15; 304 int text_field_width = 150; 305 int y = 10; 306 int gap_between_labels = 10; 307 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); 312 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); 318 319 y += label_height + gap_between_labels; 320 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); 325 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); 331 332 y += label_height + gap_between_labels; 333 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); 338 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); 344 345 y += label_height + gap_between_labels; 346 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); 351 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); 357 358 y += label_height + gap_between_labels; 359 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); 365 y += 40; 366 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); 371 y += 20; 372 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); 377 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); 385 386 y = 10; 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; 408 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); 416 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); 421 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); 427 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", 432 "Taxi", "Asterix" 433 }; 434 435 static const int kIDs[] = { 436 kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID, 437 kRidiculeLinkID, kClosetLinkID, kVisitingLinkID, kAmelieLinkID, 438 kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID, 439 kTaxiLinkID, kAsterixLinkID 440 }; 441 442 DCHECK(arraysize(kTitles) == arraysize(kIDs)); 443 444 y = 5; 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); 451 y += 15; 452 } 453 454 y = 250; 455 int width = 60; 456 button = new LabelButton(NULL, ASCIIToUTF16("OK")); 457 button->SetStyle(Button::STYLE_BUTTON); 458 button->set_id(kOKButtonID); 459 button->SetIsDefault(true); 460 461 GetContentsView()->AddChildView(button); 462 button->SetBounds(150, y, width, 30); 463 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); 469 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); 475 476 y += 40; 477 478 View* contents = NULL; 479 Link* link = NULL; 480 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); 488 489 cb = new Checkbox(ASCIIToUTF16("Italic")); 490 contents->AddChildView(cb); 491 cb->SetBounds(70, 10, 50, 20); 492 cb->set_id(kItalicCheckBoxID); 493 494 cb = new Checkbox(ASCIIToUTF16("Underlined")); 495 contents->AddChildView(cb); 496 cb->SetBounds(130, 10, 70, 20); 497 cb->set_id(kUnderlinedCheckBoxID); 498 499 link = new Link(ASCIIToUTF16("Help")); 500 contents->AddChildView(link); 501 link->SetBounds(10, 35, 70, 10); 502 link->set_id(kStyleHelpLinkID); 503 504 text_field = new Textfield(); 505 contents->AddChildView(text_field); 506 text_field->SetBounds(10, 50, 100, 20); 507 text_field->set_id(kStyleTextEditID); 508 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()); 515 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); 523 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); 529 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); 535 536 search_border_view_ = new BorderView(contents); 537 search_border_view_->set_id(kSearchContainerID); 538 539 GetContentsView()->AddChildView(search_border_view_); 540 search_border_view_->SetBounds(300, y, 240, 50); 541 542 y += 60; 543 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); 558 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); 564 } 565 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 }; 578 579 // Let's traverse the whole focus hierarchy (several times, to make sure it 580 // loops OK). 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); 587 if (focused_view) 588 EXPECT_EQ(kTraversalIDs[j], focused_view->id()); 589 } 590 } 591 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); 599 if (focused_view) 600 EXPECT_EQ(kTraversalIDs[j], focused_view->id()); 601 } 602 } 603 } 604 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 }; 611 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 }; 620 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); 626 } 627 628 View* focused_view; 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); 636 if (focused_view) 637 EXPECT_EQ(kTraversalIDs[j], focused_view->id()); 638 } 639 } 640 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); 648 if (focused_view) 649 EXPECT_EQ(kTraversalIDs[j], focused_view->id()); 650 } 651 } 652 } 653 654 TEST_F(FocusTraversalTest, TraversalWithInvisibleViews) { 655 const int kInvisibleIDs[] = { kTopCheckBoxID, kOKButtonID, 656 kThumbnailContainerID }; 657 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 }; 667 668 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); 674 } 675 676 View* focused_view; 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); 684 if (focused_view) 685 EXPECT_EQ(kTraversalIDs[j], focused_view->id()); 686 } 687 } 688 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); 696 if (focused_view) 697 EXPECT_EQ(kTraversalIDs[j], focused_view->id()); 698 } 699 } 700 } 701 702 TEST_F(FocusTraversalTest, PaneTraversal) { 703 // Tests trapping the traversal within a pane - useful for full 704 // keyboard accessibility for toolbars. 705 706 // First test the left container. 707 const int kLeftTraversalIDs[] = { 708 kAppleTextfieldID, 709 kOrangeTextfieldID, kBananaTextfieldID, kKiwiTextfieldID, 710 kFruitButtonID, kFruitCheckBoxID, kComboboxID }; 711 712 FocusSearch focus_search_left(left_container_, true, false); 713 left_container_->EnablePaneFocus(&focus_search_left); 714 FindViewByID(kComboboxID)->RequestFocus(); 715 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); 722 if (focused_view) 723 EXPECT_EQ(kLeftTraversalIDs[j], focused_view->id()); 724 } 725 } 726 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); 734 if (focused_view) 735 EXPECT_EQ(kLeftTraversalIDs[j], focused_view->id()); 736 } 737 } 738 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 }; 746 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(); 754 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); 761 if (focused_view) 762 EXPECT_EQ(kRightTraversalIDs[j], focused_view->id()); 763 } 764 } 765 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); 773 if (focused_view) 774 EXPECT_EQ(kRightTraversalIDs[j], focused_view->id()); 775 } 776 } 777 } 778 779 } // namespace views 780