Upstream version 11.40.277.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / infobars / translate_infobar_base.mm
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "chrome/browser/ui/cocoa/infobars/translate_infobar_base.h"
6
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/chrome_translate_client.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 "components/translate/core/browser/translate_infobar_delegate.h"
21 #include "grit/components_strings.h"
22 #include "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
23 #include "ui/base/l10n/l10n_util.h"
24
25 using InfoBarUtilities::MoveControl;
26 using InfoBarUtilities::VerticallyCenterView;
27 using InfoBarUtilities::VerifyControlOrderAndSpacing;
28 using InfoBarUtilities::CreateLabel;
29 using InfoBarUtilities::AddMenuItem;
30
31 scoped_ptr<infobars::InfoBar> ChromeTranslateClient::CreateInfoBar(
32     scoped_ptr<translate::TranslateInfoBarDelegate> delegate) const {
33   scoped_ptr<InfoBarCocoa> infobar(new InfoBarCocoa(delegate.Pass()));
34   base::scoped_nsobject<TranslateInfoBarControllerBase> infobar_controller;
35   switch (infobar->delegate()->AsTranslateInfoBarDelegate()->translate_step()) {
36     case translate::TRANSLATE_STEP_BEFORE_TRANSLATE:
37       infobar_controller.reset([[BeforeTranslateInfobarController alloc]
38           initWithInfoBar:infobar.get()]);
39       break;
40     case translate::TRANSLATE_STEP_AFTER_TRANSLATE:
41       infobar_controller.reset([[AfterTranslateInfobarController alloc]
42           initWithInfoBar:infobar.get()]);
43       break;
44     case translate::TRANSLATE_STEP_TRANSLATING:
45     case translate::TRANSLATE_STEP_TRANSLATE_ERROR:
46       infobar_controller.reset([[TranslateMessageInfobarController alloc]
47           initWithInfoBar:infobar.get()]);
48       break;
49     default:
50       NOTREACHED();
51   }
52   infobar->set_controller(infobar_controller);
53   return infobar.Pass();
54 }
55
56 @implementation TranslateInfoBarControllerBase (FrameChangeObserver)
57
58 // Triggered when the frame changes.  This will figure out what size and
59 // visibility the options popup should be.
60 - (void)didChangeFrame:(NSNotification*)notification {
61   [self adjustOptionsButtonSizeAndVisibilityForView:
62       [[self visibleControls] lastObject]];
63 }
64
65 @end
66
67
68 @interface TranslateInfoBarControllerBase (Private)
69
70 // Removes all controls so that layout can add in only the controls
71 // required.
72 - (void)clearAllControls;
73
74 // Create all the various controls we need for the toolbar.
75 - (void)constructViews;
76
77 // Reloads text for all labels for the current state.
78 - (void)loadLabelText:(translate::TranslateErrors::Type)error;
79
80 // Main function to update the toolbar graphic state and data model after
81 // the state has changed.
82 // Controls are moved around as needed and visibility changed to match the
83 // current state.
84 - (void)updateState;
85
86 // Called when the source or target language selection changes in a menu.
87 // |newLanguageIdx| is the index of the newly selected item in the appropriate
88 // menu.
89 - (void)sourceLanguageModified:(NSInteger)newLanguageIdx;
90 - (void)targetLanguageModified:(NSInteger)newLanguageIdx;
91
92 // Completely rebuild "from" and "to" language menus from the data model.
93 - (void)populateLanguageMenus;
94
95 @end
96
97 #pragma mark TranslateInfoBarController class
98
99 @implementation TranslateInfoBarControllerBase
100
101 - (translate::TranslateInfoBarDelegate*)delegate {
102   return reinterpret_cast<translate::TranslateInfoBarDelegate*>(
103       [super delegate]);
104 }
105
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));
114
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]);
123 }
124
125 - (void)sourceLanguageModified:(NSInteger)newLanguageIdx {
126   size_t newLanguageIdxSizeT = static_cast<size_t>(newLanguageIdx);
127   DCHECK_NE(translate::TranslateInfoBarDelegate::kNoIndex, newLanguageIdxSizeT);
128   if (newLanguageIdxSizeT == [self delegate]->original_language_index())
129     return;
130   [self delegate]->UpdateOriginalLanguageIndex(newLanguageIdxSizeT);
131   if ([self delegate]->translate_step() ==
132       translate::TRANSLATE_STEP_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];
137 }
138
139 - (void)targetLanguageModified:(NSInteger)newLanguageIdx {
140   size_t newLanguageIdxSizeT = static_cast<size_t>(newLanguageIdx);
141   DCHECK_NE(translate::TranslateInfoBarDelegate::kNoIndex, newLanguageIdxSizeT);
142   if (newLanguageIdxSizeT == [self delegate]->target_language_index())
143     return;
144   [self delegate]->UpdateTargetLanguageIndex(newLanguageIdxSizeT);
145   if ([self delegate]->translate_step() ==
146       translate::TRANSLATE_STEP_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];
151 }
152
153 - (void)loadLabelText {
154   // Do nothing by default, should be implemented by subclasses.
155 }
156
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]];
165 }
166
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;
175 }
176
177 - (void)clearAllControls {
178   // Step 1: remove all controls from the infobar so we have a clean slate.
179   NSArray *allControls = [self allControls];
180
181   for (NSControl* control in allControls) {
182     if ([control superview])
183       [control removeFromSuperview];
184   }
185 }
186
187 - (void)showVisibleControls:(NSArray*)visibleControls {
188   NSRect optionsFrame = [optionsPopUp_ frame];
189   for (NSControl* control in visibleControls) {
190     [GTMUILocalizerAndLayoutTweaker sizeToFitView:control];
191     [control setAutoresizingMask:NSViewMaxXMargin];
192
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];
197
198     if ([control isKindOfClass:[NSButton class]])
199       VerticallyCenterView(control);
200
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   }
208 }
209
210 - (void)layout {
211
212 }
213
214 - (NSArray*)visibleControls {
215   return [NSArray array];
216 }
217
218 - (void)rebuildOptionsMenu:(BOOL)hideTitle {
219   if (![self shouldShowOptionsPopUp])
220      return;
221
222   // The options model doesn't know how to handle state transitions, so rebuild
223   // it each time through here.
224   optionsMenuModel_.reset(new translate::OptionsMenuModel([self delegate]));
225
226   [optionsPopUp_ removeAllItems];
227   // Set title.
228   NSString* optionsLabel = hideTitle ? @"" :
229       l10n_util::GetNSString(IDS_TRANSLATE_INFOBAR_OPTIONS);
230   [optionsPopUp_ addItemWithTitle:optionsLabel];
231
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   }
244 }
245
246 - (BOOL)shouldShowOptionsPopUp {
247   return YES;
248 }
249
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       translate::TranslateInfoBarDelegate::kNoIndex) {
275     [fromLanguagePopUp_
276         selectItemAtIndex:([self delegate]->original_language_index())];
277   }
278   [toLanguagePopUp_
279       selectItemAtIndex:([self delegate]->target_language_index())];
280 }
281
282 - (void)addAdditionalControls {
283   using l10n_util::GetNSString;
284   using l10n_util::GetNSStringWithFixup;
285
286   // Get layout information from the NIB.
287   NSRect okButtonFrame = [okButton_ frame];
288   NSRect cancelButtonFrame = [cancelButton_ frame];
289
290   DCHECK(NSMaxX(cancelButtonFrame) < NSMinX(okButtonFrame))
291       << "Ok button expected to be on the right of the Cancel button in nib";
292
293   spaceBetweenControls_ = NSMinX(okButtonFrame) - NSMaxX(cancelButtonFrame);
294
295   // Instantiate additional controls.
296   [self constructViews];
297
298   // Set ourselves as the delegate for the options menu so we can populate it
299   // dynamically.
300   [[optionsPopUp_ menu] setDelegate:self];
301
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.
306
307   // Populate contextual menus.
308   [self rebuildOptionsMenu:NO];
309   [self populateLanguageMenus];
310
311   // Set OK & Cancel text.
312   [okButton_ setTitle:GetNSStringWithFixup(IDS_TRANSLATE_INFOBAR_ACCEPT)];
313   [cancelButton_ setTitle:GetNSStringWithFixup(IDS_TRANSLATE_INFOBAR_DENY)];
314
315   // Set up "Show original" and "Try again" buttons.
316   [showOriginalButton_ setFrame:okButtonFrame];
317
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];
334
335   [showOriginalButton_ setTarget:self];
336   [showOriginalButton_ setAction:@selector(showOriginal:)];
337   [translateMessageButton_ setTarget:self];
338   [translateMessageButton_ setAction:@selector(messageButtonPressed:)];
339
340   [showOriginalButton_
341       setTitle:GetNSStringWithFixup(IDS_TRANSLATE_INFOBAR_REVERT)];
342
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_);
353
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];
362 }
363
364 - (void)infobarWillHide {
365   [[fromLanguagePopUp_ menu] cancelTracking];
366   [[toLanguagePopUp_ menu] cancelTracking];
367   [[optionsPopUp_ menu] cancelTracking];
368   [super infobarWillHide];
369 }
370
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];
377 }
378
379 - (void)adjustOptionsButtonSizeAndVisibilityForView:(NSView*)lastView {
380   [optionsPopUp_ setHidden:NO];
381   [self rebuildOptionsMenu:NO];
382   [[optionsPopUp_ cell] setArrowPosition:NSPopUpArrowAtBottom];
383   [optionsPopUp_ sizeToFit];
384
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   }
397 }
398
399 // Called when "Translate" button is clicked.
400 - (void)ok:(id)sender {
401   if (![self isOwned])
402     return;
403   translate::TranslateInfoBarDelegate* delegate = [self delegate];
404   translate::TranslateStep state = delegate->translate_step();
405   DCHECK(state == translate::TRANSLATE_STEP_BEFORE_TRANSLATE ||
406          state == translate::TRANSLATE_STEP_TRANSLATE_ERROR);
407   delegate->Translate();
408 }
409
410 // Called when someone clicks on the "Nope" button.
411 - (void)cancel:(id)sender {
412   if (![self isOwned])
413     return;
414   translate::TranslateInfoBarDelegate* delegate = [self delegate];
415   DCHECK_EQ(translate::TRANSLATE_STEP_BEFORE_TRANSLATE,
416             delegate->translate_step());
417   delegate->TranslationDeclined();
418   [super removeSelf];
419 }
420
421 - (void)messageButtonPressed:(id)sender {
422   if (![self isOwned])
423     return;
424   [self delegate]->MessageInfoBarButtonPressed();
425 }
426
427 - (IBAction)showOriginal:(id)sender {
428   if (![self isOwned])
429     return;
430   [self delegate]->RevertTranslation();
431 }
432
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";
450 }
451
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   }
465 }
466
467 - (void)dealloc {
468   [showOriginalButton_ setTarget:nil];
469   [translateMessageButton_ setTarget:nil];
470   [super dealloc];
471 }
472
473 #pragma mark NSMenuDelegate
474
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]];
479 }
480
481 @end
482
483 @implementation TranslateInfoBarControllerBase (TestingAPI)
484
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];
490 }
491
492 - (NSMenu*)optionsMenu {
493   return [optionsPopUp_ menu];
494 }
495
496 - (NSButton*)translateMessageButton {
497   return translateMessageButton_.get();
498 }
499
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];
506
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];
512
513     if (expectedVisibility != hasSuperView) {
514       NSString *title = @"";
515       if ([control isKindOfClass:[NSPopUpButton class]]) {
516         title = [[[control menu] itemAtIndex:0] title];
517       }
518
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   }
527
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   }
551
552   return true;
553 }
554
555 @end // TranslateInfoBarControllerBase (TestingAPI)