OLD | NEW |
| (Empty) |
1 // Copyright (c) 2010 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/cocoa/translate_infobar.h" | |
7 | |
8 #include "app/l10n_util.h" | |
9 #include "base/logging.h" // for NOTREACHED() | |
10 #include "base/mac_util.h" | |
11 #include "base/sys_string_conversions.h" | |
12 #include "chrome/app/chrome_dll_resource.h" | |
13 #import "chrome/browser/cocoa/hover_close_button.h" | |
14 #include "chrome/browser/cocoa/infobar.h" | |
15 #import "chrome/browser/cocoa/infobar_controller.h" | |
16 #import "chrome/browser/cocoa/infobar_gradient_view.h" | |
17 #include "chrome/browser/tab_contents/tab_contents.h" | |
18 #include "chrome/browser/translate/page_translated_details.h" | |
19 #include "chrome/common/notification_service.h" | |
20 #include "grit/generated_resources.h" | |
21 #include "grit/locale_settings.h" | |
22 #include "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" | |
23 | |
24 // http://crbug.com/46663 disabled since it never worked. | |
25 #define DISABLE_VERIFY_CONTROL_ORDER 1 | |
26 | |
27 // Colors for translate infobar gradient background. | |
28 const int kGreyTopColor[] = {0xC0, 0xC0, 0xC0}; | |
29 const int kGreyBottomColor[] = {0xCC, 0xCC, 0xCC}; | |
30 | |
31 #pragma mark Anonymous helper functions. | |
32 namespace { | |
33 | |
34 // Move the |toMove| view |spacing| pixels before/after the |anchor| view. | |
35 // |after| signifies the side of |anchor| on which to place |toMove|. | |
36 void MoveControl(NSView* anchor, NSView* toMove, int spacing, bool after) { | |
37 NSRect anchorFrame = [anchor frame]; | |
38 NSRect toMoveFrame = [toMove frame]; | |
39 | |
40 // At the time of this writing, OS X doesn't natively support BiDi UIs, but | |
41 // it doesn't hurt to be forward looking. | |
42 bool toRight = after; | |
43 | |
44 if (toRight) { | |
45 toMoveFrame.origin.x = NSMaxX(anchorFrame) + spacing; | |
46 } else { | |
47 // Place toMove to theleft of anchor. | |
48 toMoveFrame.origin.x = NSMinX(anchorFrame) - | |
49 spacing - NSWidth(toMoveFrame); | |
50 } | |
51 [toMove setFrame:toMoveFrame]; | |
52 } | |
53 | |
54 // Vertically center |toMove| in its container. | |
55 void VerticallyCenterView(NSView *toMove) { | |
56 NSRect superViewFrame = [[toMove superview] frame]; | |
57 NSRect viewFrame = [toMove frame]; | |
58 | |
59 viewFrame.origin.y = | |
60 floor((NSHeight(superViewFrame) - NSHeight(viewFrame))/2.0); | |
61 [toMove setFrame:viewFrame]; | |
62 } | |
63 | |
64 // Check that the control |before| is ordered visually before the |after| | |
65 // control. | |
66 // Also, check that there is space between them. | |
67 // http://crbug.com/46663 the code below seems to be the reverse of this | |
68 // comment. | |
69 #if !defined(DISABLE_VERIFY_CONTROL_ORDER) | |
70 bool VerifyControlOrderAndSpacing(id before, id after) { | |
71 CGFloat spaceBetweenControls = 0; | |
72 if (before && after) { | |
73 // When messaging nil, the rects won't always be zeroed (only sizeof(id) is | |
74 // going to be zeroed by the Objective-C runtime, rest will be uninitialized | |
75 // memory). | |
76 NSRect beforeFrame = [before frame]; | |
77 NSRect afterFrame = [after frame]; | |
78 spaceBetweenControls = NSMaxX(beforeFrame) - NSMinX(afterFrame); | |
79 // RTL case to be used when we have an RTL version of this UI. | |
80 // spaceBetweenControls = NSMaxX(afterFrame) - NSMinX(beforeFrame); | |
81 | |
82 } | |
83 | |
84 return (spaceBetweenControls >= 0); | |
85 } | |
86 #endif // !defined(DISABLE_VERIFY_CONTROL_ORDER) | |
87 | |
88 // Creates a label control in the style we need for the translate infobar's | |
89 // labels within |bounds|. | |
90 NSTextField* CreateLabel(NSRect bounds) { | |
91 NSTextField* ret = [[NSTextField alloc] initWithFrame:bounds]; | |
92 [ret setEditable:NO]; | |
93 [ret setDrawsBackground:NO]; | |
94 [ret setBordered:NO]; | |
95 return ret; | |
96 } | |
97 | |
98 // Adds an item with the specified properties to |menu|. | |
99 void AddMenuItem(NSMenu *menu, id target, NSString* title, int tag, | |
100 bool enabled, bool checked) { | |
101 NSMenuItem* item = [[[NSMenuItem alloc] | |
102 initWithTitle:title | |
103 action:@selector(menuItemSelected:) | |
104 keyEquivalent:@""] autorelease]; | |
105 [item setTag:tag]; | |
106 [menu addItem:item]; | |
107 [item setTarget:target]; | |
108 if (checked) | |
109 [item setState:NSOnState]; | |
110 if (!enabled) | |
111 [item setEnabled:NO]; | |
112 } | |
113 | |
114 } // namespace | |
115 | |
116 #pragma mark TranslateInfoBarMenuModel class definition | |
117 // Bridge class to handle interfacing with menu controllers from popup | |
118 // menus in infobar. | |
119 class TranslateInfoBarMenuModel : public menus::SimpleMenuModel::Delegate { | |
120 public: | |
121 TranslateInfoBarMenuModel(TranslateInfoBarDelegate* delegate, | |
122 TranslateInfoBarController* controller) : | |
123 translate_delegate_(delegate), | |
124 controller_(controller) {} | |
125 | |
126 // Overridden from menus::SimpleMenuModel::Delegate: | |
127 virtual bool IsCommandIdChecked(int command_id) const; | |
128 virtual bool IsCommandIdEnabled(int command_id) const; | |
129 virtual bool GetAcceleratorForCommandId(int command_id, | |
130 menus::Accelerator* accelerator); | |
131 virtual void ExecuteCommand(int command_id); | |
132 | |
133 private: | |
134 TranslateInfoBarDelegate* translate_delegate_; // weak | |
135 TranslateInfoBarController* controller_; // weak | |
136 DISALLOW_COPY_AND_ASSIGN(TranslateInfoBarMenuModel); | |
137 }; | |
138 | |
139 #pragma mark TranslateNotificationObserverBridge class definition | |
140 // Bridge class to allow obj-c TranslateInfoBarController to observe | |
141 // notifications. | |
142 class TranslateNotificationObserverBridge : | |
143 public NotificationObserver { | |
144 public: | |
145 TranslateNotificationObserverBridge( | |
146 TranslateInfoBarDelegate* delegate, | |
147 TranslateInfoBarController* controller); | |
148 | |
149 // Overridden from NotificationObserver: | |
150 virtual void Observe(NotificationType type, | |
151 const NotificationSource& source, | |
152 const NotificationDetails& details); | |
153 | |
154 private: | |
155 TranslateInfoBarDelegate* translate_delegate_; // weak | |
156 TranslateInfoBarController* controller_; // weak | |
157 NotificationRegistrar notification_registrar_; | |
158 DISALLOW_COPY_AND_ASSIGN(TranslateNotificationObserverBridge); | |
159 }; | |
160 | |
161 @interface TranslateInfoBarController (Private) | |
162 | |
163 // Returns the main translate delegate. | |
164 - (TranslateInfoBarDelegate*)delegate; | |
165 | |
166 // Make the infobar grey. | |
167 - (void)setInfoBarGradientColor; | |
168 | |
169 // Reloads text for all labels for the current state. | |
170 - (void)loadLabelText:(TranslateErrors::Type)error; | |
171 | |
172 // Resizes controls and hides/shows them based on state transition. | |
173 // Called before layout; | |
174 - (void)resizeAndSetControlVisibility; | |
175 | |
176 // Move all the currently visible views into the correct place for the | |
177 // current mode. | |
178 - (void)layout; | |
179 | |
180 // Create all the various controls we need for the toolbar. | |
181 - (void)constructViews; | |
182 | |
183 // Called when the source or target language selection changes in a menu. | |
184 // |newLanguageIdx| is the index of the newly selected item in the appropriate | |
185 // menu. | |
186 - (void)sourceLanguageModified:(NSInteger)newLanguageIdx; | |
187 - (void)targetLanguageModified:(NSInteger)newLanguageIdx; | |
188 | |
189 // Called when the source or target language have changed to update the | |
190 // model state and refresh the GUI. | |
191 - (void)languageModified; | |
192 | |
193 // Completely rebuild "from" and "to" language menus from the data model. | |
194 - (void)populateLanguageMenus; | |
195 | |
196 @end | |
197 | |
198 #pragma mark TranslateInfoBarController class | |
199 @implementation TranslateInfoBarController | |
200 | |
201 - (id)initWithDelegate:(InfoBarDelegate*)delegate { | |
202 if ((self = [super initWithDelegate:delegate])) { | |
203 state_ = TranslateInfoBarDelegate::kTranslateNone; | |
204 | |
205 observer_bridge_.reset( | |
206 new TranslateNotificationObserverBridge([self delegate], self)); | |
207 | |
208 original_language_menu_model_.reset( | |
209 new LanguagesMenuModel(menu_model_.get(), [self delegate], | |
210 /*original_language=*/true)); | |
211 | |
212 target_language_menu_model_.reset( | |
213 new LanguagesMenuModel(menu_model_.get(), [self delegate], | |
214 /*original_language=*/false)); | |
215 | |
216 menu_model_.reset(new TranslateInfoBarMenuModel([self delegate], self)); | |
217 } | |
218 return self; | |
219 } | |
220 | |
221 - (TranslateInfoBarDelegate*)delegate { | |
222 return reinterpret_cast<TranslateInfoBarDelegate*>(delegate_); | |
223 } | |
224 | |
225 - (void)constructViews { | |
226 // Using a zero or very large frame causes GTMUILocalizerAndLayoutTweaker | |
227 // to not resize the view properly so we take the bounds of the first label | |
228 // which is contained in the nib. | |
229 NSRect bogusFrame = [label_ frame]; | |
230 label1_.reset(CreateLabel(bogusFrame)); | |
231 label2_.reset(CreateLabel(bogusFrame)); | |
232 label3_.reset(CreateLabel(bogusFrame)); | |
233 translatingLabel_.reset(CreateLabel(bogusFrame)); | |
234 | |
235 optionsPopUp_.reset([[NSPopUpButton alloc] initWithFrame:bogusFrame | |
236 pullsDown:YES]); | |
237 fromLanguagePopUp_.reset([[NSPopUpButton alloc] initWithFrame:bogusFrame | |
238 pullsDown:NO]); | |
239 toLanguagePopUp_.reset([[NSPopUpButton alloc] initWithFrame:bogusFrame | |
240 pullsDown:NO]); | |
241 | |
242 showOriginalButton_.reset([[NSButton alloc] initWithFrame:bogusFrame]); | |
243 tryAgainButton_.reset([[NSButton alloc] initWithFrame:bogusFrame]); | |
244 } | |
245 | |
246 - (void)sourceLanguageModified:(NSInteger)newLanguageIdx { | |
247 DCHECK_GT(newLanguageIdx, 0); | |
248 | |
249 if (newLanguageIdx == [self delegate]->original_lang_index()) | |
250 return; | |
251 | |
252 [self delegate]->ModifyOriginalLanguage(newLanguageIdx); | |
253 | |
254 int commandId = IDC_TRANSLATE_ORIGINAL_LANGUAGE_BASE + newLanguageIdx; | |
255 int newMenuIdx = [fromLanguagePopUp_ indexOfItemWithTag:commandId]; | |
256 [fromLanguagePopUp_ selectItemAtIndex:newMenuIdx]; | |
257 | |
258 [self languageModified]; | |
259 } | |
260 | |
261 - (void)targetLanguageModified:(NSInteger)newLanguageIdx { | |
262 DCHECK_GT(newLanguageIdx, 0); | |
263 if (newLanguageIdx == [self delegate]->target_lang_index()) | |
264 return; | |
265 | |
266 [self delegate]->ModifyTargetLanguage(newLanguageIdx); | |
267 | |
268 int commandId = IDC_TRANSLATE_TARGET_LANGUAGE_BASE + newLanguageIdx; | |
269 int newMenuIdx = [toLanguagePopUp_ indexOfItemWithTag:commandId]; | |
270 [toLanguagePopUp_ selectItemAtIndex:newMenuIdx]; | |
271 | |
272 [self languageModified]; | |
273 } | |
274 | |
275 - (void)languageModified { | |
276 // Selecting an item from the "from language" menu in the before translate | |
277 // phase shouldn't trigger translation - http://crbug.com/36666 | |
278 TranslateInfoBarDelegate* delegate = [self delegate]; | |
279 if (delegate->state() == TranslateInfoBarDelegate::kAfterTranslate) { | |
280 delegate->Translate(); | |
281 [self updateState:delegate->state() | |
282 translationPending:delegate->translation_pending() | |
283 error:delegate->error_type()]; | |
284 } | |
285 } | |
286 | |
287 - (void)updateState:(TranslateInfoBarDelegate::TranslateState)newState | |
288 translationPending:(bool)newTranslationPending | |
289 error:(TranslateErrors::Type)error { | |
290 if (state_ == newState && translationPending_ == newTranslationPending) | |
291 return; | |
292 | |
293 state_ = newState; | |
294 translationPending_ = newTranslationPending; | |
295 | |
296 [self loadLabelText:error]; | |
297 | |
298 [self resizeAndSetControlVisibility]; | |
299 [self layout]; | |
300 } | |
301 | |
302 - (void)setInfoBarGradientColor { | |
303 // Use grey gradient for the infobars. | |
304 NSColor* startingColor = | |
305 [NSColor colorWithCalibratedRed:kGreyTopColor[0] / 255.0 | |
306 green:kGreyTopColor[1] / 255.0 | |
307 blue:kGreyTopColor[2] / 255.0 | |
308 alpha:1.0]; | |
309 NSColor* endingColor = | |
310 [NSColor colorWithCalibratedRed:kGreyBottomColor[0] / 255.0 | |
311 green:kGreyBottomColor[1] / 255.0 | |
312 blue:kGreyBottomColor[2] / 255.0 | |
313 alpha:1.0]; | |
314 NSGradient* translateInfoBarGradient = | |
315 [[[NSGradient alloc] initWithStartingColor:startingColor | |
316 endingColor:endingColor] autorelease]; | |
317 | |
318 [infoBarView_ setGradient:translateInfoBarGradient]; | |
319 } | |
320 | |
321 - (void)resizeAndSetControlVisibility { | |
322 // Step 1: remove all controls from the infobar so we have a clean slate. | |
323 NSArray *allControls = [NSArray arrayWithObjects:label2_.get(), label3_.get(), | |
324 translatingLabel_.get(), fromLanguagePopUp_.get(), toLanguagePopUp_.get(), | |
325 showOriginalButton_.get(), tryAgainButton_.get(), nil]; | |
326 | |
327 for (NSControl* control in allControls) { | |
328 if ([control superview]) | |
329 [control removeFromSuperview]; | |
330 } | |
331 | |
332 // OK & Cancel buttons are only visible in "before translate" mode when no | |
333 // translation is in progress. | |
334 if (state_ != TranslateInfoBarDelegate::kBeforeTranslate || | |
335 translationPending_) { | |
336 // Removing okButton_ & cancelButton_ from the view may cause them | |
337 // to be released and since we can still access them from other areas | |
338 // in the code later, we need them to be nil when this happens. | |
339 [okButton_ removeFromSuperview]; | |
340 okButton_ = nil; | |
341 [cancelButton_ removeFromSuperview]; | |
342 cancelButton_ = nil; | |
343 | |
344 } | |
345 | |
346 // Step 2: Resize all visible controls and add them to the infobar. | |
347 NSMutableArray *visibleControls = nil; | |
348 | |
349 switch (state_) { | |
350 case TranslateInfoBarDelegate::kBeforeTranslate: | |
351 visibleControls = [NSMutableArray arrayWithObjects:label1_.get(), | |
352 label2_.get(), fromLanguagePopUp_.get(), nil]; | |
353 | |
354 if (!translationPending_) { | |
355 [visibleControls addObject:okButton_]; | |
356 [visibleControls addObject:cancelButton_]; | |
357 } | |
358 break; | |
359 case TranslateInfoBarDelegate::kAfterTranslate: | |
360 visibleControls = [NSMutableArray arrayWithObjects:label1_.get(), | |
361 label2_.get(), fromLanguagePopUp_.get(), toLanguagePopUp_.get(), nil]; | |
362 if (!translationPending_) { | |
363 [visibleControls addObject:showOriginalButton_.get()]; | |
364 } | |
365 break; | |
366 case TranslateInfoBarDelegate::kTranslateError: | |
367 visibleControls = [NSMutableArray arrayWithObjects:label1_.get(), nil]; | |
368 | |
369 if (!translationPending_) { | |
370 [visibleControls addObject:tryAgainButton_.get()]; | |
371 } | |
372 break; | |
373 default: | |
374 NOTREACHED() << "Invalid translate infobar state"; | |
375 break; | |
376 } | |
377 | |
378 if (translationPending_) { | |
379 [visibleControls addObject:translatingLabel_]; | |
380 } | |
381 | |
382 if (numLabelsDisplayed_ >= 3) { | |
383 [visibleControls addObject:label3_.get()]; | |
384 } | |
385 | |
386 // The options popup is only hidden in the translateError view. | |
387 BOOL optionsPopuUpHidden = | |
388 (state_ == TranslateInfoBarDelegate::kTranslateError) ? YES : NO; | |
389 [optionsPopUp_ setHidden:optionsPopuUpHidden]; | |
390 | |
391 NSRect optionsFrame = [optionsPopUp_ frame]; | |
392 for (NSControl* control in visibleControls) { | |
393 [GTMUILocalizerAndLayoutTweaker sizeToFitView:control]; | |
394 [control setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin | | |
395 NSViewMaxYMargin]; | |
396 | |
397 // Need to check if a view is already attached since |label1_| is always | |
398 // parented and we don't want to add it again. | |
399 if (![control superview]) | |
400 [infoBarView_ addSubview:control]; | |
401 | |
402 if ([control isKindOfClass:[NSButton class]]) | |
403 VerticallyCenterView(control); | |
404 | |
405 // Make "from" and "to" language popup menus the same size as the options | |
406 // menu. | |
407 // We don't autosize since some languages names are really long causing | |
408 // the toolbar to overflow. | |
409 if ([control isKindOfClass:[NSPopUpButton class]]) | |
410 [control setFrame:optionsFrame]; | |
411 } | |
412 } | |
413 | |
414 - (void)layout { | |
415 if (state_ != TranslateInfoBarDelegate::kAfterTranslate) { | |
416 // 3rd label is only displayed in some locales, but should never be | |
417 // visible in this stage. | |
418 // If it ever is visible then we need to move it into position here. | |
419 DCHECK(numLabelsDisplayed_ < 3); | |
420 } | |
421 | |
422 switch (state_) { | |
423 case TranslateInfoBarDelegate::kBeforeTranslate: | |
424 MoveControl(label1_, fromLanguagePopUp_, 0, true); | |
425 MoveControl(fromLanguagePopUp_, label2_, 0, true); | |
426 | |
427 if (!translationPending_) { | |
428 MoveControl(label2_, okButton_, spaceBetweenControls_, true); | |
429 MoveControl(okButton_, cancelButton_, spaceBetweenControls_, true); | |
430 } else { | |
431 MoveControl(label2_, translatingLabel_, spaceBetweenControls_, true); | |
432 } | |
433 break; | |
434 | |
435 case TranslateInfoBarDelegate::kAfterTranslate: { | |
436 NSView* lastControl = toLanguagePopUp_; | |
437 MoveControl(label1_, fromLanguagePopUp_, 0, true); | |
438 MoveControl(fromLanguagePopUp_, label2_, 0, true); | |
439 MoveControl(label2_, toLanguagePopUp_, 0, true); | |
440 if (numLabelsDisplayed_ == 3) { | |
441 MoveControl(toLanguagePopUp_, label3_, 0, true); | |
442 lastControl = label3_; | |
443 } | |
444 | |
445 if (translationPending_) { | |
446 MoveControl(lastControl, translatingLabel_, spaceBetweenControls_ * 2, | |
447 true); | |
448 } else { | |
449 MoveControl(lastControl, showOriginalButton_, spaceBetweenControls_ * 2, | |
450 true); | |
451 } | |
452 | |
453 break; | |
454 } | |
455 | |
456 case TranslateInfoBarDelegate::kTranslateError: | |
457 if (translationPending_) { | |
458 MoveControl(label1_, translatingLabel_, 0, true); | |
459 } else { | |
460 MoveControl(label1_, tryAgainButton_, spaceBetweenControls_ * 2, true); | |
461 } | |
462 break; | |
463 | |
464 default: | |
465 NOTREACHED() << "Invalid translate infobar state"; | |
466 break; | |
467 } | |
468 } | |
469 | |
470 - (void) rebuildOptionsMenu { | |
471 // The options model doesn't know how to handle state transitions, so rebuild | |
472 // it each time through here. | |
473 options_menu_model_.reset( | |
474 new OptionsMenuModel(menu_model_.get(), [self delegate])); | |
475 | |
476 [optionsPopUp_ removeAllItems]; | |
477 // Set title. | |
478 NSString* optionsLabel = | |
479 l10n_util::GetNSString(IDS_TRANSLATE_INFOBAR_OPTIONS); | |
480 [optionsPopUp_ addItemWithTitle:optionsLabel]; | |
481 | |
482 // Populate options menu. | |
483 NSMenu* optionsMenu = [optionsPopUp_ menu]; | |
484 [optionsMenu setAutoenablesItems:NO]; | |
485 for (int i = 0; i < options_menu_model_->GetItemCount(); ++i) { | |
486 NSString* title = base::SysUTF16ToNSString( | |
487 options_menu_model_->GetLabelAt(i)); | |
488 int cmd = options_menu_model_->GetCommandIdAt(i); | |
489 bool checked = options_menu_model_->IsItemCheckedAt(i); | |
490 bool enabled = options_menu_model_->IsEnabledAt(i); | |
491 AddMenuItem(optionsMenu, self, title, cmd, enabled, checked); | |
492 } | |
493 } | |
494 | |
495 - (void)populateLanguageMenus { | |
496 NSMenu* originalLanguageMenu = [fromLanguagePopUp_ menu]; | |
497 [originalLanguageMenu setAutoenablesItems:NO]; | |
498 int selectedMenuIndex = 0; | |
499 int selectedLangIndex = [self delegate]->original_lang_index(); | |
500 for (int i = 0; i < original_language_menu_model_->GetItemCount(); ++i) { | |
501 NSString* title = base::SysUTF16ToNSString( | |
502 original_language_menu_model_->GetLabelAt(i)); | |
503 int cmd = original_language_menu_model_->GetCommandIdAt(i); | |
504 bool checked = | |
505 (cmd - IDC_TRANSLATE_ORIGINAL_LANGUAGE_BASE) == selectedLangIndex; | |
506 if (checked) | |
507 selectedMenuIndex = i; | |
508 bool enabled = original_language_menu_model_->IsEnabledAt(i); | |
509 AddMenuItem(originalLanguageMenu, self, title, cmd, enabled, checked); | |
510 } | |
511 [fromLanguagePopUp_ selectItemAtIndex:selectedMenuIndex]; | |
512 | |
513 NSMenu* targetLanguageMenu = [toLanguagePopUp_ menu]; | |
514 [targetLanguageMenu setAutoenablesItems:NO]; | |
515 selectedLangIndex = [self delegate]->target_lang_index(); | |
516 for (int i = 0; i < target_language_menu_model_->GetItemCount(); ++i) { | |
517 NSString* title = base::SysUTF16ToNSString( | |
518 target_language_menu_model_->GetLabelAt(i)); | |
519 int cmd = target_language_menu_model_->GetCommandIdAt(i); | |
520 bool checked = | |
521 (cmd - IDC_TRANSLATE_TARGET_LANGUAGE_BASE) == selectedLangIndex; | |
522 if (checked) | |
523 selectedMenuIndex = i; | |
524 bool enabled = target_language_menu_model_->IsEnabledAt(i); | |
525 AddMenuItem(targetLanguageMenu, self, title, cmd, enabled, checked); | |
526 } | |
527 [toLanguagePopUp_ selectItemAtIndex:selectedMenuIndex]; | |
528 } | |
529 | |
530 - (void)loadLabelText:(TranslateErrors::Type)error { | |
531 numLabelsDisplayed_ = 2; | |
532 | |
533 NSString* label1Text = @""; | |
534 NSString* label2Text = @""; | |
535 NSString* label3Text = @""; | |
536 | |
537 if (state_ == TranslateInfoBarDelegate::kTranslateError) { | |
538 // Load an error message, if an error occured and the user clicked | |
539 // "try again" then blank all labels. | |
540 if (!translationPending_) { | |
541 string16 message_text_utf16 = [self delegate]->GetErrorMessage(error); | |
542 label1Text = base::SysUTF16ToNSString(message_text_utf16); | |
543 } | |
544 } else { | |
545 string16 message_text_utf16; | |
546 std::vector<size_t> offsets; | |
547 [self delegate]->GetMessageText(state_, &message_text_utf16, | |
548 &offsets, &swappedLanguagePlaceholders_); | |
549 | |
550 NSString* message_text = base::SysUTF16ToNSString(message_text_utf16); | |
551 NSRange label1Range = NSMakeRange(0, offsets[0]); | |
552 label1Text = [message_text substringWithRange:label1Range]; | |
553 NSRange label2Range = NSMakeRange(offsets[0], | |
554 offsets[1] - offsets[0]); | |
555 label2Text = [message_text substringWithRange:label2Range]; | |
556 | |
557 // If this locale requires a 3rd label for the status message. | |
558 if (offsets.size() == 3) { | |
559 NSRange label3Range = NSMakeRange(offsets[1], | |
560 offsets[2] - offsets[1]); | |
561 label3Text = [message_text substringWithRange:label3Range]; | |
562 numLabelsDisplayed_ = 3; | |
563 } | |
564 } | |
565 | |
566 [label1_ setStringValue:label1Text]; | |
567 [label2_ setStringValue:label2Text]; | |
568 [label3_ setStringValue:label3Text]; | |
569 } | |
570 | |
571 - (void)addAdditionalControls { | |
572 using l10n_util::GetNSString; | |
573 using l10n_util::GetNSStringWithFixup; | |
574 | |
575 // Get layout information from the NIB. | |
576 NSRect okButtonFrame = [okButton_ frame]; | |
577 NSRect cancelButtonFrame = [cancelButton_ frame]; | |
578 spaceBetweenControls_ = NSMinX(cancelButtonFrame) - NSMaxX(okButtonFrame); | |
579 | |
580 // Set infobar background color. | |
581 [self setInfoBarGradientColor]; | |
582 | |
583 // Instantiate additional controls. | |
584 [self constructViews]; | |
585 | |
586 // Set ourselves as the delegate for the options menu so we can populate it | |
587 // dynamically. | |
588 [[optionsPopUp_ menu] setDelegate:self]; | |
589 | |
590 // Replace label_ with label1_ so we get a consistent look between all the | |
591 // labels we display in the translate view. | |
592 [[label_ superview] replaceSubview:label_ with:label1_.get()]; | |
593 label_.reset(); // Now released. | |
594 | |
595 // Populate contextual menus. | |
596 [self rebuildOptionsMenu]; | |
597 [self populateLanguageMenus]; | |
598 | |
599 // Set OK & Cancel text. | |
600 [okButton_ setTitle:GetNSStringWithFixup(IDS_TRANSLATE_INFOBAR_ACCEPT)]; | |
601 [cancelButton_ setTitle:GetNSStringWithFixup(IDS_TRANSLATE_INFOBAR_DENY)]; | |
602 [translatingLabel_ | |
603 setStringValue:GetNSString(IDS_TRANSLATE_INFOBAR_TRANSLATING)]; | |
604 | |
605 // Set up "Show original" and "Try again" buttons. | |
606 [showOriginalButton_ setBezelStyle:NSRoundRectBezelStyle]; | |
607 [showOriginalButton_ setFrame:okButtonFrame]; | |
608 [tryAgainButton_ setBezelStyle:NSRoundRectBezelStyle]; | |
609 [tryAgainButton_ setFrame:okButtonFrame]; | |
610 | |
611 [showOriginalButton_ setTarget:self]; | |
612 [showOriginalButton_ setAction:@selector(showOriginal:)]; | |
613 [tryAgainButton_ setTarget:self]; | |
614 [tryAgainButton_ setAction:@selector(ok:)]; | |
615 | |
616 [showOriginalButton_ | |
617 setTitle:GetNSStringWithFixup(IDS_TRANSLATE_INFOBAR_REVERT)]; | |
618 [tryAgainButton_ | |
619 setTitle:GetNSStringWithFixup(IDS_TRANSLATE_INFOBAR_RETRY)]; | |
620 | |
621 // Add and configure controls that are visible in all modes. | |
622 [optionsPopUp_ setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin | | |
623 NSViewMaxYMargin]; | |
624 // Add "options" popup z-ordered below all other controls so when we | |
625 // resize the toolbar it doesn't hide them. | |
626 [infoBarView_ addSubview:optionsPopUp_ | |
627 positioned:NSWindowBelow | |
628 relativeTo:nil]; | |
629 [GTMUILocalizerAndLayoutTweaker sizeToFitView:optionsPopUp_]; | |
630 MoveControl(closeButton_, optionsPopUp_, spaceBetweenControls_, false); | |
631 VerticallyCenterView(optionsPopUp_); | |
632 | |
633 // Show and place GUI elements. | |
634 TranslateInfoBarDelegate* delegate = [self delegate]; | |
635 [self updateState:delegate->state() | |
636 translationPending:delegate->translation_pending() | |
637 error:delegate->error_type()]; | |
638 } | |
639 | |
640 // Called when "Translate" button is clicked. | |
641 - (IBAction)ok:(id)sender { | |
642 TranslateInfoBarDelegate* delegate = [self delegate]; | |
643 TranslateInfoBarDelegate::TranslateState state = delegate->state(); | |
644 DCHECK(state == TranslateInfoBarDelegate::kBeforeTranslate || | |
645 state == TranslateInfoBarDelegate::kTranslateError); | |
646 delegate->Translate(); | |
647 [self updateState:state | |
648 translationPending:delegate->translation_pending() | |
649 error:delegate->error_type()]; | |
650 UMA_HISTOGRAM_COUNTS("Translate.Translate", 1); | |
651 } | |
652 | |
653 // Called when someone clicks on the "Nope" button. | |
654 - (IBAction)cancel:(id)sender { | |
655 DCHECK( | |
656 [self delegate]->state() == TranslateInfoBarDelegate::kBeforeTranslate); | |
657 [self delegate]->TranslationDeclined(); | |
658 UMA_HISTOGRAM_COUNTS("Translate.DeclineTranslate", 1); | |
659 [super dismiss:nil]; | |
660 } | |
661 | |
662 - (IBAction)showOriginal:(id)sender { | |
663 [self delegate]->RevertTranslation(); | |
664 } | |
665 | |
666 - (void)menuItemSelected:(id)item { | |
667 if ([item respondsToSelector:@selector(tag)]) { | |
668 int cmd = [item tag]; | |
669 // Danger Will Robinson! : This call can release the infobar (e.g. invoking | |
670 // "About Translate" can open a new tab). | |
671 // Do not access member variables after this line! | |
672 menu_model_->ExecuteCommand(cmd); | |
673 } else { | |
674 NOTREACHED(); | |
675 } | |
676 } | |
677 | |
678 #pragma mark NSMenuDelegate | |
679 | |
680 // Invoked by virtue of us being set as the delegate for the options menu. | |
681 - (void)menuNeedsUpdate:(NSMenu *)menu { | |
682 [self rebuildOptionsMenu]; | |
683 } | |
684 | |
685 #pragma mark TestingAPI | |
686 - (NSMenu*)optionsMenu { | |
687 return [optionsPopUp_ menu]; | |
688 } | |
689 | |
690 - (NSButton*)tryAgainButton { | |
691 return tryAgainButton_.get(); | |
692 } | |
693 | |
694 - (TranslateInfoBarDelegate::TranslateState)state { | |
695 return state_; | |
696 } | |
697 | |
698 - (bool)verifyLayout:(TranslateInfoBarDelegate::TranslateState)state | |
699 translationPending:(bool)translationPending { | |
700 NSArray* allControls = [NSArray arrayWithObjects:label1_.get(), label2_.get(), | |
701 label3_.get(), translatingLabel_.get(), fromLanguagePopUp_.get(), | |
702 toLanguagePopUp_.get(), optionsPopUp_.get(), closeButton_, | |
703 showOriginalButton_.get(), tryAgainButton_.get(), nil]; | |
704 | |
705 // Sanity check - parameters should match internal state. | |
706 if (state != state_) { | |
707 LOG(ERROR) << "State mismatch: " << state << " vs " << state_; | |
708 return false; | |
709 } | |
710 | |
711 if (translationPending != translationPending_) { | |
712 LOG(ERROR) << "Pending Translation mismatch: " << | |
713 translationPending << " vs " << translationPending_; | |
714 return false; | |
715 } | |
716 | |
717 // Array of all visible controls ordered from start -> end. | |
718 NSArray* visibleControls = nil; | |
719 | |
720 switch (state) { | |
721 case TranslateInfoBarDelegate::kBeforeTranslate: | |
722 if (translationPending) { | |
723 visibleControls = [NSArray arrayWithObjects:label1_.get(), | |
724 fromLanguagePopUp_.get(), label2_.get(), translatingLabel_.get(), | |
725 optionsPopUp_.get(), closeButton_, nil]; | |
726 } else { | |
727 visibleControls = [NSArray arrayWithObjects:label1_.get(), | |
728 fromLanguagePopUp_.get(), label2_.get(), optionsPopUp_.get(), | |
729 closeButton_, nil]; | |
730 } | |
731 break; | |
732 case TranslateInfoBarDelegate::kAfterTranslate: | |
733 if (translationPending) { | |
734 visibleControls = [NSArray arrayWithObjects:label1_.get(), | |
735 fromLanguagePopUp_.get(), label2_.get(), toLanguagePopUp_.get(), | |
736 translatingLabel_.get(), optionsPopUp_.get(), closeButton_, nil]; | |
737 } else { | |
738 visibleControls = [NSArray arrayWithObjects:label1_.get(), | |
739 fromLanguagePopUp_.get(), label2_.get(), toLanguagePopUp_.get(), | |
740 showOriginalButton_.get(), optionsPopUp_.get(), closeButton_, nil]; | |
741 } | |
742 break; | |
743 case TranslateInfoBarDelegate::kTranslateError: | |
744 if (translationPending) { | |
745 visibleControls = [NSArray arrayWithObjects:label1_.get(), | |
746 translatingLabel_.get(), closeButton_, nil]; | |
747 } else { | |
748 visibleControls = [NSArray arrayWithObjects:label1_.get(), | |
749 tryAgainButton_.get(), closeButton_, nil]; | |
750 } | |
751 break; | |
752 default: | |
753 NOTREACHED() << "Unknown state"; | |
754 return false; | |
755 } | |
756 | |
757 // Step 1: Make sure control visibility is what we expect. | |
758 for (NSUInteger i = 0; i < [allControls count]; ++i) { | |
759 id control = [allControls objectAtIndex:i]; | |
760 bool hasSuperView = [control superview]; | |
761 bool expectedVisibility = [visibleControls containsObject:control]; | |
762 | |
763 | |
764 // Special case the options popup, which we hide rather than removing | |
765 // from the superview. | |
766 if (control == optionsPopUp_.get()) | |
767 hasSuperView = [control isHidden] == NO; | |
768 | |
769 if (expectedVisibility != hasSuperView) { | |
770 NSString *title = @""; | |
771 | |
772 if ([control isKindOfClass:[NSPopUpButton class]]) { | |
773 title = [[[control menu] itemAtIndex:0] title]; | |
774 } | |
775 | |
776 LOG(ERROR) << | |
777 "State: " << state << " translationPending " << translationPending << | |
778 " Control @" << i << (hasSuperView ? " has" : " doesn't have") << | |
779 " a superview" << [[control description] UTF8String] << | |
780 " Title=" << [title UTF8String]; | |
781 return false; | |
782 } | |
783 } | |
784 | |
785 // Step 2: Check that controls are ordered correctly with no overlap. | |
786 #if !defined(DISABLE_VERIFY_CONTROL_ORDER) | |
787 // http://crbug.com/46663 this appears to be invalid. | |
788 // VerifyControlOrderAndSpacing had an unsigned >= 0 bug, so it used to always | |
789 // return true. With that bug fixed, this loop now can return a failure. | |
790 // Scanning the code, it's not clear how this would pass since not all | |
791 // controls are visible and it needs the array order to always match display | |
792 // order. | |
793 id previousControl = nil; | |
794 for (NSUInteger i = 0; i < [allControls count]; ++i) { | |
795 id control = [allControls objectAtIndex:i]; | |
796 if (!VerifyControlOrderAndSpacing(previousControl, control)) { | |
797 LOG(ERROR) << | |
798 "State: " << state << " translationPending " << translationPending << | |
799 " Control @" << i << " not ordered correctly: " << | |
800 [[control description] UTF8String]; | |
801 return false; | |
802 } | |
803 previousControl = control; | |
804 } | |
805 #endif // !defined(DISABLE_VERIFY_CONTROL_ORDER) | |
806 | |
807 // Step 3: Check other misc. attributes of layout. | |
808 if (state == TranslateInfoBarDelegate::kTranslateError && translationPending) | |
809 { | |
810 if ([[label1_ stringValue] length] != 0) { | |
811 LOG(ERROR) << "Expected empty label1_, instead got" << | |
812 [[label1_ description] UTF8String]; | |
813 return false; | |
814 } | |
815 } | |
816 | |
817 return true; | |
818 } | |
819 | |
820 @end | |
821 | |
822 #pragma mark CreateInfoBar implementation. | |
823 InfoBar* TranslateInfoBarDelegate::CreateInfoBar() { | |
824 TranslateInfoBarController* controller = | |
825 [[TranslateInfoBarController alloc] initWithDelegate:this]; | |
826 return new InfoBar(controller); | |
827 } | |
828 | |
829 #pragma mark menus::SimpleMenuModel::Delegates | |
830 | |
831 bool TranslateInfoBarMenuModel::IsCommandIdChecked(int command_id) const { | |
832 switch (command_id) { | |
833 case IDC_TRANSLATE_OPTIONS_NEVER_TRANSLATE_LANG : | |
834 return translate_delegate_->IsLanguageBlacklisted(); | |
835 | |
836 case IDC_TRANSLATE_OPTIONS_NEVER_TRANSLATE_SITE : | |
837 return translate_delegate_->IsSiteBlacklisted(); | |
838 | |
839 case IDC_TRANSLATE_OPTIONS_ALWAYS : | |
840 return translate_delegate_->ShouldAlwaysTranslate(); | |
841 | |
842 default: | |
843 NOTREACHED() << "Invalid command_id from menu"; | |
844 break; | |
845 } | |
846 return false; | |
847 } | |
848 | |
849 bool TranslateInfoBarMenuModel::IsCommandIdEnabled(int command_id) const { | |
850 switch (command_id) { | |
851 case IDC_TRANSLATE_OPTIONS_NEVER_TRANSLATE_LANG : | |
852 case IDC_TRANSLATE_OPTIONS_NEVER_TRANSLATE_SITE : | |
853 return !translate_delegate_->ShouldAlwaysTranslate(); | |
854 | |
855 case IDC_TRANSLATE_OPTIONS_ALWAYS : | |
856 return (!translate_delegate_->IsLanguageBlacklisted() && | |
857 !translate_delegate_->IsSiteBlacklisted()); | |
858 | |
859 default: | |
860 break; | |
861 } | |
862 return true; | |
863 } | |
864 | |
865 bool TranslateInfoBarMenuModel::GetAcceleratorForCommandId(int command_id, | |
866 menus::Accelerator* accelerator) { | |
867 return false; | |
868 } | |
869 | |
870 void TranslateInfoBarMenuModel::ExecuteCommand(int command_id) { | |
871 if (command_id >= IDC_TRANSLATE_TARGET_LANGUAGE_BASE) { | |
872 int language_command_id = | |
873 command_id - IDC_TRANSLATE_TARGET_LANGUAGE_BASE; | |
874 [controller_ | |
875 targetLanguageModified:language_command_id]; | |
876 } else if (command_id >= IDC_TRANSLATE_ORIGINAL_LANGUAGE_BASE) { | |
877 int language_command_id = | |
878 command_id - IDC_TRANSLATE_ORIGINAL_LANGUAGE_BASE; | |
879 [controller_ | |
880 sourceLanguageModified:language_command_id]; | |
881 } else { | |
882 switch (command_id) { | |
883 case IDC_TRANSLATE_OPTIONS_NEVER_TRANSLATE_LANG: | |
884 translate_delegate_->ToggleLanguageBlacklist(); | |
885 break; | |
886 | |
887 case IDC_TRANSLATE_OPTIONS_NEVER_TRANSLATE_SITE: | |
888 translate_delegate_->ToggleSiteBlacklist(); | |
889 break; | |
890 | |
891 case IDC_TRANSLATE_OPTIONS_ALWAYS: | |
892 translate_delegate_->ToggleAlwaysTranslate(); | |
893 break; | |
894 | |
895 case IDC_TRANSLATE_OPTIONS_ABOUT: { | |
896 TabContents* tab_contents = translate_delegate_->tab_contents(); | |
897 if (tab_contents) { | |
898 string16 url = l10n_util::GetStringUTF16( | |
899 IDS_ABOUT_GOOGLE_TRANSLATE_URL); | |
900 tab_contents->OpenURL(GURL(url), GURL(), NEW_FOREGROUND_TAB, | |
901 PageTransition::LINK); | |
902 } | |
903 break; | |
904 } | |
905 | |
906 default: | |
907 NOTREACHED() << "Invalid command id from menu."; | |
908 break; | |
909 } | |
910 } | |
911 } | |
912 | |
913 # pragma mark TranslateInfoBarNotificationObserverBridge | |
914 | |
915 TranslateNotificationObserverBridge::TranslateNotificationObserverBridge( | |
916 TranslateInfoBarDelegate* delegate, | |
917 TranslateInfoBarController* controller) : | |
918 translate_delegate_(delegate), | |
919 controller_(controller) { | |
920 // Register for PAGE_TRANSLATED notification. | |
921 notification_registrar_.Add(this, NotificationType::PAGE_TRANSLATED, | |
922 Source<TabContents>(translate_delegate_->tab_contents())); | |
923 }; | |
924 | |
925 void TranslateNotificationObserverBridge::Observe(NotificationType type, | |
926 const NotificationSource& source, const NotificationDetails& details) { | |
927 if (type.value != NotificationType::PAGE_TRANSLATED) | |
928 return; | |
929 TabContents* tab = Source<TabContents>(source).ptr(); | |
930 if (tab != translate_delegate_->tab_contents()) | |
931 return; | |
932 PageTranslatedDetails* page_translated_details = | |
933 Details<PageTranslatedDetails>(details).ptr(); | |
934 TranslateErrors::Type error = page_translated_details->error_type; | |
935 TranslateInfoBarDelegate::TranslateState newState = | |
936 TranslateInfoBarDelegate::kAfterTranslate; | |
937 if (page_translated_details->error_type != TranslateErrors::NONE) | |
938 newState = TranslateInfoBarDelegate::kTranslateError; | |
939 [controller_ updateState:newState translationPending:false error:error]; | |
940 | |
941 } | |
OLD | NEW |