Home | History | Annotate | Download | only in translate
      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