Upstream version 5.34.104.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/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"
23
24 using InfoBarUtilities::MoveControl;
25 using InfoBarUtilities::VerticallyCenterView;
26 using InfoBarUtilities::VerifyControlOrderAndSpacing;
27 using InfoBarUtilities::CreateLabel;
28 using InfoBarUtilities::AddMenuItem;
29
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()->translate_step()) {
37     case TranslateTabHelper::BEFORE_TRANSLATE:
38       infobar_controller.reset([[BeforeTranslateInfobarController alloc]
39           initWithInfoBar:infobar.get()]);
40       break;
41     case TranslateTabHelper::AFTER_TRANSLATE:
42       infobar_controller.reset([[AfterTranslateInfobarController alloc]
43           initWithInfoBar:infobar.get()]);
44       break;
45     case TranslateTabHelper::TRANSLATING:
46     case TranslateTabHelper::TRANSLATE_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>();
55 }
56
57 @implementation TranslateInfoBarControllerBase (FrameChangeObserver)
58
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]];
64 }
65
66 @end
67
68
69 @interface TranslateInfoBarControllerBase (Private)
70
71 // Removes all controls so that layout can add in only the controls
72 // required.
73 - (void)clearAllControls;
74
75 // Create all the various controls we need for the toolbar.
76 - (void)constructViews;
77
78 // Reloads text for all labels for the current state.
79 - (void)loadLabelText:(TranslateErrors::Type)error;
80
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;
86
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;
92
93 // Completely rebuild "from" and "to" language menus from the data model.
94 - (void)populateLanguageMenus;
95
96 @end
97
98 #pragma mark TranslateInfoBarController class
99
100 @implementation TranslateInfoBarControllerBase
101
102 - (TranslateInfoBarDelegate*)delegate {
103   return reinterpret_cast<TranslateInfoBarDelegate*>([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(TranslateInfoBarDelegate::kNoIndex, newLanguageIdxSizeT);
128   if (newLanguageIdxSizeT == [self delegate]->original_language_index())
129     return;
130   [self delegate]->UpdateOriginalLanguageIndex(newLanguageIdxSizeT);
131   if ([self delegate]->translate_step() == TranslateTabHelper::AFTER_TRANSLATE)
132     [self delegate]->Translate();
133   int commandId = IDC_TRANSLATE_ORIGINAL_LANGUAGE_BASE + newLanguageIdx;
134   int newMenuIdx = [fromLanguagePopUp_ indexOfItemWithTag:commandId];
135   [fromLanguagePopUp_ selectItemAtIndex:newMenuIdx];
136 }
137
138 - (void)targetLanguageModified:(NSInteger)newLanguageIdx {
139   size_t newLanguageIdxSizeT = static_cast<size_t>(newLanguageIdx);
140   DCHECK_NE(TranslateInfoBarDelegate::kNoIndex, newLanguageIdxSizeT);
141   if (newLanguageIdxSizeT == [self delegate]->target_language_index())
142     return;
143   [self delegate]->UpdateTargetLanguageIndex(newLanguageIdxSizeT);
144   if ([self delegate]->translate_step() == TranslateTabHelper::AFTER_TRANSLATE)
145     [self delegate]->Translate();
146   int commandId = IDC_TRANSLATE_TARGET_LANGUAGE_BASE + newLanguageIdx;
147   int newMenuIdx = [toLanguagePopUp_ indexOfItemWithTag:commandId];
148   [toLanguagePopUp_ selectItemAtIndex:newMenuIdx];
149 }
150
151 - (void)loadLabelText {
152   // Do nothing by default, should be implemented by subclasses.
153 }
154
155 - (void)updateState {
156   [self loadLabelText];
157   [self clearAllControls];
158   [self showVisibleControls:[self visibleControls]];
159   [optionsPopUp_ setHidden:![self shouldShowOptionsPopUp]];
160   [self layout];
161   [self adjustOptionsButtonSizeAndVisibilityForView:
162       [[self visibleControls] lastObject]];
163 }
164
165 - (void)removeOkCancelButtons {
166   // Removing okButton_ & cancelButton_ from the view may cause them
167   // to be released and since we can still access them from other areas
168   // in the code later, we need them to be nil when this happens.
169   [okButton_ removeFromSuperview];
170   okButton_ = nil;
171   [cancelButton_ removeFromSuperview];
172   cancelButton_ = nil;
173 }
174
175 - (void)clearAllControls {
176   // Step 1: remove all controls from the infobar so we have a clean slate.
177   NSArray *allControls = [self allControls];
178
179   for (NSControl* control in allControls) {
180     if ([control superview])
181       [control removeFromSuperview];
182   }
183 }
184
185 - (void)showVisibleControls:(NSArray*)visibleControls {
186   NSRect optionsFrame = [optionsPopUp_ frame];
187   for (NSControl* control in visibleControls) {
188     [GTMUILocalizerAndLayoutTweaker sizeToFitView:control];
189     [control setAutoresizingMask:NSViewMaxXMargin];
190
191     // Need to check if a view is already attached since |label1_| is always
192     // parented and we don't want to add it again.
193     if (![control superview])
194       [infoBarView_ addSubview:control];
195
196     if ([control isKindOfClass:[NSButton class]])
197       VerticallyCenterView(control);
198
199     // Make "from" and "to" language popup menus the same size as the options
200     // menu.
201     // We don't autosize since some languages names are really long causing
202     // the toolbar to overflow.
203     if ([control isKindOfClass:[NSPopUpButton class]])
204       [control setFrame:optionsFrame];
205   }
206 }
207
208 - (void)layout {
209
210 }
211
212 - (NSArray*)visibleControls {
213   return [NSArray array];
214 }
215
216 - (void)rebuildOptionsMenu:(BOOL)hideTitle {
217   if (![self shouldShowOptionsPopUp])
218      return;
219
220   // The options model doesn't know how to handle state transitions, so rebuild
221   // it each time through here.
222   optionsMenuModel_.reset(new OptionsMenuModel([self delegate]));
223
224   [optionsPopUp_ removeAllItems];
225   // Set title.
226   NSString* optionsLabel = hideTitle ? @"" :
227       l10n_util::GetNSString(IDS_TRANSLATE_INFOBAR_OPTIONS);
228   [optionsPopUp_ addItemWithTitle:optionsLabel];
229
230    // Populate options menu.
231   NSMenu* optionsMenu = [optionsPopUp_ menu];
232   [optionsMenu setAutoenablesItems:NO];
233   for (int i = 0; i < optionsMenuModel_->GetItemCount(); ++i) {
234     AddMenuItem(optionsMenu,
235                 self,
236                 @selector(optionsMenuChanged:),
237                 base::SysUTF16ToNSString(optionsMenuModel_->GetLabelAt(i)),
238                 optionsMenuModel_->GetCommandIdAt(i),
239                 optionsMenuModel_->IsEnabledAt(i),
240                 optionsMenuModel_->IsItemCheckedAt(i));
241   }
242 }
243
244 - (BOOL)shouldShowOptionsPopUp {
245   return YES;
246 }
247
248 - (void)populateLanguageMenus {
249   NSMenu* originalLanguageMenu = [fromLanguagePopUp_ menu];
250   [originalLanguageMenu setAutoenablesItems:NO];
251   NSMenu* targetLanguageMenu = [toLanguagePopUp_ menu];
252   [targetLanguageMenu setAutoenablesItems:NO];
253   for (size_t i = 0; i < [self delegate]->num_languages(); ++i) {
254     NSString* title =
255         base::SysUTF16ToNSString([self delegate]->language_name_at(i));
256     AddMenuItem(originalLanguageMenu,
257                 self,
258                 @selector(languageMenuChanged:),
259                 title,
260                 IDC_TRANSLATE_ORIGINAL_LANGUAGE_BASE + i,
261                 i != [self delegate]->target_language_index(),
262                 i == [self delegate]->original_language_index());
263     AddMenuItem(targetLanguageMenu,
264                 self,
265                 @selector(languageMenuChanged:),
266                 title,
267                 IDC_TRANSLATE_TARGET_LANGUAGE_BASE + i,
268                 i != [self delegate]->original_language_index(),
269                 i == [self delegate]->target_language_index());
270   }
271   if ([self delegate]->original_language_index() !=
272       TranslateInfoBarDelegate::kNoIndex) {
273     [fromLanguagePopUp_
274         selectItemAtIndex:([self delegate]->original_language_index())];
275   }
276   [toLanguagePopUp_
277       selectItemAtIndex:([self delegate]->target_language_index())];
278 }
279
280 - (void)addAdditionalControls {
281   using l10n_util::GetNSString;
282   using l10n_util::GetNSStringWithFixup;
283
284   // Get layout information from the NIB.
285   NSRect okButtonFrame = [okButton_ frame];
286   NSRect cancelButtonFrame = [cancelButton_ frame];
287
288   DCHECK(NSMaxX(cancelButtonFrame) < NSMinX(okButtonFrame))
289       << "Ok button expected to be on the right of the Cancel button in nib";
290
291   spaceBetweenControls_ = NSMinX(okButtonFrame) - NSMaxX(cancelButtonFrame);
292
293   // Instantiate additional controls.
294   [self constructViews];
295
296   // Set ourselves as the delegate for the options menu so we can populate it
297   // dynamically.
298   [[optionsPopUp_ menu] setDelegate:self];
299
300   // Replace label_ with label1_ so we get a consistent look between all the
301   // labels we display in the translate view.
302   [[label_ superview] replaceSubview:label_ with:label1_.get()];
303   label_.reset(); // Now released.
304
305   // Populate contextual menus.
306   [self rebuildOptionsMenu:NO];
307   [self populateLanguageMenus];
308
309   // Set OK & Cancel text.
310   [okButton_ setTitle:GetNSStringWithFixup(IDS_TRANSLATE_INFOBAR_ACCEPT)];
311   [cancelButton_ setTitle:GetNSStringWithFixup(IDS_TRANSLATE_INFOBAR_DENY)];
312
313   // Set up "Show original" and "Try again" buttons.
314   [showOriginalButton_ setFrame:okButtonFrame];
315
316   // Set each of the buttons and popups to the NSTexturedRoundedBezelStyle
317   // (metal-looking) style.
318   NSArray* allControls = [self allControls];
319   for (NSControl* control in allControls) {
320     if (![control isKindOfClass:[NSButton class]])
321       continue;
322     NSButton* button = (NSButton*)control;
323     [button setBezelStyle:NSTexturedRoundedBezelStyle];
324     if ([button isKindOfClass:[NSPopUpButton class]]) {
325       [[button cell] setArrowPosition:NSPopUpArrowAtBottom];
326     }
327   }
328   // The options button is handled differently than the rest as it floats
329   // to the right.
330   [optionsPopUp_ setBezelStyle:NSTexturedRoundedBezelStyle];
331   [[optionsPopUp_ cell] setArrowPosition:NSPopUpArrowAtBottom];
332
333   [showOriginalButton_ setTarget:self];
334   [showOriginalButton_ setAction:@selector(showOriginal:)];
335   [translateMessageButton_ setTarget:self];
336   [translateMessageButton_ setAction:@selector(messageButtonPressed:)];
337
338   [showOriginalButton_
339       setTitle:GetNSStringWithFixup(IDS_TRANSLATE_INFOBAR_REVERT)];
340
341   // Add and configure controls that are visible in all modes.
342   [optionsPopUp_ setAutoresizingMask:NSViewMinXMargin];
343   // Add "options" popup z-ordered below all other controls so when we
344   // resize the toolbar it doesn't hide them.
345   [infoBarView_ addSubview:optionsPopUp_
346                 positioned:NSWindowBelow
347                 relativeTo:nil];
348   [GTMUILocalizerAndLayoutTweaker sizeToFitView:optionsPopUp_];
349   MoveControl(closeButton_, optionsPopUp_, spaceBetweenControls_, false);
350   VerticallyCenterView(optionsPopUp_);
351
352   [infoBarView_ setPostsFrameChangedNotifications:YES];
353   [[NSNotificationCenter defaultCenter]
354       addObserver:self
355          selector:@selector(didChangeFrame:)
356              name:NSViewFrameDidChangeNotification
357            object:infoBarView_];
358   // Show and place GUI elements.
359   [self updateState];
360 }
361
362 - (void)infobarWillHide {
363   [[fromLanguagePopUp_ menu] cancelTracking];
364   [[toLanguagePopUp_ menu] cancelTracking];
365   [[optionsPopUp_ menu] cancelTracking];
366   [super infobarWillHide];
367 }
368
369 - (void)infobarWillClose {
370   [self disablePopUpMenu:[fromLanguagePopUp_ menu]];
371   [self disablePopUpMenu:[toLanguagePopUp_ menu]];
372   [self disablePopUpMenu:[optionsPopUp_ menu]];
373   [[NSNotificationCenter defaultCenter] removeObserver:self];
374   [super infobarWillClose];
375 }
376
377 - (void)adjustOptionsButtonSizeAndVisibilityForView:(NSView*)lastView {
378   [optionsPopUp_ setHidden:NO];
379   [self rebuildOptionsMenu:NO];
380   [[optionsPopUp_ cell] setArrowPosition:NSPopUpArrowAtBottom];
381   [optionsPopUp_ sizeToFit];
382
383   MoveControl(closeButton_, optionsPopUp_, spaceBetweenControls_, false);
384   if (!VerifyControlOrderAndSpacing(lastView, optionsPopUp_)) {
385     [self rebuildOptionsMenu:YES];
386     NSRect oldFrame = [optionsPopUp_ frame];
387     oldFrame.size.width = NSHeight(oldFrame);
388     [optionsPopUp_ setFrame:oldFrame];
389     [[optionsPopUp_ cell] setArrowPosition:NSPopUpArrowAtCenter];
390     MoveControl(closeButton_, optionsPopUp_, spaceBetweenControls_, false);
391     if (!VerifyControlOrderAndSpacing(lastView, optionsPopUp_)) {
392       [optionsPopUp_ setHidden:YES];
393     }
394   }
395 }
396
397 // Called when "Translate" button is clicked.
398 - (void)ok:(id)sender {
399   if (![self isOwned])
400     return;
401   TranslateInfoBarDelegate* delegate = [self delegate];
402   TranslateTabHelper::TranslateStep state = delegate->translate_step();
403   DCHECK(state == TranslateTabHelper::BEFORE_TRANSLATE ||
404          state == TranslateTabHelper::TRANSLATE_ERROR);
405   delegate->Translate();
406 }
407
408 // Called when someone clicks on the "Nope" button.
409 - (void)cancel:(id)sender {
410   if (![self isOwned])
411     return;
412   TranslateInfoBarDelegate* delegate = [self delegate];
413   DCHECK_EQ(TranslateTabHelper::BEFORE_TRANSLATE, delegate->translate_step());
414   delegate->TranslationDeclined();
415   [super removeSelf];
416 }
417
418 - (void)messageButtonPressed:(id)sender {
419   if (![self isOwned])
420     return;
421   [self delegate]->MessageInfoBarButtonPressed();
422 }
423
424 - (IBAction)showOriginal:(id)sender {
425   if (![self isOwned])
426     return;
427   [self delegate]->RevertTranslation();
428 }
429
430 // Called when any of the language drop down menus are changed.
431 - (void)languageMenuChanged:(id)item {
432   if (![self isOwned])
433     return;
434   if ([item respondsToSelector:@selector(tag)]) {
435     int cmd = [item tag];
436     if (cmd >= IDC_TRANSLATE_TARGET_LANGUAGE_BASE) {
437       cmd -= IDC_TRANSLATE_TARGET_LANGUAGE_BASE;
438       [self targetLanguageModified:cmd];
439       return;
440     } else if (cmd >= IDC_TRANSLATE_ORIGINAL_LANGUAGE_BASE) {
441       cmd -= IDC_TRANSLATE_ORIGINAL_LANGUAGE_BASE;
442       [self sourceLanguageModified:cmd];
443       return;
444     }
445   }
446   NOTREACHED() << "Language menu was changed with a bad language ID";
447 }
448
449 // Called when the options menu is changed.
450 - (void)optionsMenuChanged:(id)item {
451   if (![self isOwned])
452     return;
453   if ([item respondsToSelector:@selector(tag)]) {
454     int cmd = [item tag];
455     // Danger Will Robinson! : This call can release the infobar (e.g. invoking
456     // "About Translate" can open a new tab).
457     // Do not access member variables after this line!
458     optionsMenuModel_->ExecuteCommand(cmd, 0);
459   } else {
460     NOTREACHED();
461   }
462 }
463
464 - (void)dealloc {
465   [showOriginalButton_ setTarget:nil];
466   [translateMessageButton_ setTarget:nil];
467   [super dealloc];
468 }
469
470 #pragma mark NSMenuDelegate
471
472 // Invoked by virtue of us being set as the delegate for the options menu.
473 - (void)menuNeedsUpdate:(NSMenu *)menu {
474   [self adjustOptionsButtonSizeAndVisibilityForView:
475       [[self visibleControls] lastObject]];
476 }
477
478 @end
479
480 @implementation TranslateInfoBarControllerBase (TestingAPI)
481
482 - (NSArray*)allControls {
483   return [NSArray arrayWithObjects:label1_.get(),fromLanguagePopUp_.get(),
484       label2_.get(), toLanguagePopUp_.get(), label3_.get(), okButton_,
485       cancelButton_, showOriginalButton_.get(), translateMessageButton_.get(),
486       nil];
487 }
488
489 - (NSMenu*)optionsMenu {
490   return [optionsPopUp_ menu];
491 }
492
493 - (NSButton*)translateMessageButton {
494   return translateMessageButton_.get();
495 }
496
497 - (bool)verifyLayout {
498   // All the controls available to translate infobars, except the options popup.
499   // The options popup is shown/hidden instead of actually removed.  This gets
500   // checked in the subclasses.
501   NSArray* allControls = [self allControls];
502   NSArray* visibleControls = [self visibleControls];
503
504   // Step 1: Make sure control visibility is what we expect.
505   for (NSUInteger i = 0; i < [allControls count]; ++i) {
506     id control = [allControls objectAtIndex:i];
507     bool hasSuperView = [control superview];
508     bool expectedVisibility = [visibleControls containsObject:control];
509
510     if (expectedVisibility != hasSuperView) {
511       NSString *title = @"";
512       if ([control isKindOfClass:[NSPopUpButton class]]) {
513         title = [[[control menu] itemAtIndex:0] title];
514       }
515
516       LOG(ERROR) <<
517           "State: " << [self description] <<
518           " Control @" << i << (hasSuperView ? " has" : " doesn't have") <<
519           " a superview" << [[control description] UTF8String] <<
520           " Title=" << [title UTF8String];
521       return false;
522     }
523   }
524
525   // Step 2: Check that controls are ordered correctly with no overlap.
526   id previousControl = nil;
527   for (NSUInteger i = 0; i < [visibleControls count]; ++i) {
528     id control = [visibleControls objectAtIndex:i];
529     // The options pop up doesn't lay out like the rest of the controls as
530     // it floats to the right.  It has some known issues shown in
531     // http://crbug.com/47941.
532     if (control == optionsPopUp_.get())
533       continue;
534     if (previousControl &&
535         !VerifyControlOrderAndSpacing(previousControl, control)) {
536       NSString *title = @"";
537       if ([control isKindOfClass:[NSPopUpButton class]]) {
538         title = [[[control menu] itemAtIndex:0] title];
539       }
540       LOG(ERROR) <<
541           "State: " << [self description] <<
542           " Control @" << i << " not ordered correctly: " <<
543           [[control description] UTF8String] <<[title UTF8String];
544       return false;
545     }
546     previousControl = control;
547   }
548
549   return true;
550 }
551
552 @end // TranslateInfoBarControllerBase (TestingAPI)