• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
25using TranslateInfoBarUtilities::MoveControl;
26using TranslateInfoBarUtilities::VerticallyCenterView;
27using TranslateInfoBarUtilities::VerifyControlOrderAndSpacing;
28using TranslateInfoBarUtilities::CreateLabel;
29using TranslateInfoBarUtilities::AddMenuItem;
30
31#pragma mark TranslateInfoBarUtilities helper functions.
32
33namespace 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|.
37void 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.
58bool 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.
65void 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|.
81NSTextField* 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|.
90void 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:
112InfoBar* 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