diff options
Diffstat (limited to 'chrome/browser/cocoa/about_window_controller.mm')
-rw-r--r-- | chrome/browser/cocoa/about_window_controller.mm | 351 |
1 files changed, 305 insertions, 46 deletions
diff --git a/chrome/browser/cocoa/about_window_controller.mm b/chrome/browser/cocoa/about_window_controller.mm index d9ea076..5280c26 100644 --- a/chrome/browser/cocoa/about_window_controller.mm +++ b/chrome/browser/cocoa/about_window_controller.mm @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "app/l10n_util.h" +#include "app/l10n_util_mac.h" +#include "app/resource_bundle.h" #include "base/file_version_info.h" #include "base/logging.h" #include "base/mac_util.h" @@ -10,15 +11,170 @@ #include "base/sys_string_conversions.h" #import "chrome/app/keystone_glue.h" #import "chrome/browser/cocoa/about_window_controller.h" +#import "chrome/browser/cocoa/background_tile_view.h" +#include "chrome/browser/cocoa/restart_browser.h" +#include "grit/chromium_strings.h" #include "grit/generated_resources.h" +#include "grit/theme_resources.h" +#include "grit/locale_settings.h" +#include "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" NSString* const kUserClosedAboutNotification = @"kUserClosedAboutNotification"; @interface AboutWindowController (Private) - (KeystoneGlue*)defaultKeystoneGlue; +- (void)startProgressMessageID:(uint32_t)messageID; +- (void)startProgressMessage:(NSString*)message; +- (void)stopProgressMessage:(NSString*)message imageID:(uint32_t)imageID; @end +namespace { + +// Keystone doesn't give us error numbers on some results, so we just make +// our own for reporting in the UI. +const int kUpdateInstallFailed = 128; +const int kUpdateInstallFailedToStart = 129; + +void AttributedStringAppendString(NSMutableAttributedString* attr_str, + NSString* str) { + // You might think doing [[attr_str mutableString] appendString:str] would + // work, but it causes any trailing style to get extened, meaning as we + // append links, they grow to include the new text, not what we want. + NSAttributedString* new_attr_str = + [[[NSAttributedString alloc] initWithString:str] autorelease]; + [attr_str appendAttributedString:new_attr_str]; +} + +void AttributedStringAppendHyperlink(NSMutableAttributedString* attr_str, + NSString* text, NSString* url_str) { + + // Figure out the range of the text we're adding and add the text. + NSRange range = NSMakeRange([attr_str length], [text length]); + AttributedStringAppendString(attr_str, text); + + // Add the link + [attr_str addAttribute:NSLinkAttributeName value:url_str range:range]; + + // Blue and underlined + [attr_str addAttribute:NSForegroundColorAttributeName + value:[NSColor blueColor] + range:range]; + [attr_str addAttribute:NSUnderlineStyleAttributeName + value:[NSNumber numberWithInt:NSSingleUnderlineStyle] + range:range]; +} + +NSAttributedString* BuildLegalTextBlock() { + // Windows builds this up in a very complex way, we're just trying to model + // it the best we can to get all the information in (they actually do it + // but created Labels and Links that they carefully place to make it appear + // to be a paragraph of text). + // src/chrome/browser/views/about_chrome_view.cc AboutChromeView::Init() + + NSMutableAttributedString* legal_block = + [[[NSMutableAttributedString alloc] init] autorelease]; + [legal_block beginEditing]; + + NSString* copyright = l10n_util::GetNSString(IDS_ABOUT_VERSION_COPYRIGHT); + AttributedStringAppendString(legal_block, copyright); + + // These are the markers directly in IDS_ABOUT_VERSION_LICENSE + NSString* kBeginLinkChr = @"BEGIN_LINK_CHR"; + NSString* kBeginLinkOss = @"BEGIN_LINK_OSS"; + NSString* kEndLinkChr = @"END_LINK_CHR"; + NSString* kEndLinkOss = @"END_LINK_OSS"; + // The CHR link should go to here + NSString* kChromiumProject = l10n_util::GetNSString(IDS_CHROMIUM_PROJECT_URL); + // The OSS link should go to here + NSString* kAcknowledgements = @"about:credits"; + + // Now fetch the license string and deal with the markers + + NSString* license = l10n_util::GetNSString(IDS_ABOUT_VERSION_LICENSE); + + NSRange begin_chr = [license rangeOfString:kBeginLinkChr]; + NSRange begin_oss = [license rangeOfString:kBeginLinkOss]; + NSRange end_chr = [license rangeOfString:kEndLinkChr]; + NSRange end_oss = [license rangeOfString:kEndLinkOss]; + DCHECK(begin_chr.location != NSNotFound); + DCHECK(begin_oss.location != NSNotFound); + DCHECK(end_chr.location != NSNotFound); + DCHECK(end_oss.location != NSNotFound); + + // We don't know which link will come first, so we have to deal with things + // like this: + // [text][begin][text][end][text][start][text][end][text] + + bool chromium_link_first = begin_chr.location < begin_oss.location; + + NSRange* begin1 = &begin_chr; + NSRange* begin2 = &begin_oss; + NSRange* end1 = &end_chr; + NSRange* end2 = &end_oss; + NSString* link1 = kChromiumProject; + NSString* link2 = kAcknowledgements; + if (!chromium_link_first) { + // OSS came first, switch! + begin2 = &begin_chr; + begin1 = &begin_oss; + end2 = &end_chr; + end1 = &end_oss; + link2 = kChromiumProject; + link1 = kAcknowledgements; + } + + NSString *sub_str; + + AttributedStringAppendString(legal_block, @"\n"); + sub_str = [license substringWithRange:NSMakeRange(0, begin1->location)]; + AttributedStringAppendString(legal_block, sub_str); + sub_str = [license substringWithRange:NSMakeRange(NSMaxRange(*begin1), + end1->location - + NSMaxRange(*begin1))]; + AttributedStringAppendHyperlink(legal_block, sub_str, link1); + sub_str = [license substringWithRange:NSMakeRange(NSMaxRange(*end1), + begin2->location - + NSMaxRange(*end1))]; + AttributedStringAppendString(legal_block, sub_str); + sub_str = [license substringWithRange:NSMakeRange(NSMaxRange(*begin2), + end2->location - + NSMaxRange(*begin2))]; + AttributedStringAppendHyperlink(legal_block, sub_str, link2); + sub_str = [license substringWithRange:NSMakeRange(NSMaxRange(*end2), + [license length] - + NSMaxRange(*end2))]; + AttributedStringAppendString(legal_block, sub_str); + +#if defined(GOOGLE_CHROME_BUILD) + // Terms of service is only valid for Google Chrome + + // The url within terms should point here: + NSString* kTOS = @"about:terms"; + // Following Window. There is one marker in the string for where the terms + // link goes, but the text of the link comes from a second string resources. + std::vector<size_t> url_offsets; + std::wstring w_about_terms = l10n_util::GetStringF(IDS_ABOUT_TERMS_OF_SERVICE, + std::wstring(), + std::wstring(), + &url_offsets); + DCHECK_EQ(url_offsets.size(), 1U); + NSString* about_terms = base::SysWideToNSString(w_about_terms); + NSString* terms_link_text = l10n_util::GetNSString(IDS_TERMS_OF_SERVICE); + + AttributedStringAppendString(legal_block, @"\n"); + sub_str = [about_terms substringToIndex:url_offsets[0]]; + AttributedStringAppendString(legal_block, sub_str); + AttributedStringAppendHyperlink(legal_block, terms_link_text, kTOS); + sub_str = [about_terms substringFromIndex:url_offsets[0]]; + AttributedStringAppendString(legal_block, sub_str); +#endif // defined(GOOGLE_CHROME_BUILD) + + [legal_block endEditing]; + return legal_block; +} + +} // namespace @implementation AboutWindowController @@ -33,45 +189,133 @@ NSString* const kUserClosedAboutNotification = // Set our current version. scoped_ptr<FileVersionInfo> version_info( FileVersionInfo::CreateFileVersionInfoForCurrentModule()); - NSString* version = base::SysWideToNSString(version_info->file_version()); - [version_ setStringValue:version]; + std::wstring version(version_info->product_version()); +#if !defined(GOOGLE_CHROME_BUILD) + // Yes, Windows does this raw since it is only in Chromium builds + // src/chrome/browser/views/about_chrome_view.cc AboutChromeView::Init() + version += L" ("; + version += version_info->last_change(); + version += L")"; +#endif + NSString* nsversion = base::SysWideToNSString(version); + [version_ setStringValue:nsversion]; + + // Put the two images into the ui + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + NSImage* backgroundImage = rb.GetNSImageNamed(IDR_ABOUT_BACKGROUND_COLOR); + DCHECK(backgroundImage); + [backgroundView_ setTileImage:backgroundImage]; + NSImage* logoImage = rb.GetNSImageNamed(IDR_ABOUT_BACKGROUND); + DCHECK(logoImage); + [logoView_ setImage:logoImage]; + + // Put the legal text into + [legalBlock_ setAttributedStringValue:BuildLegalTextBlock()]; + + KeystoneGlue* keystone = [self defaultKeystoneGlue]; + CGFloat updateShift = 0.0; + if (keystone) { + // Initiate an update check. + if ([keystone checkForUpdate:self]) { + [self startProgressMessageID:IDS_UPGRADE_CHECK_STARTED]; + } + } else { + // Hide all the update UI + [updateBlock_ setHidden:YES]; + // Figure out the amount we're removing by taking about the update block + // (and it's spacing). + updateShift = NSMinY([legalBlock_ frame]) - NSMinY([updateBlock_ frame]); + } - // Localize the update now button. - NSString* updateNow = base::SysWideToNSString( - l10n_util::GetString(IDS_ABOUT_CHROME_UPDATE_CHECK)); - [updateNowButton_ setStringValue:updateNow]; + // Adjust the sizes/locations. - // TODO(jrg): localize the rest of the elements: - // - "Google Chrome" string at top - // - copyright string at bottom + CGFloat legalShift = + [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:legalBlock_]; - // Initiate an update check. - if ([[self defaultKeystoneGlue] checkForUpdate:self]) - [spinner_ startAnimation:self]; + NSRect rect = [legalBlock_ frame]; + rect.origin.y -= updateShift; + [legalBlock_ setFrame:rect]; + + rect = [backgroundView_ frame]; + rect.origin.y = rect.origin.y - updateShift + legalShift; + [backgroundView_ setFrame:rect]; + + NSWindow* window = [self window]; + [[window contentView] setAutoresizesSubviews:NO]; + rect = [window frame]; + rect.size.height = rect.size.height - updateShift + legalShift; + [window setFrame:rect display:NO]; + [[window contentView] setAutoresizesSubviews:YES]; } - (KeystoneGlue*)defaultKeystoneGlue { return [KeystoneGlue defaultKeystoneGlue]; } +- (void)startProgressMessageID:(uint32_t)messageID { + NSString* message = l10n_util::GetNSStringWithFixup(messageID); + [self startProgressMessage:message]; +} + +- (void)startProgressMessage:(NSString*)message { + [updateStatusIndicator_ setHidden:YES]; + [spinner_ setHidden:NO]; + [spinner_ startAnimation:self]; + + [updateText_ setStringValue:message]; +} + +- (void)stopProgressMessage:(NSString*)message imageID:(uint32_t)imageID { + [spinner_ stopAnimation:self]; + [spinner_ setHidden:YES]; + if (imageID) { + [updateStatusIndicator_ setHidden:NO]; + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + NSImage* statusImage = rb.GetNSImageNamed(imageID); + DCHECK(statusImage); + [updateStatusIndicator_ setImage:statusImage]; + } + + [updateText_ setStringValue:message]; +} + // Callback from KeystoneGlue; implementation of KeystoneGlueCallbacks protocol. // Warning: latest version may be nil if not set in server config. - (void)upToDateCheckCompleted:(BOOL)updatesAvailable latestVersion:(NSString*)latestVersion { - [spinner_ stopAnimation:self]; - - // If an update is available, be sure to enable the "update now" button. - NSString* display = @"No updates are available."; + uint32_t imageID; + NSString* message; if (updatesAvailable) { + newVersionAvailable_.reset([latestVersion copy]); + + // Window UI doesn't put the version number in the string. + imageID = IDR_UPDATE_AVAILABLE; + message = + l10n_util::GetNSStringF(IDS_UPGRADE_AVAILABLE, + l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)); [updateNowButton_ setEnabled:YES]; - // TODO(jrg): IDS_UPGRADE_AVAILABLE seems close but there is no facility - // for specifying a verision in the update string. Do we care? - display = latestVersion ? - [NSString stringWithFormat:@"Version %@ is available for update.", - latestVersion] : - @"A new version is available."; + } else { + // NOTE: This is can be a lie, Keystone does not provide us with an error if + // it was not able to reach the server. So we can't completely map to the + // Windows UI. + + // Keystone does not provide the version number when we are up to date so to + // maintain the UI, we just go fetch our version and call it good. + scoped_ptr<FileVersionInfo> version_info( + FileVersionInfo::CreateFileVersionInfoForCurrentModule()); + std::wstring version(version_info->product_version()); + + // TODO: We really should check to see if what is on disk is newer then what + // is running and report it as such. (Windows has some messages that can + // help with this.) http://crbug.com/13165 + + imageID = IDR_UPDATE_UPTODATE; + message = + l10n_util::GetNSStringF(IDS_UPGRADE_ALREADY_UP_TO_DATE, + l10n_util::GetStringUTF16(IDS_PRODUCT_NAME), + WideToUTF16(version)); } - [upToDate_ setStringValue:display]; + [self stopProgressMessage:message imageID:imageID]; } - (void)windowWillClose:(NSNotification*)notification { @@ -90,32 +334,51 @@ NSString* const kUserClosedAboutNotification = // Callback from KeystoneGlue; implementation of KeystoneGlueCallbacks protocol. - (void)updateCompleted:(BOOL)successful installs:(int)installs { - [spinner_ stopAnimation:self]; - // TODO(jrg): localize. No current string really says this. - NSString* display = ((successful && installs) ? - @"Update completed! Please restart Chrome." : - @"Self update failed."); - [updateCompleted_ setStringValue:display]; - - // Allow a second chance. - if (!(successful && installs)) + uint32_t imageID; + NSString* message; + if (successful && installs) { + imageID = IDR_UPDATE_UPTODATE; + if ([newVersionAvailable_.get() length]) { + message = + l10n_util::GetNSStringF(IDS_UPGRADE_SUCCESSFUL, + l10n_util::GetStringUTF16(IDS_PRODUCT_NAME), + base::SysNSStringToUTF16( + newVersionAvailable_.get())); + } else { + message = + l10n_util::GetNSStringF(IDS_UPGRADE_SUCCESSFUL_NOVERSION, + l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)); + } + + // Tell the user to restart their browser. + restart_browser::RequestRestart(nil); + + } else { + imageID = IDR_UPDATE_FAIL; + message = + l10n_util::GetNSStringF(IDS_UPGRADE_ERROR, + IntToString16(kUpdateInstallFailed)); + + // Allow a second chance. [updateNowButton_ setEnabled:YES]; + } + + [self stopProgressMessage:message imageID:imageID]; } - (IBAction)updateNow:(id)sender { updateTriggered_ = YES; + // Don't let someone click "Update Now" twice! [updateNowButton_ setEnabled:NO]; - [spinner_ startAnimation:self]; if ([[self defaultKeystoneGlue] startUpdate:self]) { // Clear any previous error message from the throbber area. - [updateCompleted_ setStringValue:@""]; - [spinner_ startAnimation:self]; + [self startProgressMessageID:IDS_UPGRADE_STARTED]; } else { - // TODO(jrg): localize. - // IDS_UPGRADE_ERROR doesn't work here; we don't have an error number, and - // "server not available" is too specific. - [updateCompleted_ setStringValue:@"Failed to start updates."]; + NSString* message = + l10n_util::GetNSStringF(IDS_UPGRADE_ERROR, + IntToString16(kUpdateInstallFailedToStart)); + [self stopProgressMessage:message imageID:IDR_UPDATE_FAIL]; } } @@ -123,12 +386,8 @@ NSString* const kUserClosedAboutNotification = return updateNowButton_; } -- (NSTextField*)upToDateTextField { - return upToDate_; -} - -- (NSTextField*)updateCompletedTextField { - return updateCompleted_; +- (NSTextField*)updateText { + return updateText_; } @end |