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/focus/accelerator_handler.h" 24 #include "ui/views/focus/focus_manager_test.h" 25 #include "ui/views/widget/root_view.h" 26 #include "ui/views/widget/widget.h" 27 28 namespace views { 29 30 namespace { 31 32 int count = 1; 33 34 const int kTopCheckBoxID = count++; // 1 35 const int kLeftContainerID = count++; 36 const int kAppleLabelID = count++; 37 const int kAppleTextfieldID = count++; 38 const int kOrangeLabelID = count++; // 5 39 const int kOrangeTextfieldID = count++; 40 const int kBananaLabelID = count++; 41 const int kBananaTextfieldID = count++; 42 const int kKiwiLabelID = count++; 43 const int kKiwiTextfieldID = count++; // 10 44 const int kFruitButtonID = count++; 45 const int kFruitCheckBoxID = count++; 46 const int kComboboxID = count++; 47 48 const int kRightContainerID = count++; 49 const int kAsparagusButtonID = count++; // 15 50 const int kBroccoliButtonID = count++; 51 const int kCauliflowerButtonID = count++; 52 53 const int kInnerContainerID = count++; 54 const int kScrollViewID = count++; 55 const int kRosettaLinkID = count++; // 20 56 const int kStupeurEtTremblementLinkID = count++; 57 const int kDinerGameLinkID = count++; 58 const int kRidiculeLinkID = count++; 59 const int kClosetLinkID = count++; 60 const int kVisitingLinkID = count++; // 25 61 const int kAmelieLinkID = count++; 62 const int kJoyeuxNoelLinkID = count++; 63 const int kCampingLinkID = count++; 64 const int kBriceDeNiceLinkID = count++; 65 const int kTaxiLinkID = count++; // 30 66 const int kAsterixLinkID = count++; 67 68 const int kOKButtonID = count++; 69 const int kCancelButtonID = count++; 70 const int kHelpButtonID = count++; 71 72 const int kStyleContainerID = count++; // 35 73 const int kBoldCheckBoxID = count++; 74 const int kItalicCheckBoxID = count++; 75 const int kUnderlinedCheckBoxID = count++; 76 const int kStyleHelpLinkID = count++; 77 const int kStyleTextEditID = count++; // 40 78 79 const int kSearchContainerID = count++; 80 const int kSearchTextfieldID = count++; 81 const int kSearchButtonID = count++; 82 const int kHelpLinkID = count++; 83 84 const int kThumbnailContainerID = count++; // 45 85 const int kThumbnailStarID = count++; 86 const int kThumbnailSuperStarID = count++; 87 88 class DummyComboboxModel : public ui::ComboboxModel { 89 public: 90 // Overridden from ui::ComboboxModel: 91 virtual int GetItemCount() const OVERRIDE { return 10; } 92 virtual string16 GetItemAt(int index) OVERRIDE { 93 return ASCIIToUTF16("Item ") + base::IntToString16(index); 94 } 95 }; 96 97 // A View that can act as a pane. 98 class PaneView : public View, public FocusTraversable { 99 public: 100 PaneView() : focus_search_(NULL) {} 101 102 // If this method is called, this view will use GetPaneFocusTraversable to 103 // have this provided FocusSearch used instead of the default one, allowing 104 // you to trap focus within the pane. 105 void EnablePaneFocus(FocusSearch* focus_search) { 106 focus_search_ = focus_search; 107 } 108 109 // Overridden from View: 110 virtual FocusTraversable* GetPaneFocusTraversable() OVERRIDE { 111 if (focus_search_) 112 return this; 113 else 114 return NULL; 115 } 116 117 // Overridden from FocusTraversable: 118 virtual views::FocusSearch* GetFocusSearch() OVERRIDE { 119 return focus_search_; 120 } 121 virtual FocusTraversable* GetFocusTraversableParent() OVERRIDE { 122 return NULL; 123 } 124 virtual View* GetFocusTraversableParentView() OVERRIDE { 125 return NULL; 126 } 127 128 private: 129 FocusSearch* focus_search_; 130 }; 131 132 // BorderView is a view containing a native window with its own view hierarchy. 133 // It is interesting to test focus traversal from a view hierarchy to an inner 134 // view hierarchy. 135 class BorderView : public NativeViewHost { 136 public: 137 explicit BorderView(View* child) : child_(child), widget_(NULL) { 138 DCHECK(child); 139 SetFocusable(false); 140 } 141 142 virtual ~BorderView() {} 143 144 virtual internal::RootView* GetContentsRootView() { 145 return static_cast<internal::RootView*>(widget_->GetRootView()); 146 } 147 148 virtual FocusTraversable* GetFocusTraversable() OVERRIDE { 149 return static_cast<internal::RootView*>(widget_->GetRootView()); 150 } 151 152 virtual void ViewHierarchyChanged( 153 const ViewHierarchyChangedDetails& details) OVERRIDE { 154 NativeViewHost::ViewHierarchyChanged(details); 155 156 if (details.child == this && details.is_add) { 157 if (!widget_) { 158 widget_ = new Widget; 159 Widget::InitParams params(Widget::InitParams::TYPE_CONTROL); 160 #if defined(OS_WIN) || defined(USE_AURA) 161 params.parent = details.parent->GetWidget()->GetNativeView(); 162 #else 163 NOTREACHED(); 164 #endif 165 widget_->Init(params); 166 widget_->SetFocusTraversableParentView(this); 167 widget_->SetContentsView(child_); 168 } 169 170 // We have been added to a view hierarchy, attach the native view. 171 Attach(widget_->GetNativeView()); 172 // Also update the FocusTraversable parent so the focus traversal works. 173 static_cast<internal::RootView*>(widget_->GetRootView())-> 174 SetFocusTraversableParent(GetWidget()->GetFocusTraversable()); 175 } 176 } 177 178 private: 179 View* child_; 180 Widget* widget_; 181 182 DISALLOW_COPY_AND_ASSIGN(BorderView); 183 }; 184 185 } // namespace 186 187 class FocusTraversalTest : public FocusManagerTest { 188 public: 189 virtual ~FocusTraversalTest(); 190 191 virtual void InitContentView() OVERRIDE; 192 193 protected: 194 FocusTraversalTest(); 195 196 View* FindViewByID(int id) { 197 View* view = GetContentsView()->GetViewByID(id); 198 if (view) 199 return view; 200 if (style_tab_) 201 view = style_tab_->GetSelectedTab()->GetViewByID(id); 202 if (view) 203 return view; 204 view = search_border_view_->GetContentsRootView()->GetViewByID(id); 205 if (view) 206 return view; 207 return NULL; 208 } 209 210 protected: 211 TabbedPane* style_tab_; 212 BorderView* search_border_view_; 213 DummyComboboxModel combobox_model_; 214 PaneView* left_container_; 215 PaneView* right_container_; 216 217 DISALLOW_COPY_AND_ASSIGN(FocusTraversalTest); 218 }; 219 220 FocusTraversalTest::FocusTraversalTest() 221 : style_tab_(NULL), 222 search_border_view_(NULL) { 223 } 224 225 FocusTraversalTest::~FocusTraversalTest() { 226 } 227 228 void FocusTraversalTest::InitContentView() { 229 // Create a complicated view hierarchy with lots of control types for 230 // use by all of the focus traversal tests. 231 // 232 // Class name, ID, and asterisk next to focusable views: 233 // 234 // View 235 // Checkbox * kTopCheckBoxID 236 // PaneView kLeftContainerID 237 // Label kAppleLabelID 238 // Textfield * kAppleTextfieldID 239 // Label kOrangeLabelID 240 // Textfield * kOrangeTextfieldID 241 // Label kBananaLabelID 242 // Textfield * kBananaTextfieldID 243 // Label kKiwiLabelID 244 // Textfield * kKiwiTextfieldID 245 // NativeButton * kFruitButtonID 246 // Checkbox * kFruitCheckBoxID 247 // Combobox * kComboboxID 248 // PaneView kRightContainerID 249 // RadioButton * kAsparagusButtonID 250 // RadioButton * kBroccoliButtonID 251 // RadioButton * kCauliflowerButtonID 252 // View kInnerContainerID 253 // ScrollView kScrollViewID 254 // View 255 // Link * kRosettaLinkID 256 // Link * kStupeurEtTremblementLinkID 257 // Link * kDinerGameLinkID 258 // Link * kRidiculeLinkID 259 // Link * kClosetLinkID 260 // Link * kVisitingLinkID 261 // Link * kAmelieLinkID 262 // Link * kJoyeuxNoelLinkID 263 // Link * kCampingLinkID 264 // Link * kBriceDeNiceLinkID 265 // Link * kTaxiLinkID 266 // Link * kAsterixLinkID 267 // NativeButton * kOKButtonID 268 // NativeButton * kCancelButtonID 269 // NativeButton * kHelpButtonID 270 // TabbedPane * kStyleContainerID 271 // View 272 // Checkbox * kBoldCheckBoxID 273 // Checkbox * kItalicCheckBoxID 274 // Checkbox * kUnderlinedCheckBoxID 275 // Link * kStyleHelpLinkID 276 // Textfield * kStyleTextEditID 277 // Other 278 // BorderView kSearchContainerID 279 // View 280 // Textfield * kSearchTextfieldID 281 // NativeButton * kSearchButtonID 282 // Link * kHelpLinkID 283 // View * kThumbnailContainerID 284 // NativeButton * kThumbnailStarID 285 // NativeButton * kThumbnailSuperStarID 286 287 GetContentsView()->set_background( 288 Background::CreateSolidBackground(SK_ColorWHITE)); 289 290 Checkbox* cb = new Checkbox(ASCIIToUTF16("This is a checkbox")); 291 GetContentsView()->AddChildView(cb); 292 // In this fast paced world, who really has time for non hard-coded layout? 293 cb->SetBounds(10, 10, 200, 20); 294 cb->set_id(kTopCheckBoxID); 295 296 left_container_ = new PaneView(); 297 left_container_->set_border(Border::CreateSolidBorder(1, SK_ColorBLACK)); 298 left_container_->set_background( 299 Background::CreateSolidBackground(240, 240, 240)); 300 left_container_->set_id(kLeftContainerID); 301 GetContentsView()->AddChildView(left_container_); 302 left_container_->SetBounds(10, 35, 250, 200); 303 304 int label_x = 5; 305 int label_width = 50; 306 int label_height = 15; 307 int text_field_width = 150; 308 int y = 10; 309 int gap_between_labels = 10; 310 311 Label* label = new Label(ASCIIToUTF16("Apple:")); 312 label->set_id(kAppleLabelID); 313 left_container_->AddChildView(label); 314 label->SetBounds(label_x, y, label_width, label_height); 315 316 Textfield* text_field = new Textfield(); 317 text_field->set_id(kAppleTextfieldID); 318 left_container_->AddChildView(text_field); 319 text_field->SetBounds(label_x + label_width + 5, y, 320 text_field_width, label_height); 321 322 y += label_height + gap_between_labels; 323 324 label = new Label(ASCIIToUTF16("Orange:")); 325 label->set_id(kOrangeLabelID); 326 left_container_->AddChildView(label); 327 label->SetBounds(label_x, y, label_width, label_height); 328 329 text_field = new Textfield(); 330 text_field->set_id(kOrangeTextfieldID); 331 left_container_->AddChildView(text_field); 332 text_field->SetBounds(label_x + label_width + 5, y, 333 text_field_width, label_height); 334 335 y += label_height + gap_between_labels; 336 337 label = new Label(ASCIIToUTF16("Banana:")); 338 label->set_id(kBananaLabelID); 339 left_container_->AddChildView(label); 340 label->SetBounds(label_x, y, label_width, label_height); 341 342 text_field = new Textfield(); 343 text_field->set_id(kBananaTextfieldID); 344 left_container_->AddChildView(text_field); 345 text_field->SetBounds(label_x + label_width + 5, y, 346 text_field_width, label_height); 347 348 y += label_height + gap_between_labels; 349 350 label = new Label(ASCIIToUTF16("Kiwi:")); 351 label->set_id(kKiwiLabelID); 352 left_container_->AddChildView(label); 353 label->SetBounds(label_x, y, label_width, label_height); 354 355 text_field = new Textfield(); 356 text_field->set_id(kKiwiTextfieldID); 357 left_container_->AddChildView(text_field); 358 text_field->SetBounds(label_x + label_width + 5, y, 359 text_field_width, label_height); 360 361 y += label_height + gap_between_labels; 362 363 LabelButton* button = new LabelButton(NULL, ASCIIToUTF16("Click me")); 364 button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON); 365 button->SetBounds(label_x, y + 10, 80, 30); 366 button->set_id(kFruitButtonID); 367 left_container_->AddChildView(button); 368 y += 40; 369 370 cb = new Checkbox(ASCIIToUTF16("This is another check box")); 371 cb->SetBounds(label_x + label_width + 5, y, 180, 20); 372 cb->set_id(kFruitCheckBoxID); 373 left_container_->AddChildView(cb); 374 y += 20; 375 376 Combobox* combobox = new Combobox(&combobox_model_); 377 combobox->SetBounds(label_x + label_width + 5, y, 150, 30); 378 combobox->set_id(kComboboxID); 379 left_container_->AddChildView(combobox); 380 381 right_container_ = new PaneView(); 382 right_container_->set_border(Border::CreateSolidBorder(1, SK_ColorBLACK)); 383 right_container_->set_background( 384 Background::CreateSolidBackground(240, 240, 240)); 385 right_container_->set_id(kRightContainerID); 386 GetContentsView()->AddChildView(right_container_); 387 right_container_->SetBounds(270, 35, 300, 200); 388 389 y = 10; 390 int radio_button_height = 18; 391 int gap_between_radio_buttons = 10; 392 RadioButton* radio_button = new RadioButton(ASCIIToUTF16("Asparagus"), 1); 393 radio_button->set_id(kAsparagusButtonID); 394 right_container_->AddChildView(radio_button); 395 radio_button->SetBounds(5, y, 70, radio_button_height); 396 radio_button->SetGroup(1); 397 y += radio_button_height + gap_between_radio_buttons; 398 radio_button = new RadioButton(ASCIIToUTF16("Broccoli"), 1); 399 radio_button->set_id(kBroccoliButtonID); 400 right_container_->AddChildView(radio_button); 401 radio_button->SetBounds(5, y, 70, radio_button_height); 402 radio_button->SetGroup(1); 403 RadioButton* radio_button_to_check = radio_button; 404 y += radio_button_height + gap_between_radio_buttons; 405 radio_button = new RadioButton(ASCIIToUTF16("Cauliflower"), 1); 406 radio_button->set_id(kCauliflowerButtonID); 407 right_container_->AddChildView(radio_button); 408 radio_button->SetBounds(5, y, 70, radio_button_height); 409 radio_button->SetGroup(1); 410 y += radio_button_height + gap_between_radio_buttons; 411 412 View* inner_container = new View(); 413 inner_container->set_border(Border::CreateSolidBorder(1, SK_ColorBLACK)); 414 inner_container->set_background( 415 Background::CreateSolidBackground(230, 230, 230)); 416 inner_container->set_id(kInnerContainerID); 417 right_container_->AddChildView(inner_container); 418 inner_container->SetBounds(100, 10, 150, 180); 419 420 ScrollView* scroll_view = new ScrollView(); 421 scroll_view->set_id(kScrollViewID); 422 inner_container->AddChildView(scroll_view); 423 scroll_view->SetBounds(1, 1, 148, 178); 424 425 View* scroll_content = new View(); 426 scroll_content->SetBounds(0, 0, 200, 200); 427 scroll_content->set_background( 428 Background::CreateSolidBackground(200, 200, 200)); 429 scroll_view->SetContents(scroll_content); 430 431 static const char* const kTitles[] = { 432 "Rosetta", "Stupeur et tremblement", "The diner game", 433 "Ridicule", "Le placard", "Les Visiteurs", "Amelie", 434 "Joyeux Noel", "Camping", "Brice de Nice", 435 "Taxi", "Asterix" 436 }; 437 438 static const int kIDs[] = { 439 kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID, 440 kRidiculeLinkID, kClosetLinkID, kVisitingLinkID, kAmelieLinkID, 441 kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID, 442 kTaxiLinkID, kAsterixLinkID 443 }; 444 445 DCHECK(arraysize(kTitles) == arraysize(kIDs)); 446 447 y = 5; 448 for (size_t i = 0; i < arraysize(kTitles); ++i) { 449 Link* link = new Link(ASCIIToUTF16(kTitles[i])); 450 link->SetHorizontalAlignment(gfx::ALIGN_LEFT); 451 link->set_id(kIDs[i]); 452 scroll_content->AddChildView(link); 453 link->SetBounds(5, y, 300, 15); 454 y += 15; 455 } 456 457 y = 250; 458 int width = 60; 459 button = new LabelButton(NULL, ASCIIToUTF16("OK")); 460 button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON); 461 button->set_id(kOKButtonID); 462 button->SetIsDefault(true); 463 464 GetContentsView()->AddChildView(button); 465 button->SetBounds(150, y, width, 30); 466 467 button = new LabelButton(NULL, ASCIIToUTF16("Cancel")); 468 button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON); 469 button->set_id(kCancelButtonID); 470 GetContentsView()->AddChildView(button); 471 button->SetBounds(220, y, width, 30); 472 473 button = new LabelButton(NULL, ASCIIToUTF16("Help")); 474 button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON); 475 button->set_id(kHelpButtonID); 476 GetContentsView()->AddChildView(button); 477 button->SetBounds(290, y, width, 30); 478 479 y += 40; 480 481 View* contents = NULL; 482 Link* link = NULL; 483 484 // Left bottom box with style checkboxes. 485 contents = new View(); 486 contents->set_background(Background::CreateSolidBackground(SK_ColorWHITE)); 487 cb = new Checkbox(ASCIIToUTF16("Bold")); 488 contents->AddChildView(cb); 489 cb->SetBounds(10, 10, 50, 20); 490 cb->set_id(kBoldCheckBoxID); 491 492 cb = new Checkbox(ASCIIToUTF16("Italic")); 493 contents->AddChildView(cb); 494 cb->SetBounds(70, 10, 50, 20); 495 cb->set_id(kItalicCheckBoxID); 496 497 cb = new Checkbox(ASCIIToUTF16("Underlined")); 498 contents->AddChildView(cb); 499 cb->SetBounds(130, 10, 70, 20); 500 cb->set_id(kUnderlinedCheckBoxID); 501 502 link = new Link(ASCIIToUTF16("Help")); 503 contents->AddChildView(link); 504 link->SetBounds(10, 35, 70, 10); 505 link->set_id(kStyleHelpLinkID); 506 507 text_field = new Textfield(); 508 contents->AddChildView(text_field); 509 text_field->SetBounds(10, 50, 100, 20); 510 text_field->set_id(kStyleTextEditID); 511 512 style_tab_ = new TabbedPane(); 513 style_tab_->set_id(kStyleContainerID); 514 GetContentsView()->AddChildView(style_tab_); 515 style_tab_->SetBounds(10, y, 210, 100); 516 style_tab_->AddTab(ASCIIToUTF16("Style"), contents); 517 style_tab_->AddTab(ASCIIToUTF16("Other"), new View()); 518 519 // Right bottom box with search. 520 contents = new View(); 521 contents->set_background(Background::CreateSolidBackground(SK_ColorWHITE)); 522 text_field = new Textfield(); 523 contents->AddChildView(text_field); 524 text_field->SetBounds(10, 10, 100, 20); 525 text_field->set_id(kSearchTextfieldID); 526 527 button = new LabelButton(NULL, ASCIIToUTF16("Search")); 528 button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON); 529 contents->AddChildView(button); 530 button->SetBounds(112, 5, 60, 30); 531 button->set_id(kSearchButtonID); 532 533 link = new Link(ASCIIToUTF16("Help")); 534 link->SetHorizontalAlignment(gfx::ALIGN_LEFT); 535 link->set_id(kHelpLinkID); 536 contents->AddChildView(link); 537 link->SetBounds(175, 10, 30, 20); 538 539 search_border_view_ = new BorderView(contents); 540 search_border_view_->set_id(kSearchContainerID); 541 542 GetContentsView()->AddChildView(search_border_view_); 543 search_border_view_->SetBounds(300, y, 240, 50); 544 545 y += 60; 546 547 contents = new View(); 548 contents->SetFocusable(true); 549 contents->set_background(Background::CreateSolidBackground(SK_ColorBLUE)); 550 contents->set_id(kThumbnailContainerID); 551 button = new LabelButton(NULL, ASCIIToUTF16("Star")); 552 button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON); 553 contents->AddChildView(button); 554 button->SetBounds(5, 5, 50, 30); 555 button->set_id(kThumbnailStarID); 556 button = new LabelButton(NULL, ASCIIToUTF16("SuperStar")); 557 button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON); 558 contents->AddChildView(button); 559 button->SetBounds(60, 5, 100, 30); 560 button->set_id(kThumbnailSuperStarID); 561 562 GetContentsView()->AddChildView(contents); 563 contents->SetBounds(250, y, 200, 50); 564 // We can only call RadioButton::SetChecked() on the radio-button is part of 565 // the view hierarchy. 566 radio_button_to_check->SetChecked(true); 567 } 568 569 TEST_F(FocusTraversalTest, NormalTraversal) { 570 const int kTraversalIDs[] = { kTopCheckBoxID, kAppleTextfieldID, 571 kOrangeTextfieldID, kBananaTextfieldID, kKiwiTextfieldID, 572 kFruitButtonID, kFruitCheckBoxID, kComboboxID, kBroccoliButtonID, 573 kRosettaLinkID, kStupeurEtTremblementLinkID, 574 kDinerGameLinkID, kRidiculeLinkID, kClosetLinkID, kVisitingLinkID, 575 kAmelieLinkID, kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID, 576 kTaxiLinkID, kAsterixLinkID, kOKButtonID, kCancelButtonID, kHelpButtonID, 577 kStyleContainerID, kBoldCheckBoxID, kItalicCheckBoxID, 578 kUnderlinedCheckBoxID, kStyleHelpLinkID, kStyleTextEditID, 579 kSearchTextfieldID, kSearchButtonID, kHelpLinkID, 580 kThumbnailContainerID, kThumbnailStarID, kThumbnailSuperStarID }; 581 582 // Uncomment the following line to manually test the UI of this test. 583 // base::RunLoop(new AcceleratorHandler()).Run(); 584 585 // Let's traverse the whole focus hierarchy (several times, to make sure it 586 // loops OK). 587 GetFocusManager()->ClearFocus(); 588 for (int i = 0; i < 3; ++i) { 589 for (size_t j = 0; j < arraysize(kTraversalIDs); j++) { 590 GetFocusManager()->AdvanceFocus(false); 591 View* focused_view = GetFocusManager()->GetFocusedView(); 592 EXPECT_TRUE(focused_view != NULL); 593 if (focused_view) 594 EXPECT_EQ(kTraversalIDs[j], focused_view->id()); 595 } 596 } 597 598 // Let's traverse in reverse order. 599 GetFocusManager()->ClearFocus(); 600 for (int i = 0; i < 3; ++i) { 601 for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) { 602 GetFocusManager()->AdvanceFocus(true); 603 View* focused_view = GetFocusManager()->GetFocusedView(); 604 EXPECT_TRUE(focused_view != NULL); 605 if (focused_view) 606 EXPECT_EQ(kTraversalIDs[j], focused_view->id()); 607 } 608 } 609 } 610 611 TEST_F(FocusTraversalTest, TraversalWithNonEnabledViews) { 612 const int kDisabledIDs[] = { 613 kBananaTextfieldID, kFruitCheckBoxID, kComboboxID, kAsparagusButtonID, 614 kCauliflowerButtonID, kClosetLinkID, kVisitingLinkID, kBriceDeNiceLinkID, 615 kTaxiLinkID, kAsterixLinkID, kHelpButtonID, kBoldCheckBoxID, 616 kSearchTextfieldID, kHelpLinkID }; 617 618 const int kTraversalIDs[] = { kTopCheckBoxID, kAppleTextfieldID, 619 kOrangeTextfieldID, kKiwiTextfieldID, kFruitButtonID, kBroccoliButtonID, 620 kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID, 621 kRidiculeLinkID, kAmelieLinkID, kJoyeuxNoelLinkID, kCampingLinkID, 622 kOKButtonID, kCancelButtonID, kStyleContainerID, kItalicCheckBoxID, 623 kUnderlinedCheckBoxID, kStyleHelpLinkID, kStyleTextEditID, 624 kSearchButtonID, kThumbnailContainerID, kThumbnailStarID, 625 kThumbnailSuperStarID }; 626 627 // Let's disable some views. 628 for (size_t i = 0; i < arraysize(kDisabledIDs); i++) { 629 View* v = FindViewByID(kDisabledIDs[i]); 630 ASSERT_TRUE(v != NULL); 631 v->SetEnabled(false); 632 } 633 634 // Uncomment the following line to manually test the UI of this test. 635 // base::RunLoop(new AcceleratorHandler()).Run(); 636 637 View* focused_view; 638 // Let's do one traversal (several times, to make sure it loops ok). 639 GetFocusManager()->ClearFocus(); 640 for (int i = 0; i < 3; ++i) { 641 for (size_t j = 0; j < arraysize(kTraversalIDs); j++) { 642 GetFocusManager()->AdvanceFocus(false); 643 focused_view = GetFocusManager()->GetFocusedView(); 644 EXPECT_TRUE(focused_view != NULL); 645 if (focused_view) 646 EXPECT_EQ(kTraversalIDs[j], focused_view->id()); 647 } 648 } 649 650 // Same thing in reverse. 651 GetFocusManager()->ClearFocus(); 652 for (int i = 0; i < 3; ++i) { 653 for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) { 654 GetFocusManager()->AdvanceFocus(true); 655 focused_view = GetFocusManager()->GetFocusedView(); 656 EXPECT_TRUE(focused_view != NULL); 657 if (focused_view) 658 EXPECT_EQ(kTraversalIDs[j], focused_view->id()); 659 } 660 } 661 } 662 663 TEST_F(FocusTraversalTest, TraversalWithInvisibleViews) { 664 const int kInvisibleIDs[] = { kTopCheckBoxID, kOKButtonID, 665 kThumbnailContainerID }; 666 667 const int kTraversalIDs[] = { kAppleTextfieldID, kOrangeTextfieldID, 668 kBananaTextfieldID, kKiwiTextfieldID, kFruitButtonID, kFruitCheckBoxID, 669 kComboboxID, kBroccoliButtonID, kRosettaLinkID, 670 kStupeurEtTremblementLinkID, kDinerGameLinkID, kRidiculeLinkID, 671 kClosetLinkID, kVisitingLinkID, kAmelieLinkID, kJoyeuxNoelLinkID, 672 kCampingLinkID, kBriceDeNiceLinkID, kTaxiLinkID, kAsterixLinkID, 673 kCancelButtonID, kHelpButtonID, kStyleContainerID, kBoldCheckBoxID, 674 kItalicCheckBoxID, kUnderlinedCheckBoxID, kStyleHelpLinkID, 675 kStyleTextEditID, kSearchTextfieldID, kSearchButtonID, kHelpLinkID }; 676 677 678 // Let's make some views invisible. 679 for (size_t i = 0; i < arraysize(kInvisibleIDs); i++) { 680 View* v = FindViewByID(kInvisibleIDs[i]); 681 ASSERT_TRUE(v != NULL); 682 v->SetVisible(false); 683 } 684 685 // Uncomment the following line to manually test the UI of this test. 686 // base::RunLoop(new AcceleratorHandler()).Run(); 687 688 View* focused_view; 689 // Let's do one traversal (several times, to make sure it loops ok). 690 GetFocusManager()->ClearFocus(); 691 for (int i = 0; i < 3; ++i) { 692 for (size_t j = 0; j < arraysize(kTraversalIDs); j++) { 693 GetFocusManager()->AdvanceFocus(false); 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 // Same thing in reverse. 702 GetFocusManager()->ClearFocus(); 703 for (int i = 0; i < 3; ++i) { 704 for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) { 705 GetFocusManager()->AdvanceFocus(true); 706 focused_view = GetFocusManager()->GetFocusedView(); 707 EXPECT_TRUE(focused_view != NULL); 708 if (focused_view) 709 EXPECT_EQ(kTraversalIDs[j], focused_view->id()); 710 } 711 } 712 } 713 714 TEST_F(FocusTraversalTest, PaneTraversal) { 715 // Tests trapping the traversal within a pane - useful for full 716 // keyboard accessibility for toolbars. 717 718 // First test the left container. 719 const int kLeftTraversalIDs[] = { 720 kAppleTextfieldID, 721 kOrangeTextfieldID, kBananaTextfieldID, kKiwiTextfieldID, 722 kFruitButtonID, kFruitCheckBoxID, kComboboxID }; 723 724 FocusSearch focus_search_left(left_container_, true, false); 725 left_container_->EnablePaneFocus(&focus_search_left); 726 FindViewByID(kComboboxID)->RequestFocus(); 727 728 // Traverse the focus hierarchy within the pane several times. 729 for (int i = 0; i < 3; ++i) { 730 for (size_t j = 0; j < arraysize(kLeftTraversalIDs); j++) { 731 GetFocusManager()->AdvanceFocus(false); 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 // Traverse in reverse order. 740 FindViewByID(kAppleTextfieldID)->RequestFocus(); 741 for (int i = 0; i < 3; ++i) { 742 for (int j = arraysize(kLeftTraversalIDs) - 1; j >= 0; --j) { 743 GetFocusManager()->AdvanceFocus(true); 744 View* focused_view = GetFocusManager()->GetFocusedView(); 745 EXPECT_TRUE(focused_view != NULL); 746 if (focused_view) 747 EXPECT_EQ(kLeftTraversalIDs[j], focused_view->id()); 748 } 749 } 750 751 // Now test the right container, but this time with accessibility mode. 752 // Make some links not focusable, but mark one of them as 753 // "accessibility focusable", so it should show up in the traversal. 754 const int kRightTraversalIDs[] = { 755 kBroccoliButtonID, kDinerGameLinkID, kRidiculeLinkID, 756 kClosetLinkID, kVisitingLinkID, kAmelieLinkID, kJoyeuxNoelLinkID, 757 kCampingLinkID, kBriceDeNiceLinkID, kTaxiLinkID, kAsterixLinkID }; 758 759 FocusSearch focus_search_right(right_container_, true, true); 760 right_container_->EnablePaneFocus(&focus_search_right); 761 FindViewByID(kRosettaLinkID)->SetFocusable(false); 762 FindViewByID(kStupeurEtTremblementLinkID)->SetFocusable(false); 763 FindViewByID(kDinerGameLinkID)->SetAccessibilityFocusable(true); 764 FindViewByID(kDinerGameLinkID)->SetFocusable(false); 765 FindViewByID(kAsterixLinkID)->RequestFocus(); 766 767 // Traverse the focus hierarchy within the pane several times. 768 for (int i = 0; i < 3; ++i) { 769 for (size_t j = 0; j < arraysize(kRightTraversalIDs); j++) { 770 GetFocusManager()->AdvanceFocus(false); 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 // Traverse in reverse order. 779 FindViewByID(kBroccoliButtonID)->RequestFocus(); 780 for (int i = 0; i < 3; ++i) { 781 for (int j = arraysize(kRightTraversalIDs) - 1; j >= 0; --j) { 782 GetFocusManager()->AdvanceFocus(true); 783 View* focused_view = GetFocusManager()->GetFocusedView(); 784 EXPECT_TRUE(focused_view != NULL); 785 if (focused_view) 786 EXPECT_EQ(kRightTraversalIDs[j], focused_view->id()); 787 } 788 } 789 } 790 791 } // namespace views 792