1 // Copyright (c) 2011 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 #import <Cocoa/Cocoa.h> 6 #import "chrome/browser/ui/cocoa/translate/translate_infobar_base.h" 7 8 #include "base/logging.h" 9 #include "base/metrics/histogram.h" 10 #include "base/sys_string_conversions.h" 11 #include "chrome/app/chrome_command_ids.h" 12 #include "chrome/browser/translate/translate_infobar_delegate.h" 13 #import "chrome/browser/ui/cocoa/hover_close_button.h" 14 #include "chrome/browser/ui/cocoa/infobars/infobar.h" 15 #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h" 16 #import "chrome/browser/ui/cocoa/infobars/infobar_controller.h" 17 #import "chrome/browser/ui/cocoa/infobars/infobar_gradient_view.h" 18 #include "chrome/browser/ui/cocoa/translate/after_translate_infobar_controller.h" 19 #import "chrome/browser/ui/cocoa/translate/before_translate_infobar_controller.h" 20 #include "chrome/browser/ui/cocoa/translate/translate_message_infobar_controller.h" 21 #include "grit/generated_resources.h" 22 #include "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" 23 #include "ui/base/l10n/l10n_util.h" 24 25 using TranslateInfoBarUtilities::MoveControl; 26 using TranslateInfoBarUtilities::VerticallyCenterView; 27 using TranslateInfoBarUtilities::VerifyControlOrderAndSpacing; 28 using TranslateInfoBarUtilities::CreateLabel; 29 using TranslateInfoBarUtilities::AddMenuItem; 30 31 #pragma mark TranslateInfoBarUtilities helper functions. 32 33 namespace TranslateInfoBarUtilities { 34 35 // Move the |toMove| view |spacing| pixels before/after the |anchor| view. 36 // |after| signifies the side of |anchor| on which to place |toMove|. 37 void MoveControl(NSView* anchor, NSView* toMove, int spacing, bool after) { 38 NSRect anchorFrame = [anchor frame]; 39 NSRect toMoveFrame = [toMove frame]; 40 41 // At the time of this writing, OS X doesn't natively support BiDi UIs, but 42 // it doesn't hurt to be forward looking. 43 bool toRight = after; 44 45 if (toRight) { 46 toMoveFrame.origin.x = NSMaxX(anchorFrame) + spacing; 47 } else { 48 // Place toMove to theleft of anchor. 49 toMoveFrame.origin.x = NSMinX(anchorFrame) - 50 spacing - NSWidth(toMoveFrame); 51 } 52 [toMove setFrame:toMoveFrame]; 53 } 54 55 // Check that the control |before| is ordered visually before the |after| 56 // control. 57 // Also, check that there is space between them. 58 bool VerifyControlOrderAndSpacing(id before, id after) { 59 NSRect beforeFrame = [before frame]; 60 NSRect afterFrame = [after frame]; 61 return NSMinX(afterFrame) >= NSMaxX(beforeFrame); 62 } 63 64 // Vertically center |toMove| in its container. 65 void VerticallyCenterView(NSView* toMove) { 66 NSRect superViewFrame = [[toMove superview] frame]; 67 NSRect viewFrame = [toMove frame]; 68 // If the superview is the infobar view, then subtract out the anti-spoof 69 // height so that the content is centered in the content area of the infobar, 70 // rather than in the total height (which includes the bulge). 71 CGFloat superHeight = NSHeight(superViewFrame); 72 if ([[toMove superview] isKindOfClass:[InfoBarGradientView class]]) 73 superHeight = infobars::kBaseHeight; 74 viewFrame.origin.y = 75 floor((superHeight - NSHeight(viewFrame)) / 2.0); 76 [toMove setFrame:viewFrame]; 77 } 78 79 // Creates a label control in the style we need for the translate infobar's 80 // labels within |bounds|. 81 NSTextField* CreateLabel(NSRect bounds) { 82 NSTextField* ret = [[NSTextField alloc] initWithFrame:bounds]; 83 [ret setEditable:NO]; 84 [ret setDrawsBackground:NO]; 85 [ret setBordered:NO]; 86 return ret; 87 } 88 89 // Adds an item with the specified properties to |menu|. 90 void AddMenuItem(NSMenu *menu, id target, SEL selector, NSString* title, 91 int tag, bool enabled, bool checked) { 92 if (tag == -1) { 93 [menu addItem:[NSMenuItem separatorItem]]; 94 } else { 95 NSMenuItem* item = [[[NSMenuItem alloc] 96 initWithTitle:title 97 action:selector 98 keyEquivalent:@""] autorelease]; 99 [item setTag:tag]; 100 [menu addItem:item]; 101 [item setTarget:target]; 102 if (checked) 103 [item setState:NSOnState]; 104 if (!enabled) 105 [item setEnabled:NO]; 106 } 107 } 108 109 } // namespace TranslateInfoBarUtilities 110 111 // TranslateInfoBarDelegate views specific method: 112 InfoBar* TranslateInfoBarDelegate::CreateInfoBar() { 113 TranslateInfoBarControllerBase* infobar_controller = NULL; 114 switch (type_) { 115 case BEFORE_TRANSLATE: 116 infobar_controller = 117 [[BeforeTranslateInfobarController alloc] initWithDelegate:this]; 118 break; 119 case AFTER_TRANSLATE: 120 infobar_controller = 121 [[AfterTranslateInfobarController alloc] initWithDelegate:this]; 122 break; 123 case TRANSLATING: 124 case TRANSLATION_ERROR: 125 infobar_controller = 126 [[TranslateMessageInfobarController alloc] initWithDelegate:this]; 127 break; 128 default: 129 NOTREACHED(); 130 } 131 return new InfoBar(infobar_controller); 132 } 133 134 @implementation TranslateInfoBarControllerBase (FrameChangeObserver) 135 136 // Triggered when the frame changes. This will figure out what size and 137 // visibility the options popup should be. 138 - (void)didChangeFrame:(NSNotification*)notification { 139 [self adjustOptionsButtonSizeAndVisibilityForView: 140 [[self visibleControls] lastObject]]; 141 } 142 143 @end 144 145 146 @interface TranslateInfoBarControllerBase (Private) 147 148 // Removes all controls so that layout can add in only the controls 149 // required. 150 - (void)clearAllControls; 151 152 // Create all the various controls we need for the toolbar. 153 - (void)constructViews; 154 155 // Reloads text for all labels for the current state. 156 - (void)loadLabelText:(TranslateErrors::Type)error; 157 158 // Set the infobar background gradient. 159 - (void)setInfoBarGradientColor; 160 161 // Main function to update the toolbar graphic state and data model after 162 // the state has changed. 163 // Controls are moved around as needed and visibility changed to match the 164 // current state. 165 - (void)updateState; 166 167 // Called when the source or target language selection changes in a menu. 168 // |newLanguageIdx| is the index of the newly selected item in the appropriate 169 // menu. 170 - (void)sourceLanguageModified:(NSInteger)newLanguageIdx; 171 - (void)targetLanguageModified:(NSInteger)newLanguageIdx; 172 173 // Completely rebuild "from" and "to" language menus from the data model. 174 - (void)populateLanguageMenus; 175 176 @end 177 178 #pragma mark TranslateInfoBarController class 179 180 @implementation TranslateInfoBarControllerBase 181 182 - (id)initWithDelegate:(InfoBarDelegate*)delegate { 183 if ((self = [super initWithDelegate:delegate])) { 184 originalLanguageMenuModel_.reset( 185 new LanguagesMenuModel([self delegate], 186 LanguagesMenuModel::ORIGINAL)); 187 188 targetLanguageMenuModel_.reset( 189 new LanguagesMenuModel([self delegate], 190 LanguagesMenuModel::TARGET)); 191 } 192 return self; 193 } 194 195 - (TranslateInfoBarDelegate*)delegate { 196 return reinterpret_cast<TranslateInfoBarDelegate*>(delegate_); 197 } 198 199 - (void)constructViews { 200 // Using a zero or very large frame causes GTMUILocalizerAndLayoutTweaker 201 // to not resize the view properly so we take the bounds of the first label 202 // which is contained in the nib. 203 NSRect bogusFrame = [label_ frame]; 204 label1_.reset(CreateLabel(bogusFrame)); 205 label2_.reset(CreateLabel(bogusFrame)); 206 label3_.reset(CreateLabel(bogusFrame)); 207 208 optionsPopUp_.reset([[NSPopUpButton alloc] initWithFrame:bogusFrame 209 pullsDown:YES]); 210 fromLanguagePopUp_.reset([[NSPopUpButton alloc] initWithFrame:bogusFrame 211 pullsDown:NO]); 212 toLanguagePopUp_.reset([[NSPopUpButton alloc] initWithFrame:bogusFrame 213 pullsDown:NO]); 214 showOriginalButton_.reset([[NSButton alloc] init]); 215 translateMessageButton_.reset([[NSButton alloc] init]); 216 } 217 218 - (void)sourceLanguageModified:(NSInteger)newLanguageIdx { 219 size_t newLanguageIdxSizeT = static_cast<size_t>(newLanguageIdx); 220 DCHECK_NE(TranslateInfoBarDelegate::kNoIndex, newLanguageIdxSizeT); 221 if (newLanguageIdxSizeT == [self delegate]->original_language_index()) 222 return; 223 [self delegate]->SetOriginalLanguage(newLanguageIdxSizeT); 224 int commandId = IDC_TRANSLATE_ORIGINAL_LANGUAGE_BASE + newLanguageIdx; 225 int newMenuIdx = [fromLanguagePopUp_ indexOfItemWithTag:commandId]; 226 [fromLanguagePopUp_ selectItemAtIndex:newMenuIdx]; 227 } 228 229 - (void)targetLanguageModified:(NSInteger)newLanguageIdx { 230 size_t newLanguageIdxSizeT = static_cast<size_t>(newLanguageIdx); 231 DCHECK_NE(TranslateInfoBarDelegate::kNoIndex, newLanguageIdxSizeT); 232 if (newLanguageIdxSizeT == [self delegate]->target_language_index()) 233 return; 234 [self delegate]->SetTargetLanguage(newLanguageIdxSizeT); 235 int commandId = IDC_TRANSLATE_TARGET_LANGUAGE_BASE + newLanguageIdx; 236 int newMenuIdx = [toLanguagePopUp_ indexOfItemWithTag:commandId]; 237 [toLanguagePopUp_ selectItemAtIndex:newMenuIdx]; 238 } 239 240 - (void)loadLabelText { 241 // Do nothing by default, should be implemented by subclasses. 242 } 243 244 - (void)updateState { 245 [self loadLabelText]; 246 [self clearAllControls]; 247 [self showVisibleControls:[self visibleControls]]; 248 [optionsPopUp_ setHidden:![self shouldShowOptionsPopUp]]; 249 [self layout]; 250 [self adjustOptionsButtonSizeAndVisibilityForView: 251 [[self visibleControls] lastObject]]; 252 } 253 254 - (void)setInfoBarGradientColor { 255 NSColor* startingColor = [NSColor colorWithCalibratedWhite:0.93 alpha:1.0]; 256 NSColor* endingColor = [NSColor colorWithCalibratedWhite:0.85 alpha:1.0]; 257 NSGradient* translateInfoBarGradient = 258 [[[NSGradient alloc] initWithStartingColor:startingColor 259 endingColor:endingColor] autorelease]; 260 261 [infoBarView_ setGradient:translateInfoBarGradient]; 262 [infoBarView_ 263 setStrokeColor:[NSColor colorWithCalibratedWhite:0.75 alpha:1.0]]; 264 } 265 266 - (void)removeOkCancelButtons { 267 // Removing okButton_ & cancelButton_ from the view may cause them 268 // to be released and since we can still access them from other areas 269 // in the code later, we need them to be nil when this happens. 270 [okButton_ removeFromSuperview]; 271 okButton_ = nil; 272 [cancelButton_ removeFromSuperview]; 273 cancelButton_ = nil; 274 } 275 276 - (void)clearAllControls { 277 // Step 1: remove all controls from the infobar so we have a clean slate. 278 NSArray *allControls = [self allControls]; 279 280 for (NSControl* control in allControls) { 281 if ([control superview]) 282 [control removeFromSuperview]; 283 } 284 } 285 286 - (void)showVisibleControls:(NSArray*)visibleControls { 287 NSRect optionsFrame = [optionsPopUp_ frame]; 288 for (NSControl* control in visibleControls) { 289 [GTMUILocalizerAndLayoutTweaker sizeToFitView:control]; 290 [control setAutoresizingMask:NSViewMaxXMargin]; 291 292 // Need to check if a view is already attached since |label1_| is always 293 // parented and we don't want to add it again. 294 if (![control superview]) 295 [infoBarView_ addSubview:control]; 296 297 if ([control isKindOfClass:[NSButton class]]) 298 VerticallyCenterView(control); 299 300 // Make "from" and "to" language popup menus the same size as the options 301 // menu. 302 // We don't autosize since some languages names are really long causing 303 // the toolbar to overflow. 304 if ([control isKindOfClass:[NSPopUpButton class]]) 305 [control setFrame:optionsFrame]; 306 } 307 } 308 309 - (void)layout { 310 311 } 312 313 - (NSArray*)visibleControls { 314 return [NSArray array]; 315 } 316 317 - (void)rebuildOptionsMenu:(BOOL)hideTitle { 318 if (![self shouldShowOptionsPopUp]) 319 return; 320 321 // The options model doesn't know how to handle state transitions, so rebuild 322 // it each time through here. 323 optionsMenuModel_.reset( 324 new OptionsMenuModel([self delegate])); 325 326 [optionsPopUp_ removeAllItems]; 327 // Set title. 328 NSString* optionsLabel = hideTitle ? @"" : 329 l10n_util::GetNSString(IDS_TRANSLATE_INFOBAR_OPTIONS); 330 [optionsPopUp_ addItemWithTitle:optionsLabel]; 331 332 // Populate options menu. 333 NSMenu* optionsMenu = [optionsPopUp_ menu]; 334 [optionsMenu setAutoenablesItems:NO]; 335 for (int i = 0; i < optionsMenuModel_->GetItemCount(); ++i) { 336 NSString* title = base::SysUTF16ToNSString( 337 optionsMenuModel_->GetLabelAt(i)); 338 int cmd = optionsMenuModel_->GetCommandIdAt(i); 339 bool checked = optionsMenuModel_->IsItemCheckedAt(i); 340 bool enabled = optionsMenuModel_->IsEnabledAt(i); 341 AddMenuItem(optionsMenu, 342 self, 343 @selector(optionsMenuChanged:), 344 title, 345 cmd, 346 enabled, 347 checked); 348 } 349 } 350 351 - (BOOL)shouldShowOptionsPopUp { 352 return YES; 353 } 354 355 - (void)populateLanguageMenus { 356 NSMenu* originalLanguageMenu = [fromLanguagePopUp_ menu]; 357 [originalLanguageMenu setAutoenablesItems:NO]; 358 int selectedMenuIndex = 0; 359 int selectedLangIndex = 360 static_cast<int>([self delegate]->original_language_index()); 361 for (int i = 0; i < originalLanguageMenuModel_->GetItemCount(); ++i) { 362 NSString* title = base::SysUTF16ToNSString( 363 originalLanguageMenuModel_->GetLabelAt(i)); 364 int cmd = originalLanguageMenuModel_->GetCommandIdAt(i); 365 bool checked = (cmd == selectedLangIndex); 366 if (checked) 367 selectedMenuIndex = i; 368 bool enabled = originalLanguageMenuModel_->IsEnabledAt(i); 369 cmd += IDC_TRANSLATE_ORIGINAL_LANGUAGE_BASE; 370 AddMenuItem(originalLanguageMenu, 371 self, 372 @selector(languageMenuChanged:), 373 title, 374 cmd, 375 enabled, 376 checked); 377 } 378 [fromLanguagePopUp_ selectItemAtIndex:selectedMenuIndex]; 379 380 NSMenu* targetLanguageMenu = [toLanguagePopUp_ menu]; 381 [targetLanguageMenu setAutoenablesItems:NO]; 382 selectedLangIndex = 383 static_cast<int>([self delegate]->target_language_index()); 384 for (int i = 0; i < targetLanguageMenuModel_->GetItemCount(); ++i) { 385 NSString* title = base::SysUTF16ToNSString( 386 targetLanguageMenuModel_->GetLabelAt(i)); 387 int cmd = targetLanguageMenuModel_->GetCommandIdAt(i); 388 bool checked = (cmd == selectedLangIndex); 389 if (checked) 390 selectedMenuIndex = i; 391 bool enabled = targetLanguageMenuModel_->IsEnabledAt(i); 392 cmd += IDC_TRANSLATE_TARGET_LANGUAGE_BASE; 393 AddMenuItem(targetLanguageMenu, 394 self, 395 @selector(languageMenuChanged:), 396 title, 397 cmd, 398 enabled, 399 checked); 400 } 401 [toLanguagePopUp_ selectItemAtIndex:selectedMenuIndex]; 402 } 403 404 - (void)addAdditionalControls { 405 using l10n_util::GetNSString; 406 using l10n_util::GetNSStringWithFixup; 407 408 // Get layout information from the NIB. 409 NSRect okButtonFrame = [okButton_ frame]; 410 NSRect cancelButtonFrame = [cancelButton_ frame]; 411 spaceBetweenControls_ = NSMinX(cancelButtonFrame) - NSMaxX(okButtonFrame); 412 413 // Set infobar background color. 414 [self setInfoBarGradientColor]; 415 416 // Instantiate additional controls. 417 [self constructViews]; 418 419 // Set ourselves as the delegate for the options menu so we can populate it 420 // dynamically. 421 [[optionsPopUp_ menu] setDelegate:self]; 422 423 // Replace label_ with label1_ so we get a consistent look between all the 424 // labels we display in the translate view. 425 [[label_ superview] replaceSubview:label_ with:label1_.get()]; 426 label_.reset(); // Now released. 427 428 // Populate contextual menus. 429 [self rebuildOptionsMenu:NO]; 430 [self populateLanguageMenus]; 431 432 // Set OK & Cancel text. 433 [okButton_ setTitle:GetNSStringWithFixup(IDS_TRANSLATE_INFOBAR_ACCEPT)]; 434 [cancelButton_ setTitle:GetNSStringWithFixup(IDS_TRANSLATE_INFOBAR_DENY)]; 435 436 // Set up "Show original" and "Try again" buttons. 437 [showOriginalButton_ setFrame:okButtonFrame]; 438 439 // Set each of the buttons and popups to the NSTexturedRoundedBezelStyle 440 // (metal-looking) style. 441 NSArray* allControls = [self allControls]; 442 for (NSControl* control in allControls) { 443 if (![control isKindOfClass:[NSButton class]]) 444 continue; 445 NSButton* button = (NSButton*)control; 446 [button setBezelStyle:NSTexturedRoundedBezelStyle]; 447 if ([button isKindOfClass:[NSPopUpButton class]]) { 448 [[button cell] setArrowPosition:NSPopUpArrowAtBottom]; 449 } 450 } 451 // The options button is handled differently than the rest as it floats 452 // to the right. 453 [optionsPopUp_ setBezelStyle:NSTexturedRoundedBezelStyle]; 454 [[optionsPopUp_ cell] setArrowPosition:NSPopUpArrowAtBottom]; 455 456 [showOriginalButton_ setTarget:self]; 457 [showOriginalButton_ setAction:@selector(showOriginal:)]; 458 [translateMessageButton_ setTarget:self]; 459 [translateMessageButton_ setAction:@selector(messageButtonPressed:)]; 460 461 [showOriginalButton_ 462 setTitle:GetNSStringWithFixup(IDS_TRANSLATE_INFOBAR_REVERT)]; 463 464 // Add and configure controls that are visible in all modes. 465 [optionsPopUp_ setAutoresizingMask:NSViewMinXMargin]; 466 // Add "options" popup z-ordered below all other controls so when we 467 // resize the toolbar it doesn't hide them. 468 [infoBarView_ addSubview:optionsPopUp_ 469 positioned:NSWindowBelow 470 relativeTo:nil]; 471 [GTMUILocalizerAndLayoutTweaker sizeToFitView:optionsPopUp_]; 472 MoveControl(closeButton_, optionsPopUp_, spaceBetweenControls_, false); 473 VerticallyCenterView(optionsPopUp_); 474 475 [infoBarView_ setPostsFrameChangedNotifications:YES]; 476 [[NSNotificationCenter defaultCenter] 477 addObserver:self 478 selector:@selector(didChangeFrame:) 479 name:NSViewFrameDidChangeNotification 480 object:infoBarView_]; 481 // Show and place GUI elements. 482 [self updateState]; 483 } 484 485 - (void)infobarWillClose { 486 [[optionsPopUp_ menu] cancelTracking]; 487 [super infobarWillClose]; 488 } 489 490 - (void)adjustOptionsButtonSizeAndVisibilityForView:(NSView*)lastView { 491 [optionsPopUp_ setHidden:NO]; 492 [self rebuildOptionsMenu:NO]; 493 [[optionsPopUp_ cell] setArrowPosition:NSPopUpArrowAtBottom]; 494 [optionsPopUp_ sizeToFit]; 495 496 MoveControl(closeButton_, optionsPopUp_, spaceBetweenControls_, false); 497 if (!VerifyControlOrderAndSpacing(lastView, optionsPopUp_)) { 498 [self rebuildOptionsMenu:YES]; 499 NSRect oldFrame = [optionsPopUp_ frame]; 500 oldFrame.size.width = NSHeight(oldFrame); 501 [optionsPopUp_ setFrame:oldFrame]; 502 [[optionsPopUp_ cell] setArrowPosition:NSPopUpArrowAtCenter]; 503 MoveControl(closeButton_, optionsPopUp_, spaceBetweenControls_, false); 504 if (!VerifyControlOrderAndSpacing(lastView, optionsPopUp_)) { 505 [optionsPopUp_ setHidden:YES]; 506 } 507 } 508 } 509 510 // Called when "Translate" button is clicked. 511 - (IBAction)ok:(id)sender { 512 TranslateInfoBarDelegate* delegate = [self delegate]; 513 TranslateInfoBarDelegate::Type state = delegate->type(); 514 DCHECK(state == TranslateInfoBarDelegate::BEFORE_TRANSLATE || 515 state == TranslateInfoBarDelegate::TRANSLATION_ERROR); 516 delegate->Translate(); 517 UMA_HISTOGRAM_COUNTS("Translate.Translate", 1); 518 } 519 520 // Called when someone clicks on the "Nope" button. 521 - (IBAction)cancel:(id)sender { 522 DCHECK( 523 [self delegate]->type() == TranslateInfoBarDelegate::BEFORE_TRANSLATE); 524 [self delegate]->TranslationDeclined(); 525 UMA_HISTOGRAM_COUNTS("Translate.DeclineTranslate", 1); 526 [super dismiss:nil]; 527 } 528 529 - (void)messageButtonPressed:(id)sender { 530 [self delegate]->MessageInfoBarButtonPressed(); 531 } 532 533 - (IBAction)showOriginal:(id)sender { 534 [self delegate]->RevertTranslation(); 535 } 536 537 // Called when any of the language drop down menus are changed. 538 - (void)languageMenuChanged:(id)item { 539 if ([item respondsToSelector:@selector(tag)]) { 540 int cmd = [item tag]; 541 if (cmd >= IDC_TRANSLATE_TARGET_LANGUAGE_BASE) { 542 cmd -= IDC_TRANSLATE_TARGET_LANGUAGE_BASE; 543 [self targetLanguageModified:cmd]; 544 return; 545 } else if (cmd >= IDC_TRANSLATE_ORIGINAL_LANGUAGE_BASE) { 546 cmd -= IDC_TRANSLATE_ORIGINAL_LANGUAGE_BASE; 547 [self sourceLanguageModified:cmd]; 548 return; 549 } 550 } 551 NOTREACHED() << "Language menu was changed with a bad language ID"; 552 } 553 554 // Called when the options menu is changed. 555 - (void)optionsMenuChanged:(id)item { 556 if ([item respondsToSelector:@selector(tag)]) { 557 int cmd = [item tag]; 558 // Danger Will Robinson! : This call can release the infobar (e.g. invoking 559 // "About Translate" can open a new tab). 560 // Do not access member variables after this line! 561 optionsMenuModel_->ExecuteCommand(cmd); 562 } else { 563 NOTREACHED(); 564 } 565 } 566 567 - (void)dealloc { 568 [[NSNotificationCenter defaultCenter] removeObserver:self]; 569 [super dealloc]; 570 } 571 572 #pragma mark NSMenuDelegate 573 574 // Invoked by virtue of us being set as the delegate for the options menu. 575 - (void)menuNeedsUpdate:(NSMenu *)menu { 576 [self adjustOptionsButtonSizeAndVisibilityForView: 577 [[self visibleControls] lastObject]]; 578 } 579 580 @end 581 582 @implementation TranslateInfoBarControllerBase (TestingAPI) 583 584 - (NSArray*)allControls { 585 return [NSArray arrayWithObjects:label1_.get(),fromLanguagePopUp_.get(), 586 label2_.get(), toLanguagePopUp_.get(), label3_.get(), okButton_, 587 cancelButton_, showOriginalButton_.get(), translateMessageButton_.get(), 588 nil]; 589 } 590 591 - (NSMenu*)optionsMenu { 592 return [optionsPopUp_ menu]; 593 } 594 595 - (NSButton*)translateMessageButton { 596 return translateMessageButton_.get(); 597 } 598 599 - (bool)verifyLayout { 600 // All the controls available to translate infobars, except the options popup. 601 // The options popup is shown/hidden instead of actually removed. This gets 602 // checked in the subclasses. 603 NSArray* allControls = [self allControls]; 604 NSArray* visibleControls = [self visibleControls]; 605 606 // Step 1: Make sure control visibility is what we expect. 607 for (NSUInteger i = 0; i < [allControls count]; ++i) { 608 id control = [allControls objectAtIndex:i]; 609 bool hasSuperView = [control superview]; 610 bool expectedVisibility = [visibleControls containsObject:control]; 611 612 if (expectedVisibility != hasSuperView) { 613 NSString *title = @""; 614 if ([control isKindOfClass:[NSPopUpButton class]]) { 615 title = [[[control menu] itemAtIndex:0] title]; 616 } 617 618 LOG(ERROR) << 619 "State: " << [self description] << 620 " Control @" << i << (hasSuperView ? " has" : " doesn't have") << 621 " a superview" << [[control description] UTF8String] << 622 " Title=" << [title UTF8String]; 623 return false; 624 } 625 } 626 627 // Step 2: Check that controls are ordered correctly with no overlap. 628 id previousControl = nil; 629 for (NSUInteger i = 0; i < [visibleControls count]; ++i) { 630 id control = [visibleControls objectAtIndex:i]; 631 // The options pop up doesn't lay out like the rest of the controls as 632 // it floats to the right. It has some known issues shown in 633 // http://crbug.com/47941. 634 if (control == optionsPopUp_.get()) 635 continue; 636 if (previousControl && 637 !VerifyControlOrderAndSpacing(previousControl, control)) { 638 NSString *title = @""; 639 if ([control isKindOfClass:[NSPopUpButton class]]) { 640 title = [[[control menu] itemAtIndex:0] title]; 641 } 642 LOG(ERROR) << 643 "State: " << [self description] << 644 " Control @" << i << " not ordered correctly: " << 645 [[control description] UTF8String] <<[title UTF8String]; 646 return false; 647 } 648 previousControl = control; 649 } 650 651 return true; 652 } 653 654 @end // TranslateInfoBarControllerBase (TestingAPI) 655 656