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/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" 25 26namespace views { 27 28namespace { 29 30int count = 1; 31 32const int kTopCheckBoxID = count++; // 1 33const int kLeftContainerID = count++; 34const int kAppleLabelID = count++; 35const int kAppleTextfieldID = count++; 36const int kOrangeLabelID = count++; // 5 37const int kOrangeTextfieldID = count++; 38const int kBananaLabelID = count++; 39const int kBananaTextfieldID = count++; 40const int kKiwiLabelID = count++; 41const int kKiwiTextfieldID = count++; // 10 42const int kFruitButtonID = count++; 43const int kFruitCheckBoxID = count++; 44const int kComboboxID = count++; 45 46const int kRightContainerID = count++; 47const int kAsparagusButtonID = count++; // 15 48const int kBroccoliButtonID = count++; 49const int kCauliflowerButtonID = count++; 50 51const int kInnerContainerID = count++; 52const int kScrollViewID = count++; 53const int kRosettaLinkID = count++; // 20 54const int kStupeurEtTremblementLinkID = count++; 55const int kDinerGameLinkID = count++; 56const int kRidiculeLinkID = count++; 57const int kClosetLinkID = count++; 58const int kVisitingLinkID = count++; // 25 59const int kAmelieLinkID = count++; 60const int kJoyeuxNoelLinkID = count++; 61const int kCampingLinkID = count++; 62const int kBriceDeNiceLinkID = count++; 63const int kTaxiLinkID = count++; // 30 64const int kAsterixLinkID = count++; 65 66const int kOKButtonID = count++; 67const int kCancelButtonID = count++; 68const int kHelpButtonID = count++; 69 70const int kStyleContainerID = count++; // 35 71const int kBoldCheckBoxID = count++; 72const int kItalicCheckBoxID = count++; 73const int kUnderlinedCheckBoxID = count++; 74const int kStyleHelpLinkID = count++; 75const int kStyleTextEditID = count++; // 40 76 77const int kSearchContainerID = count++; 78const int kSearchTextfieldID = count++; 79const int kSearchButtonID = count++; 80const int kHelpLinkID = count++; 81 82const int kThumbnailContainerID = count++; // 45 83const int kThumbnailStarID = count++; 84const int kThumbnailSuperStarID = count++; 85 86class DummyComboboxModel : public ui::ComboboxModel { 87 public: 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); 92 } 93}; 94 95// A View that can act as a pane. 96class PaneView : public View, public FocusTraversable { 97 public: 98 PaneView() : focus_search_(NULL) {} 99 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; 105 } 106 107 // Overridden from View: 108 virtual FocusTraversable* GetPaneFocusTraversable() OVERRIDE { 109 if (focus_search_) 110 return this; 111 else 112 return NULL; 113 } 114 115 // Overridden from FocusTraversable: 116 virtual views::FocusSearch* GetFocusSearch() OVERRIDE { 117 return focus_search_; 118 } 119 virtual FocusTraversable* GetFocusTraversableParent() OVERRIDE { 120 return NULL; 121 } 122 virtual View* GetFocusTraversableParentView() OVERRIDE { 123 return NULL; 124 } 125 126 private: 127 FocusSearch* focus_search_; 128}; 129 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 132// view hierarchy. 133class BorderView : public NativeViewHost { 134 public: 135 explicit BorderView(View* child) : child_(child), widget_(NULL) { 136 DCHECK(child); 137 set_focusable(false); 138 } 139 140 virtual ~BorderView() {} 141 142 virtual internal::RootView* GetContentsRootView() { 143 return static_cast<internal::RootView*>(widget_->GetRootView()); 144 } 145 146 virtual FocusTraversable* GetFocusTraversable() OVERRIDE { 147 return static_cast<internal::RootView*>(widget_->GetRootView()); 148 } 149 150 virtual void ViewHierarchyChanged( 151 const ViewHierarchyChangedDetails& details) OVERRIDE { 152 NativeViewHost::ViewHierarchyChanged(details); 153 154 if (details.child == this && details.is_add) { 155 if (!widget_) { 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(); 160#else 161 NOTREACHED(); 162#endif 163 widget_->Init(params); 164 widget_->SetFocusTraversableParentView(this); 165 widget_->SetContentsView(child_); 166 } 167 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()); 173 } 174 } 175 176 private: 177 View* child_; 178 Widget* widget_; 179 180 DISALLOW_COPY_AND_ASSIGN(BorderView); 181}; 182 183} // namespace 184 185class FocusTraversalTest : public FocusManagerTest { 186 public: 187 virtual ~FocusTraversalTest(); 188 189 virtual void InitContentView() OVERRIDE; 190 191 protected: 192 FocusTraversalTest(); 193 194 View* FindViewByID(int id) { 195 View* view = GetContentsView()->GetViewByID(id); 196 if (view) 197 return view; 198 if (style_tab_) 199 view = style_tab_->GetSelectedTab()->GetViewByID(id); 200 if (view) 201 return view; 202 view = search_border_view_->GetContentsRootView()->GetViewByID(id); 203 if (view) 204 return view; 205 return NULL; 206 } 207 208 protected: 209 TabbedPane* style_tab_; 210 BorderView* search_border_view_; 211 DummyComboboxModel combobox_model_; 212 PaneView* left_container_; 213 PaneView* right_container_; 214 215 DISALLOW_COPY_AND_ASSIGN(FocusTraversalTest); 216}; 217 218FocusTraversalTest::FocusTraversalTest() 219 : style_tab_(NULL), 220 search_border_view_(NULL) { 221} 222 223FocusTraversalTest::~FocusTraversalTest() { 224} 225 226void FocusTraversalTest::InitContentView() { 227 // Create a complicated view hierarchy with lots of control types for 228 // use by all of the focus traversal tests. 229 // 230 // Class name, ID, and asterisk next to focusable views: 231 // 232 // View 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 252 // View 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 269 // View 270 // Checkbox * kBoldCheckBoxID 271 // Checkbox * kItalicCheckBoxID 272 // Checkbox * kUnderlinedCheckBoxID 273 // Link * kStyleHelpLinkID 274 // Textfield * kStyleTextEditID 275 // Other 276 // BorderView kSearchContainerID 277 // View 278 // Textfield * kSearchTextfieldID 279 // NativeButton * kSearchButtonID 280 // Link * kHelpLinkID 281 // View * kThumbnailContainerID 282 // NativeButton * kThumbnailStarID 283 // NativeButton * kThumbnailSuperStarID 284 285 GetContentsView()->set_background( 286 Background::CreateSolidBackground(SK_ColorWHITE)); 287 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); 293 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); 301 302 int label_x = 5; 303 int label_width = 50; 304 int label_height = 15; 305 int text_field_width = 150; 306 int y = 10; 307 int gap_between_labels = 10; 308 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); 313 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); 319 320 y += label_height + gap_between_labels; 321 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); 326 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); 332 333 y += label_height + gap_between_labels; 334 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); 339 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); 345 346 y += label_height + gap_between_labels; 347 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); 352 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); 358 359 y += label_height + gap_between_labels; 360 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); 366 y += 40; 367 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); 372 y += 20; 373 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); 378 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); 386 387 y = 10; 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; 409 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); 417 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); 422 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); 428 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", 433 "Taxi", "Asterix" 434 }; 435 436 static const int kIDs[] = { 437 kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID, 438 kRidiculeLinkID, kClosetLinkID, kVisitingLinkID, kAmelieLinkID, 439 kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID, 440 kTaxiLinkID, kAsterixLinkID 441 }; 442 443 DCHECK(arraysize(kTitles) == arraysize(kIDs)); 444 445 y = 5; 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); 452 y += 15; 453 } 454 455 y = 250; 456 int width = 60; 457 button = new LabelButton(NULL, ASCIIToUTF16("OK")); 458 button->SetStyle(Button::STYLE_NATIVE_TEXTBUTTON); 459 button->set_id(kOKButtonID); 460 button->SetIsDefault(true); 461 462 GetContentsView()->AddChildView(button); 463 button->SetBounds(150, y, width, 30); 464 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); 470 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); 476 477 y += 40; 478 479 View* contents = NULL; 480 Link* link = NULL; 481 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); 489 490 cb = new Checkbox(ASCIIToUTF16("Italic")); 491 contents->AddChildView(cb); 492 cb->SetBounds(70, 10, 50, 20); 493 cb->set_id(kItalicCheckBoxID); 494 495 cb = new Checkbox(ASCIIToUTF16("Underlined")); 496 contents->AddChildView(cb); 497 cb->SetBounds(130, 10, 70, 20); 498 cb->set_id(kUnderlinedCheckBoxID); 499 500 link = new Link(ASCIIToUTF16("Help")); 501 contents->AddChildView(link); 502 link->SetBounds(10, 35, 70, 10); 503 link->set_id(kStyleHelpLinkID); 504 505 text_field = new Textfield(); 506 contents->AddChildView(text_field); 507 text_field->SetBounds(10, 50, 100, 20); 508 text_field->set_id(kStyleTextEditID); 509 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()); 516 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); 524 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); 530 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); 536 537 search_border_view_ = new BorderView(contents); 538 search_border_view_->set_id(kSearchContainerID); 539 540 GetContentsView()->AddChildView(search_border_view_); 541 search_border_view_->SetBounds(300, y, 240, 50); 542 543 y += 60; 544 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); 559 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); 565} 566 567TEST_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 }; 579 580 // Uncomment the following line to manually test the UI of this test. 581 // base::RunLoop(new AcceleratorHandler()).Run(); 582 583 // Let's traverse the whole focus hierarchy (several times, to make sure it 584 // loops OK). 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); 591 if (focused_view) 592 EXPECT_EQ(kTraversalIDs[j], focused_view->id()); 593 } 594 } 595 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); 603 if (focused_view) 604 EXPECT_EQ(kTraversalIDs[j], focused_view->id()); 605 } 606 } 607} 608 609TEST_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 }; 615 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 }; 624 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); 630 } 631 632 // Uncomment the following line to manually test the UI of this test. 633 // base::RunLoop(new AcceleratorHandler()).Run(); 634 635 View* focused_view; 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); 643 if (focused_view) 644 EXPECT_EQ(kTraversalIDs[j], focused_view->id()); 645 } 646 } 647 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); 655 if (focused_view) 656 EXPECT_EQ(kTraversalIDs[j], focused_view->id()); 657 } 658 } 659} 660 661TEST_F(FocusTraversalTest, TraversalWithInvisibleViews) { 662 const int kInvisibleIDs[] = { kTopCheckBoxID, kOKButtonID, 663 kThumbnailContainerID }; 664 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 }; 674 675 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); 681 } 682 683 // Uncomment the following line to manually test the UI of this test. 684 // base::RunLoop(new AcceleratorHandler()).Run(); 685 686 View* focused_view; 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); 694 if (focused_view) 695 EXPECT_EQ(kTraversalIDs[j], focused_view->id()); 696 } 697 } 698 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); 706 if (focused_view) 707 EXPECT_EQ(kTraversalIDs[j], focused_view->id()); 708 } 709 } 710} 711 712TEST_F(FocusTraversalTest, PaneTraversal) { 713 // Tests trapping the traversal within a pane - useful for full 714 // keyboard accessibility for toolbars. 715 716 // First test the left container. 717 const int kLeftTraversalIDs[] = { 718 kAppleTextfieldID, 719 kOrangeTextfieldID, kBananaTextfieldID, kKiwiTextfieldID, 720 kFruitButtonID, kFruitCheckBoxID, kComboboxID }; 721 722 FocusSearch focus_search_left(left_container_, true, false); 723 left_container_->EnablePaneFocus(&focus_search_left); 724 FindViewByID(kComboboxID)->RequestFocus(); 725 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); 732 if (focused_view) 733 EXPECT_EQ(kLeftTraversalIDs[j], focused_view->id()); 734 } 735 } 736 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); 744 if (focused_view) 745 EXPECT_EQ(kLeftTraversalIDs[j], focused_view->id()); 746 } 747 } 748 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 }; 756 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(); 764 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); 771 if (focused_view) 772 EXPECT_EQ(kRightTraversalIDs[j], focused_view->id()); 773 } 774 } 775 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); 783 if (focused_view) 784 EXPECT_EQ(kRightTraversalIDs[j], focused_view->id()); 785 } 786 } 787} 788 789} // namespace views 790