Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / infobars / translate_infobar_base.mm
bloba2007c063361a608a36ed6dd54fc5123237ffaae
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.
5 #import "chrome/browser/ui/cocoa/infobars/translate_infobar_base.h"
7 #include "base/logging.h"
8 #include "base/strings/sys_string_conversions.h"
9 #include "chrome/app/chrome_command_ids.h"
10 #include "chrome/browser/translate/translate_infobar_delegate.h"
11 #import "chrome/browser/ui/cocoa/hover_close_button.h"
12 #include "chrome/browser/ui/cocoa/infobars/after_translate_infobar_controller.h"
13 #import "chrome/browser/ui/cocoa/infobars/before_translate_infobar_controller.h"
14 #include "chrome/browser/ui/cocoa/infobars/infobar_cocoa.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 #import "chrome/browser/ui/cocoa/infobars/infobar_utilities.h"
19 #include "chrome/browser/ui/cocoa/infobars/translate_message_infobar_controller.h"
20 #include "grit/generated_resources.h"
21 #include "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
22 #include "ui/base/l10n/l10n_util.h"
24 using InfoBarUtilities::MoveControl;
25 using InfoBarUtilities::VerticallyCenterView;
26 using InfoBarUtilities::VerifyControlOrderAndSpacing;
27 using InfoBarUtilities::CreateLabel;
28 using InfoBarUtilities::AddMenuItem;
30 // static
31 scoped_ptr<InfoBar> TranslateInfoBarDelegate::CreateInfoBar(
32     scoped_ptr<TranslateInfoBarDelegate> delegate) {
33   scoped_ptr<InfoBarCocoa> infobar(
34       new InfoBarCocoa(delegate.PassAs<InfoBarDelegate>()));
35   base::scoped_nsobject<TranslateInfoBarControllerBase> infobar_controller;
36   switch (infobar->delegate()->AsTranslateInfoBarDelegate()->infobar_type()) {
37     case BEFORE_TRANSLATE:
38       infobar_controller.reset([[BeforeTranslateInfobarController alloc]
39           initWithInfoBar:infobar.get()]);
40       break;
41     case AFTER_TRANSLATE:
42       infobar_controller.reset([[AfterTranslateInfobarController alloc]
43           initWithInfoBar:infobar.get()]);
44       break;
45     case TRANSLATING:
46     case TRANSLATION_ERROR:
47       infobar_controller.reset([[TranslateMessageInfobarController alloc]
48           initWithInfoBar:infobar.get()]);
49       break;
50     default:
51       NOTREACHED();
52   }
53   infobar->set_controller(infobar_controller);
54   return infobar.PassAs<InfoBar>();
57 @implementation TranslateInfoBarControllerBase (FrameChangeObserver)
59 // Triggered when the frame changes.  This will figure out what size and
60 // visibility the options popup should be.
61 - (void)didChangeFrame:(NSNotification*)notification {
62   [self adjustOptionsButtonSizeAndVisibilityForView:
63       [[self visibleControls] lastObject]];
66 @end
69 @interface TranslateInfoBarControllerBase (Private)
71 // Removes all controls so that layout can add in only the controls
72 // required.
73 - (void)clearAllControls;
75 // Create all the various controls we need for the toolbar.
76 - (void)constructViews;
78 // Reloads text for all labels for the current state.
79 - (void)loadLabelText:(TranslateErrors::Type)error;
81 // Main function to update the toolbar graphic state and data model after
82 // the state has changed.
83 // Controls are moved around as needed and visibility changed to match the
84 // current state.
85 - (void)updateState;
87 // Called when the source or target language selection changes in a menu.
88 // |newLanguageIdx| is the index of the newly selected item in the appropriate
89 // menu.
90 - (void)sourceLanguageModified:(NSInteger)newLanguageIdx;
91 - (void)targetLanguageModified:(NSInteger)newLanguageIdx;
93 // Completely rebuild "from" and "to" language menus from the data model.
94 - (void)populateLanguageMenus;
96 @end
98 #pragma mark TranslateInfoBarController class
100 @implementation TranslateInfoBarControllerBase
102 - (TranslateInfoBarDelegate*)delegate {
103   return reinterpret_cast<TranslateInfoBarDelegate*>([super delegate]);
106 - (void)constructViews {
107   // Using a zero or very large frame causes GTMUILocalizerAndLayoutTweaker
108   // to not resize the view properly so we take the bounds of the first label
109   // which is contained in the nib.
110   NSRect bogusFrame = [label_ frame];
111   label1_.reset(CreateLabel(bogusFrame));
112   label2_.reset(CreateLabel(bogusFrame));
113   label3_.reset(CreateLabel(bogusFrame));
115   optionsPopUp_.reset([[NSPopUpButton alloc] initWithFrame:bogusFrame
116                                                  pullsDown:YES]);
117   fromLanguagePopUp_.reset([[NSPopUpButton alloc] initWithFrame:bogusFrame
118                                                       pullsDown:NO]);
119   toLanguagePopUp_.reset([[NSPopUpButton alloc] initWithFrame:bogusFrame
120                                                     pullsDown:NO]);
121   showOriginalButton_.reset([[NSButton alloc] init]);
122   translateMessageButton_.reset([[NSButton alloc] init]);
125 - (void)sourceLanguageModified:(NSInteger)newLanguageIdx {
126   size_t newLanguageIdxSizeT = static_cast<size_t>(newLanguageIdx);
127   DCHECK_NE(TranslateInfoBarDelegate::kNoIndex, newLanguageIdxSizeT);
128   if (newLanguageIdxSizeT == [self delegate]->original_language_index())
129     return;
130   [self delegate]->UpdateOriginalLanguageIndex(newLanguageIdxSizeT);
131   if ([self delegate]->infobar_type() ==
132       TranslateInfoBarDelegate::AFTER_TRANSLATE)
133     [self delegate]->Translate();
134   int commandId = IDC_TRANSLATE_ORIGINAL_LANGUAGE_BASE + newLanguageIdx;
135   int newMenuIdx = [fromLanguagePopUp_ indexOfItemWithTag:commandId];
136   [fromLanguagePopUp_ selectItemAtIndex:newMenuIdx];
139 - (void)targetLanguageModified:(NSInteger)newLanguageIdx {
140   size_t newLanguageIdxSizeT = static_cast<size_t>(newLanguageIdx);
141   DCHECK_NE(TranslateInfoBarDelegate::kNoIndex, newLanguageIdxSizeT);
142   if (newLanguageIdxSizeT == [self delegate]->target_language_index())
143     return;
144   [self delegate]->UpdateTargetLanguageIndex(newLanguageIdxSizeT);
145   if ([self delegate]->infobar_type() ==
146       TranslateInfoBarDelegate::AFTER_TRANSLATE)
147     [self delegate]->Translate();
148   int commandId = IDC_TRANSLATE_TARGET_LANGUAGE_BASE + newLanguageIdx;
149   int newMenuIdx = [toLanguagePopUp_ indexOfItemWithTag:commandId];
150   [toLanguagePopUp_ selectItemAtIndex:newMenuIdx];
153 - (void)loadLabelText {
154   // Do nothing by default, should be implemented by subclasses.
157 - (void)updateState {
158   [self loadLabelText];
159   [self clearAllControls];
160   [self showVisibleControls:[self visibleControls]];
161   [optionsPopUp_ setHidden:![self shouldShowOptionsPopUp]];
162   [self layout];
163   [self adjustOptionsButtonSizeAndVisibilityForView:
164       [[self visibleControls] lastObject]];
167 - (void)removeOkCancelButtons {
168   // Removing okButton_ & cancelButton_ from the view may cause them
169   // to be released and since we can still access them from other areas
170   // in the code later, we need them to be nil when this happens.
171   [okButton_ removeFromSuperview];
172   okButton_ = nil;
173   [cancelButton_ removeFromSuperview];
174   cancelButton_ = nil;
177 - (void)clearAllControls {
178   // Step 1: remove all controls from the infobar so we have a clean slate.
179   NSArray *allControls = [self allControls];
181   for (NSControl* control in allControls) {
182     if ([control superview])
183       [control removeFromSuperview];
184   }
187 - (void)showVisibleControls:(NSArray*)visibleControls {
188   NSRect optionsFrame = [optionsPopUp_ frame];
189   for (NSControl* control in visibleControls) {
190     [GTMUILocalizerAndLayoutTweaker sizeToFitView:control];
191     [control setAutoresizingMask:NSViewMaxXMargin];
193     // Need to check if a view is already attached since |label1_| is always
194     // parented and we don't want to add it again.
195     if (![control superview])
196       [infoBarView_ addSubview:control];
198     if ([control isKindOfClass:[NSButton class]])
199       VerticallyCenterView(control);
201     // Make "from" and "to" language popup menus the same size as the options
202     // menu.
203     // We don't autosize since some languages names are really long causing
204     // the toolbar to overflow.
205     if ([control isKindOfClass:[NSPopUpButton class]])
206       [control setFrame:optionsFrame];
207   }
210 - (void)layout {
214 - (NSArray*)visibleControls {
215   return [NSArray array];
218 - (void)rebuildOptionsMenu:(BOOL)hideTitle {
219   if (![self shouldShowOptionsPopUp])
220      return;
222   // The options model doesn't know how to handle state transitions, so rebuild
223   // it each time through here.
224   optionsMenuModel_.reset(new OptionsMenuModel([self delegate]));
226   [optionsPopUp_ removeAllItems];
227   // Set title.
228   NSString* optionsLabel = hideTitle ? @"" :
229       l10n_util::GetNSString(IDS_TRANSLATE_INFOBAR_OPTIONS);
230   [optionsPopUp_ addItemWithTitle:optionsLabel];
232    // Populate options menu.
233   NSMenu* optionsMenu = [optionsPopUp_ menu];
234   [optionsMenu setAutoenablesItems:NO];
235   for (int i = 0; i < optionsMenuModel_->GetItemCount(); ++i) {
236     AddMenuItem(optionsMenu,
237                 self,
238                 @selector(optionsMenuChanged:),
239                 base::SysUTF16ToNSString(optionsMenuModel_->GetLabelAt(i)),
240                 optionsMenuModel_->GetCommandIdAt(i),
241                 optionsMenuModel_->IsEnabledAt(i),
242                 optionsMenuModel_->IsItemCheckedAt(i));
243   }
246 - (BOOL)shouldShowOptionsPopUp {
247   return YES;
250 - (void)populateLanguageMenus {
251   NSMenu* originalLanguageMenu = [fromLanguagePopUp_ menu];
252   [originalLanguageMenu setAutoenablesItems:NO];
253   NSMenu* targetLanguageMenu = [toLanguagePopUp_ menu];
254   [targetLanguageMenu setAutoenablesItems:NO];
255   for (size_t i = 0; i < [self delegate]->num_languages(); ++i) {
256     NSString* title =
257         base::SysUTF16ToNSString([self delegate]->language_name_at(i));
258     AddMenuItem(originalLanguageMenu,
259                 self,
260                 @selector(languageMenuChanged:),
261                 title,
262                 IDC_TRANSLATE_ORIGINAL_LANGUAGE_BASE + i,
263                 i != [self delegate]->target_language_index(),
264                 i == [self delegate]->original_language_index());
265     AddMenuItem(targetLanguageMenu,
266                 self,
267                 @selector(languageMenuChanged:),
268                 title,
269                 IDC_TRANSLATE_TARGET_LANGUAGE_BASE + i,
270                 i != [self delegate]->original_language_index(),
271                 i == [self delegate]->target_language_index());
272   }
273   if ([self delegate]->original_language_index() !=
274       TranslateInfoBarDelegate::kNoIndex) {
275     [fromLanguagePopUp_
276         selectItemAtIndex:([self delegate]->original_language_index())];
277   }
278   [toLanguagePopUp_
279       selectItemAtIndex:([self delegate]->target_language_index())];
282 - (void)addAdditionalControls {
283   using l10n_util::GetNSString;
284   using l10n_util::GetNSStringWithFixup;
286   // Get layout information from the NIB.
287   NSRect okButtonFrame = [okButton_ frame];
288   NSRect cancelButtonFrame = [cancelButton_ frame];
290   DCHECK(NSMaxX(cancelButtonFrame) < NSMinX(okButtonFrame))
291       << "Ok button expected to be on the right of the Cancel button in nib";
293   spaceBetweenControls_ = NSMinX(okButtonFrame) - NSMaxX(cancelButtonFrame);
295   // Instantiate additional controls.
296   [self constructViews];
298   // Set ourselves as the delegate for the options menu so we can populate it
299   // dynamically.
300   [[optionsPopUp_ menu] setDelegate:self];
302   // Replace label_ with label1_ so we get a consistent look between all the
303   // labels we display in the translate view.
304   [[label_ superview] replaceSubview:label_ with:label1_.get()];
305   label_.reset(); // Now released.
307   // Populate contextual menus.
308   [self rebuildOptionsMenu:NO];
309   [self populateLanguageMenus];
311   // Set OK & Cancel text.
312   [okButton_ setTitle:GetNSStringWithFixup(IDS_TRANSLATE_INFOBAR_ACCEPT)];
313   [cancelButton_ setTitle:GetNSStringWithFixup(IDS_TRANSLATE_INFOBAR_DENY)];
315   // Set up "Show original" and "Try again" buttons.
316   [showOriginalButton_ setFrame:okButtonFrame];
318   // Set each of the buttons and popups to the NSTexturedRoundedBezelStyle
319   // (metal-looking) style.
320   NSArray* allControls = [self allControls];
321   for (NSControl* control in allControls) {
322     if (![control isKindOfClass:[NSButton class]])
323       continue;
324     NSButton* button = (NSButton*)control;
325     [button setBezelStyle:NSTexturedRoundedBezelStyle];
326     if ([button isKindOfClass:[NSPopUpButton class]]) {
327       [[button cell] setArrowPosition:NSPopUpArrowAtBottom];
328     }
329   }
330   // The options button is handled differently than the rest as it floats
331   // to the right.
332   [optionsPopUp_ setBezelStyle:NSTexturedRoundedBezelStyle];
333   [[optionsPopUp_ cell] setArrowPosition:NSPopUpArrowAtBottom];
335   [showOriginalButton_ setTarget:self];
336   [showOriginalButton_ setAction:@selector(showOriginal:)];
337   [translateMessageButton_ setTarget:self];
338   [translateMessageButton_ setAction:@selector(messageButtonPressed:)];
340   [showOriginalButton_
341       setTitle:GetNSStringWithFixup(IDS_TRANSLATE_INFOBAR_REVERT)];
343   // Add and configure controls that are visible in all modes.
344   [optionsPopUp_ setAutoresizingMask:NSViewMinXMargin];
345   // Add "options" popup z-ordered below all other controls so when we
346   // resize the toolbar it doesn't hide them.
347   [infoBarView_ addSubview:optionsPopUp_
348                 positioned:NSWindowBelow
349                 relativeTo:nil];
350   [GTMUILocalizerAndLayoutTweaker sizeToFitView:optionsPopUp_];
351   MoveControl(closeButton_, optionsPopUp_, spaceBetweenControls_, false);
352   VerticallyCenterView(optionsPopUp_);
354   [infoBarView_ setPostsFrameChangedNotifications:YES];
355   [[NSNotificationCenter defaultCenter]
356       addObserver:self
357          selector:@selector(didChangeFrame:)
358              name:NSViewFrameDidChangeNotification
359            object:infoBarView_];
360   // Show and place GUI elements.
361   [self updateState];
364 - (void)infobarWillHide {
365   [[fromLanguagePopUp_ menu] cancelTracking];
366   [[toLanguagePopUp_ menu] cancelTracking];
367   [[optionsPopUp_ menu] cancelTracking];
368   [super infobarWillHide];
371 - (void)infobarWillClose {
372   [self disablePopUpMenu:[fromLanguagePopUp_ menu]];
373   [self disablePopUpMenu:[toLanguagePopUp_ menu]];
374   [self disablePopUpMenu:[optionsPopUp_ menu]];
375   [[NSNotificationCenter defaultCenter] removeObserver:self];
376   [super infobarWillClose];
379 - (void)adjustOptionsButtonSizeAndVisibilityForView:(NSView*)lastView {
380   [optionsPopUp_ setHidden:NO];
381   [self rebuildOptionsMenu:NO];
382   [[optionsPopUp_ cell] setArrowPosition:NSPopUpArrowAtBottom];
383   [optionsPopUp_ sizeToFit];
385   MoveControl(closeButton_, optionsPopUp_, spaceBetweenControls_, false);
386   if (!VerifyControlOrderAndSpacing(lastView, optionsPopUp_)) {
387     [self rebuildOptionsMenu:YES];
388     NSRect oldFrame = [optionsPopUp_ frame];
389     oldFrame.size.width = NSHeight(oldFrame);
390     [optionsPopUp_ setFrame:oldFrame];
391     [[optionsPopUp_ cell] setArrowPosition:NSPopUpArrowAtCenter];
392     MoveControl(closeButton_, optionsPopUp_, spaceBetweenControls_, false);
393     if (!VerifyControlOrderAndSpacing(lastView, optionsPopUp_)) {
394       [optionsPopUp_ setHidden:YES];
395     }
396   }
399 // Called when "Translate" button is clicked.
400 - (void)ok:(id)sender {
401   if (![self isOwned])
402     return;
403   TranslateInfoBarDelegate* delegate = [self delegate];
404   TranslateInfoBarDelegate::Type state = delegate->infobar_type();
405   DCHECK(state == TranslateInfoBarDelegate::BEFORE_TRANSLATE ||
406          state == TranslateInfoBarDelegate::TRANSLATION_ERROR);
407   delegate->Translate();
410 // Called when someone clicks on the "Nope" button.
411 - (void)cancel:(id)sender {
412   if (![self isOwned])
413     return;
414   TranslateInfoBarDelegate* delegate = [self delegate];
415   DCHECK_EQ(TranslateInfoBarDelegate::BEFORE_TRANSLATE,
416             delegate->infobar_type());
417   delegate->TranslationDeclined();
418   [super removeSelf];
421 - (void)messageButtonPressed:(id)sender {
422   if (![self isOwned])
423     return;
424   [self delegate]->MessageInfoBarButtonPressed();
427 - (IBAction)showOriginal:(id)sender {
428   if (![self isOwned])
429     return;
430   [self delegate]->RevertTranslation();
433 // Called when any of the language drop down menus are changed.
434 - (void)languageMenuChanged:(id)item {
435   if (![self isOwned])
436     return;
437   if ([item respondsToSelector:@selector(tag)]) {
438     int cmd = [item tag];
439     if (cmd >= IDC_TRANSLATE_TARGET_LANGUAGE_BASE) {
440       cmd -= IDC_TRANSLATE_TARGET_LANGUAGE_BASE;
441       [self targetLanguageModified:cmd];
442       return;
443     } else if (cmd >= IDC_TRANSLATE_ORIGINAL_LANGUAGE_BASE) {
444       cmd -= IDC_TRANSLATE_ORIGINAL_LANGUAGE_BASE;
445       [self sourceLanguageModified:cmd];
446       return;
447     }
448   }
449   NOTREACHED() << "Language menu was changed with a bad language ID";
452 // Called when the options menu is changed.
453 - (void)optionsMenuChanged:(id)item {
454   if (![self isOwned])
455     return;
456   if ([item respondsToSelector:@selector(tag)]) {
457     int cmd = [item tag];
458     // Danger Will Robinson! : This call can release the infobar (e.g. invoking
459     // "About Translate" can open a new tab).
460     // Do not access member variables after this line!
461     optionsMenuModel_->ExecuteCommand(cmd, 0);
462   } else {
463     NOTREACHED();
464   }
467 - (void)dealloc {
468   [showOriginalButton_ setTarget:nil];
469   [translateMessageButton_ setTarget:nil];
470   [super dealloc];
473 #pragma mark NSMenuDelegate
475 // Invoked by virtue of us being set as the delegate for the options menu.
476 - (void)menuNeedsUpdate:(NSMenu *)menu {
477   [self adjustOptionsButtonSizeAndVisibilityForView:
478       [[self visibleControls] lastObject]];
481 @end
483 @implementation TranslateInfoBarControllerBase (TestingAPI)
485 - (NSArray*)allControls {
486   return [NSArray arrayWithObjects:label1_.get(),fromLanguagePopUp_.get(),
487       label2_.get(), toLanguagePopUp_.get(), label3_.get(), okButton_,
488       cancelButton_, showOriginalButton_.get(), translateMessageButton_.get(),
489       nil];
492 - (NSMenu*)optionsMenu {
493   return [optionsPopUp_ menu];
496 - (NSButton*)translateMessageButton {
497   return translateMessageButton_.get();
500 - (bool)verifyLayout {
501   // All the controls available to translate infobars, except the options popup.
502   // The options popup is shown/hidden instead of actually removed.  This gets
503   // checked in the subclasses.
504   NSArray* allControls = [self allControls];
505   NSArray* visibleControls = [self visibleControls];
507   // Step 1: Make sure control visibility is what we expect.
508   for (NSUInteger i = 0; i < [allControls count]; ++i) {
509     id control = [allControls objectAtIndex:i];
510     bool hasSuperView = [control superview];
511     bool expectedVisibility = [visibleControls containsObject:control];
513     if (expectedVisibility != hasSuperView) {
514       NSString *title = @"";
515       if ([control isKindOfClass:[NSPopUpButton class]]) {
516         title = [[[control menu] itemAtIndex:0] title];
517       }
519       LOG(ERROR) <<
520           "State: " << [self description] <<
521           " Control @" << i << (hasSuperView ? " has" : " doesn't have") <<
522           " a superview" << [[control description] UTF8String] <<
523           " Title=" << [title UTF8String];
524       return false;
525     }
526   }
528   // Step 2: Check that controls are ordered correctly with no overlap.
529   id previousControl = nil;
530   for (NSUInteger i = 0; i < [visibleControls count]; ++i) {
531     id control = [visibleControls objectAtIndex:i];
532     // The options pop up doesn't lay out like the rest of the controls as
533     // it floats to the right.  It has some known issues shown in
534     // http://crbug.com/47941.
535     if (control == optionsPopUp_.get())
536       continue;
537     if (previousControl &&
538         !VerifyControlOrderAndSpacing(previousControl, control)) {
539       NSString *title = @"";
540       if ([control isKindOfClass:[NSPopUpButton class]]) {
541         title = [[[control menu] itemAtIndex:0] title];
542       }
543       LOG(ERROR) <<
544           "State: " << [self description] <<
545           " Control @" << i << " not ordered correctly: " <<
546           [[control description] UTF8String] <<[title UTF8String];
547       return false;
548     }
549     previousControl = control;
550   }
552   return true;
555 @end // TranslateInfoBarControllerBase (TestingAPI)