| 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 |