summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/cocoa/about_window_controller.mm
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/ui/cocoa/about_window_controller.mm')
-rw-r--r--chrome/browser/ui/cocoa/about_window_controller.mm761
1 files changed, 761 insertions, 0 deletions
diff --git a/chrome/browser/ui/cocoa/about_window_controller.mm b/chrome/browser/ui/cocoa/about_window_controller.mm
new file mode 100644
index 0000000..f6da6ab
--- /dev/null
+++ b/chrome/browser/ui/cocoa/about_window_controller.mm
@@ -0,0 +1,761 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/browser/ui/cocoa/about_window_controller.h"
+
+#include "app/l10n_util.h"
+#include "app/l10n_util_mac.h"
+#include "app/resource_bundle.h"
+#include "base/logging.h"
+#include "base/mac_util.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/sys_string_conversions.h"
+#include "chrome/browser/browser_list.h"
+#include "chrome/browser/browser_window.h"
+#include "chrome/browser/platform_util.h"
+#import "chrome/browser/ui/cocoa/background_tile_view.h"
+#import "chrome/browser/ui/cocoa/keystone_glue.h"
+#include "chrome/browser/ui/cocoa/restart_browser.h"
+#include "chrome/common/url_constants.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"
+
+namespace {
+
+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];
+ [attr_str addAttribute:NSCursorAttributeName
+ value:[NSCursor pointingHandCursor]
+ range:range];
+}
+
+} // namespace
+
+@interface AboutWindowController(Private)
+
+// Launches a check for available updates.
+- (void)checkForUpdate;
+
+// Turns the update and promotion blocks on and off as needed based on whether
+// updates are possible and promotion is desired or required.
+- (void)adjustUpdateUIVisibility;
+
+// Maintains the update and promotion block visibility and window sizing.
+// This uses bool instead of BOOL for the convenience of the internal
+// implementation.
+- (void)setAllowsUpdate:(bool)update allowsPromotion:(bool)promotion;
+
+// Notification callback, called with the status of asynchronous
+// -checkForUpdate and -updateNow: operations.
+- (void)updateStatus:(NSNotification*)notification;
+
+// These methods maintain the image (or throbber) and text displayed regarding
+// update status. -setUpdateThrobberMessage: starts a progress throbber and
+// sets the text. -setUpdateImage:message: displays an image and sets the
+// text.
+- (void)setUpdateThrobberMessage:(NSString*)message;
+- (void)setUpdateImage:(int)imageID message:(NSString*)message;
+
+@end // @interface AboutWindowController(Private)
+
+@implementation AboutLegalTextView
+
+// Never draw the insertion point (otherwise, it shows up without any user
+// action if full keyboard accessibility is enabled).
+- (BOOL)shouldDrawInsertionPoint {
+ return NO;
+}
+
+@end
+
+@implementation AboutWindowController
+
+- (id)initWithProfile:(Profile*)profile {
+ NSString* nibPath = [mac_util::MainAppBundle() pathForResource:@"About"
+ ofType:@"nib"];
+ if ((self = [super initWithWindowNibPath:nibPath owner:self])) {
+ profile_ = profile;
+ NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
+ [center addObserver:self
+ selector:@selector(updateStatus:)
+ name:kAutoupdateStatusNotification
+ object:nil];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+}
+
+// YES when an About box is currently showing the kAutoupdateInstallFailed
+// status, or if no About box is visible, if the most recent About box to be
+// closed was closed while showing this status. When an About box opens, if
+// the recent status is kAutoupdateInstallFailed or kAutoupdatePromoteFailed
+// and recentShownUserActionFailedStatus is NO, the failure needs to be shown
+// instead of launching a new update check. recentShownInstallFailedStatus is
+// maintained by -updateStatus:.
+static BOOL recentShownUserActionFailedStatus = NO;
+
+- (void)awakeFromNib {
+ NSBundle* bundle = mac_util::MainAppBundle();
+ NSString* chromeVersion =
+ [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
+
+ NSString* versionModifier = @"";
+ NSString* svnRevision = @"";
+ std::string modifier = platform_util::GetVersionStringModifier();
+ if (!modifier.empty())
+ versionModifier = [NSString stringWithFormat:@" %@",
+ base::SysUTF8ToNSString(modifier)];
+
+#if !defined(GOOGLE_CHROME_BUILD)
+ svnRevision = [NSString stringWithFormat:@" (%@)",
+ [bundle objectForInfoDictionaryKey:@"SVNRevision"]];
+#endif
+ // The format string is not localized, but this is how the displayed version
+ // is built on Windows too.
+ NSString* version =
+ [NSString stringWithFormat:@"%@%@%@",
+ chromeVersion, svnRevision, versionModifier];
+
+ [version_ setStringValue:version];
+
+ // Put the two images into the UI.
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ NSImage* backgroundImage = rb.GetNativeImageNamed(IDR_ABOUT_BACKGROUND_COLOR);
+ DCHECK(backgroundImage);
+ [backgroundView_ setTileImage:backgroundImage];
+ NSImage* logoImage = rb.GetNativeImageNamed(IDR_ABOUT_BACKGROUND);
+ DCHECK(logoImage);
+ [logoView_ setImage:logoImage];
+
+ [[legalText_ textStorage] setAttributedString:[[self class] legalTextBlock]];
+
+ // Resize our text view now so that the |updateShift| below is set
+ // correctly. The About box has its controls manually positioned, so we need
+ // to calculate how much larger (or smaller) our text box is and store that
+ // difference in |legalShift|. We do something similar with |updateShift|
+ // below, which is either 0, or the amount of space to offset the window size
+ // because the view that contains the update button has been removed because
+ // this build doesn't have Keystone.
+ NSRect oldLegalRect = [legalBlock_ frame];
+ [legalText_ sizeToFit];
+ NSRect newRect = oldLegalRect;
+ newRect.size.height = [legalText_ frame].size.height;
+ [legalBlock_ setFrame:newRect];
+ CGFloat legalShift = newRect.size.height - oldLegalRect.size.height;
+
+ NSRect backgroundFrame = [backgroundView_ frame];
+ backgroundFrame.origin.y += legalShift;
+ [backgroundView_ setFrame:backgroundFrame];
+
+ NSSize windowDelta = NSMakeSize(0.0, legalShift);
+ [GTMUILocalizerAndLayoutTweaker
+ resizeWindowWithoutAutoResizingSubViews:[self window]
+ delta:windowDelta];
+
+ windowHeight_ = [[self window] frame].size.height;
+
+ [self adjustUpdateUIVisibility];
+
+ // Don't do anything update-related if adjustUpdateUIVisibility decided that
+ // updates aren't possible.
+ if (![updateBlock_ isHidden]) {
+ KeystoneGlue* keystoneGlue = [KeystoneGlue defaultKeystoneGlue];
+ AutoupdateStatus recentStatus = [keystoneGlue recentStatus];
+ if ([keystoneGlue asyncOperationPending] ||
+ recentStatus == kAutoupdateRegisterFailed ||
+ ((recentStatus == kAutoupdateInstallFailed ||
+ recentStatus == kAutoupdatePromoteFailed) &&
+ !recentShownUserActionFailedStatus)) {
+ // If an asynchronous update operation is currently pending, such as a
+ // check for updates or an update installation attempt, set the status
+ // up correspondingly without launching a new update check.
+ //
+ // If registration failed, no other operations make sense, so just go
+ // straight to the error.
+ //
+ // If a previous update or promotion attempt was unsuccessful but no
+ // About box was around to report the error, show it now, and allow
+ // another chance to perform the action.
+ [self updateStatus:[keystoneGlue recentNotification]];
+ } else {
+ // Launch a new update check, even if one was already completed, because
+ // a new update may be available or a new update may have been installed
+ // in the background since the last time an About box was displayed.
+ [self checkForUpdate];
+ }
+ }
+
+ [[self window] center];
+}
+
+- (void)windowWillClose:(NSNotification*)notification {
+ [self autorelease];
+}
+
+- (void)adjustUpdateUIVisibility {
+ bool allowUpdate;
+ bool allowPromotion;
+
+ KeystoneGlue* keystoneGlue = [KeystoneGlue defaultKeystoneGlue];
+ if (keystoneGlue && ![keystoneGlue isOnReadOnlyFilesystem]) {
+ AutoupdateStatus recentStatus = [keystoneGlue recentStatus];
+ if (recentStatus == kAutoupdateRegistering ||
+ recentStatus == kAutoupdateRegisterFailed ||
+ recentStatus == kAutoupdatePromoted) {
+ // Show the update block while registering so that there's a progress
+ // spinner, and if registration failed so that there's an error message.
+ // Show it following a promotion because updates should be possible
+ // after promotion successfully completes.
+ allowUpdate = true;
+
+ // Promotion isn't possible at this point.
+ allowPromotion = false;
+ } else if (recentStatus == kAutoupdatePromoteFailed) {
+ // TODO(mark): Add kAutoupdatePromoting to this block. KSRegistration
+ // currently handles the promotion synchronously, meaning that the main
+ // thread's loop doesn't spin, meaning that animations and other updates
+ // to the window won't occur until KSRegistration is done with
+ // promotion. This looks laggy and bad and probably qualifies as
+ // "jank." For now, there just won't be any visual feedback while
+ // promotion is in progress, but it should complete (or fail) very
+ // quickly. http://b/2290009.
+ //
+ // Also see the TODO for kAutoupdatePromoting in -updateStatus:version:.
+ //
+ // Show the update block so that there's some visual feedback that
+ // promotion is under way or that it's failed. Show the promotion block
+ // because the user either just clicked that button or because the user
+ // should be able to click it again.
+ allowUpdate = true;
+ allowPromotion = true;
+ } else {
+ // Show the update block only if a promotion is not absolutely required.
+ allowUpdate = ![keystoneGlue needsPromotion];
+
+ // Show the promotion block if promotion is a possibility.
+ allowPromotion = [keystoneGlue wantsPromotion];
+ }
+ } else {
+ // There is no glue, or the application is on a read-only filesystem.
+ // Updates and promotions are impossible.
+ allowUpdate = false;
+ allowPromotion = false;
+ }
+
+ [self setAllowsUpdate:allowUpdate allowsPromotion:allowPromotion];
+}
+
+- (void)setAllowsUpdate:(bool)update allowsPromotion:(bool)promotion {
+ bool oldUpdate = ![updateBlock_ isHidden];
+ bool oldPromotion = ![promoteButton_ isHidden];
+
+ if (promotion == oldPromotion && update == oldUpdate) {
+ return;
+ }
+
+ NSRect updateFrame = [updateBlock_ frame];
+ CGFloat delta = 0.0;
+
+ if (update != oldUpdate) {
+ [updateBlock_ setHidden:!update];
+ delta += (update ? 1.0 : -1.0) * NSHeight(updateFrame);
+ }
+
+ if (promotion != oldPromotion) {
+ [promoteButton_ setHidden:!promotion];
+ }
+
+ NSRect legalFrame = [legalBlock_ frame];
+
+ if (delta) {
+ updateFrame.origin.y += delta;
+ [updateBlock_ setFrame:updateFrame];
+
+ legalFrame.origin.y += delta;
+ [legalBlock_ setFrame:legalFrame];
+
+ NSRect backgroundFrame = [backgroundView_ frame];
+ backgroundFrame.origin.y += delta;
+ [backgroundView_ setFrame:backgroundFrame];
+
+ // GTMUILocalizerAndLayoutTweaker resizes the window without any
+ // opportunity for animation. In order to animate, disable window
+ // updates, save the current frame, let GTMUILocalizerAndLayoutTweaker do
+ // its thing, save the desired frame, restore the original frame, and then
+ // animate.
+ NSWindow* window = [self window];
+ [window disableScreenUpdatesUntilFlush];
+
+ NSRect oldFrame = [window frame];
+
+ // GTMUILocalizerAndLayoutTweaker applies its delta to the window's
+ // current size (like oldFrame.size), but oldFrame isn't trustworthy if
+ // an animation is in progress. Set the window's frame to
+ // intermediateFrame, which is a frame of the size that an existing
+ // animation is animating to, so that GTM can apply the delta to the right
+ // size.
+ NSRect intermediateFrame = oldFrame;
+ intermediateFrame.origin.y -= intermediateFrame.size.height - windowHeight_;
+ intermediateFrame.size.height = windowHeight_;
+ [window setFrame:intermediateFrame display:NO];
+
+ NSSize windowDelta = NSMakeSize(0.0, delta);
+ [GTMUILocalizerAndLayoutTweaker
+ resizeWindowWithoutAutoResizingSubViews:window
+ delta:windowDelta];
+ [window setFrameTopLeftPoint:NSMakePoint(NSMinX(intermediateFrame),
+ NSMaxY(intermediateFrame))];
+ NSRect newFrame = [window frame];
+
+ windowHeight_ += delta;
+
+ if (![[self window] isVisible]) {
+ // Don't animate if the window isn't on screen yet.
+ [window setFrame:newFrame display:NO];
+ } else {
+ [window setFrame:oldFrame display:NO];
+ [window setFrame:newFrame display:YES animate:YES];
+ }
+ }
+}
+
+- (void)setUpdateThrobberMessage:(NSString*)message {
+ [updateStatusIndicator_ setHidden:YES];
+
+ [spinner_ setHidden:NO];
+ [spinner_ startAnimation:self];
+
+ [updateText_ setStringValue:message];
+}
+
+- (void)setUpdateImage:(int)imageID message:(NSString*)message {
+ [spinner_ stopAnimation:self];
+ [spinner_ setHidden:YES];
+
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ NSImage* statusImage = rb.GetNativeImageNamed(imageID);
+ DCHECK(statusImage);
+ [updateStatusIndicator_ setImage:statusImage];
+ [updateStatusIndicator_ setHidden:NO];
+
+ [updateText_ setStringValue:message];
+}
+
+- (void)checkForUpdate {
+ [[KeystoneGlue defaultKeystoneGlue] checkForUpdate];
+
+ // Immediately, kAutoupdateStatusNotification will be posted, and
+ // -updateStatus: will be called with status kAutoupdateChecking.
+ //
+ // Upon completion, kAutoupdateStatusNotification will be posted, and
+ // -updateStatus: will be called with a status indicating the result of the
+ // check.
+}
+
+- (IBAction)updateNow:(id)sender {
+ [[KeystoneGlue defaultKeystoneGlue] installUpdate];
+
+ // Immediately, kAutoupdateStatusNotification will be posted, and
+ // -updateStatus: will be called with status kAutoupdateInstalling.
+ //
+ // Upon completion, kAutoupdateStatusNotification will be posted, and
+ // -updateStatus: will be called with a status indicating the result of the
+ // installation attempt.
+}
+
+- (IBAction)promoteUpdater:(id)sender {
+ [[KeystoneGlue defaultKeystoneGlue] promoteTicket];
+
+ // Immediately, kAutoupdateStatusNotification will be posted, and
+ // -updateStatus: will be called with status kAutoupdatePromoting.
+ //
+ // Upon completion, kAutoupdateStatusNotification will be posted, and
+ // -updateStatus: will be called with a status indicating a result of the
+ // installation attempt.
+ //
+ // If the promotion was successful, KeystoneGlue will re-register the ticket
+ // and -updateStatus: will be called again indicating first that
+ // registration is in progress and subsequently that it has completed.
+}
+
+- (void)updateStatus:(NSNotification*)notification {
+ recentShownUserActionFailedStatus = NO;
+
+ NSDictionary* dictionary = [notification userInfo];
+ AutoupdateStatus status = static_cast<AutoupdateStatus>(
+ [[dictionary objectForKey:kAutoupdateStatusStatus] intValue]);
+
+ // Don't assume |version| is a real string. It may be nil.
+ NSString* version = [dictionary objectForKey:kAutoupdateStatusVersion];
+
+ bool updateMessage = true;
+ bool throbber = false;
+ int imageID = 0;
+ NSString* message;
+ bool enableUpdateButton = false;
+ bool enablePromoteButton = true;
+
+ switch (status) {
+ case kAutoupdateRegistering:
+ // When registering, use the "checking" message. The check will be
+ // launched if appropriate immediately after registration.
+ throbber = true;
+ message = l10n_util::GetNSStringWithFixup(IDS_UPGRADE_CHECK_STARTED);
+ enablePromoteButton = false;
+
+ break;
+
+ case kAutoupdateRegistered:
+ // Once registered, the ability to update and promote is known.
+ [self adjustUpdateUIVisibility];
+
+ if (![updateBlock_ isHidden]) {
+ // If registration completes while the window is visible, go straight
+ // into an update check. Return immediately, this routine will be
+ // re-entered shortly with kAutoupdateChecking.
+ [self checkForUpdate];
+ return;
+ }
+
+ // Nothing actually failed, but updates aren't possible. The throbber
+ // and message are hidden, but they'll be reset to these dummy values
+ // just to get the throbber to stop spinning if it's running.
+ imageID = IDR_UPDATE_FAIL;
+ message = l10n_util::GetNSStringFWithFixup(IDS_UPGRADE_ERROR,
+ base::IntToString16(status));
+
+ break;
+
+ case kAutoupdateChecking:
+ throbber = true;
+ message = l10n_util::GetNSStringWithFixup(IDS_UPGRADE_CHECK_STARTED);
+ enablePromoteButton = false;
+
+ break;
+
+ case kAutoupdateCurrent:
+ imageID = IDR_UPDATE_UPTODATE;
+ message = l10n_util::GetNSStringFWithFixup(
+ IDS_UPGRADE_ALREADY_UP_TO_DATE,
+ l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
+ base::SysNSStringToUTF16(version));
+
+ break;
+
+ case kAutoupdateAvailable:
+ imageID = IDR_UPDATE_AVAILABLE;
+ message = l10n_util::GetNSStringFWithFixup(
+ IDS_UPGRADE_AVAILABLE, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
+ enableUpdateButton = true;
+
+ break;
+
+ case kAutoupdateInstalling:
+ throbber = true;
+ message = l10n_util::GetNSStringWithFixup(IDS_UPGRADE_STARTED);
+ enablePromoteButton = false;
+
+ break;
+
+ case kAutoupdateInstalled:
+ {
+ imageID = IDR_UPDATE_UPTODATE;
+ string16 productName = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME);
+ if (version) {
+ message = l10n_util::GetNSStringFWithFixup(
+ IDS_UPGRADE_SUCCESSFUL,
+ productName,
+ base::SysNSStringToUTF16(version));
+ } else {
+ message = l10n_util::GetNSStringFWithFixup(
+ IDS_UPGRADE_SUCCESSFUL_NOVERSION, productName);
+ }
+
+ // TODO(mark): Turn the button in the dialog into a restart button
+ // instead of springing this sheet or dialog.
+ NSWindow* window = [self window];
+ NSWindow* restartDialogParent = [window isVisible] ? window : nil;
+ restart_browser::RequestRestart(restartDialogParent);
+ }
+
+ break;
+
+ case kAutoupdatePromoting:
+#if 1
+ // TODO(mark): See the TODO in -adjustUpdateUIVisibility for an
+ // explanation of why nothing can be done here at the moment. When
+ // KSRegistration handles promotion asynchronously, this dummy block can
+ // be replaced with the #else block. For now, just leave the messaging
+ // alone. http://b/2290009.
+ updateMessage = false;
+#else
+ // The visibility may be changing.
+ [self adjustUpdateUIVisibility];
+
+ // This is not a terminal state, and kAutoupdatePromoted or
+ // kAutoupdatePromoteFailed will follow. Use the throbber and
+ // "checking" message so that it looks like something's happening.
+ throbber = true;
+ message = l10n_util::GetNSStringWithFixup(IDS_UPGRADE_CHECK_STARTED);
+#endif
+
+ enablePromoteButton = false;
+
+ break;
+
+ case kAutoupdatePromoted:
+ // The visibility may be changing.
+ [self adjustUpdateUIVisibility];
+
+ if (![updateBlock_ isHidden]) {
+ // If promotion completes while the window is visible, go straight
+ // into an update check. Return immediately, this routine will be
+ // re-entered shortly with kAutoupdateChecking.
+ [self checkForUpdate];
+ return;
+ }
+
+ // Nothing actually failed, but updates aren't possible. The throbber
+ // and message are hidden, but they'll be reset to these dummy values
+ // just to get the throbber to stop spinning if it's running.
+ imageID = IDR_UPDATE_FAIL;
+ message = l10n_util::GetNSStringFWithFixup(IDS_UPGRADE_ERROR,
+ base::IntToString16(status));
+
+ break;
+
+ case kAutoupdateRegisterFailed:
+ imageID = IDR_UPDATE_FAIL;
+ message = l10n_util::GetNSStringFWithFixup(IDS_UPGRADE_ERROR,
+ base::IntToString16(status));
+ enablePromoteButton = false;
+
+ break;
+
+ case kAutoupdateCheckFailed:
+ imageID = IDR_UPDATE_FAIL;
+ message = l10n_util::GetNSStringFWithFixup(IDS_UPGRADE_ERROR,
+ base::IntToString16(status));
+
+ break;
+
+ case kAutoupdateInstallFailed:
+ recentShownUserActionFailedStatus = YES;
+
+ imageID = IDR_UPDATE_FAIL;
+ message = l10n_util::GetNSStringFWithFixup(IDS_UPGRADE_ERROR,
+ base::IntToString16(status));
+
+ // Allow another chance.
+ enableUpdateButton = true;
+
+ break;
+
+ case kAutoupdatePromoteFailed:
+ recentShownUserActionFailedStatus = YES;
+
+ imageID = IDR_UPDATE_FAIL;
+ message = l10n_util::GetNSStringFWithFixup(IDS_UPGRADE_ERROR,
+ base::IntToString16(status));
+
+ break;
+
+ default:
+ NOTREACHED();
+
+ return;
+ }
+
+ if (updateMessage) {
+ if (throbber) {
+ [self setUpdateThrobberMessage:message];
+ } else {
+ DCHECK_NE(imageID, 0);
+ [self setUpdateImage:imageID message:message];
+ }
+ }
+
+ // Note that these buttons may be hidden depending on what
+ // -adjustUpdateUIVisibility did. Their enabled/disabled status doesn't
+ // necessarily have anything to do with their visibility.
+ [updateNowButton_ setEnabled:enableUpdateButton];
+ [promoteButton_ setEnabled:enablePromoteButton];
+}
+
+- (BOOL)textView:(NSTextView *)aTextView
+ clickedOnLink:(id)link
+ atIndex:(NSUInteger)charIndex {
+ // We always create a new window, so there's no need to try to re-use
+ // an existing one just to pass in the NEW_WINDOW disposition.
+ Browser* browser = Browser::Create(profile_);
+ browser->OpenURL(GURL([link UTF8String]), GURL(), NEW_FOREGROUND_TAB,
+ PageTransition::LINK);
+ browser->window()->Show();
+ return YES;
+}
+
+- (NSTextView*)legalText {
+ return legalText_;
+}
+
+- (NSButton*)updateButton {
+ return updateNowButton_;
+}
+
+- (NSTextField*)updateText {
+ return updateText_;
+}
+
++ (NSAttributedString*)legalTextBlock {
+ // 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::GetNSStringWithFixup(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 =
+ [NSString stringWithUTF8String:chrome::kAboutCreditsURL];
+
+ // Now fetch the license string and deal with the markers
+
+ NSString* license =
+ l10n_util::GetNSStringWithFixup(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_NE(begin_chr.location, NSNotFound);
+ DCHECK_NE(begin_oss.location, NSNotFound);
+ DCHECK_NE(end_chr.location, NSNotFound);
+ DCHECK_NE(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 = [NSString stringWithUTF8String:chrome::kAboutTermsURL];
+ // Following Windows. 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;
+ NSString* about_terms = l10n_util::GetNSStringF(IDS_ABOUT_TERMS_OF_SERVICE,
+ string16(),
+ string16(),
+ &url_offsets);
+ DCHECK_EQ(url_offsets.size(), 1U);
+ NSString* terms_link_text =
+ l10n_util::GetNSStringWithFixup(IDS_TERMS_OF_SERVICE);
+
+ AttributedStringAppendString(legal_block, @"\n\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 // GOOGLE_CHROME_BUILD
+
+ // We need to explicitly select Lucida Grande because once we click on
+ // the NSTextView, it changes to Helvetica 12 otherwise.
+ NSRange string_range = NSMakeRange(0, [legal_block length]);
+ [legal_block addAttribute:NSFontAttributeName
+ value:[NSFont labelFontOfSize:11]
+ range:string_range];
+
+ [legal_block endEditing];
+ return legal_block;
+}
+
+@end // @implementation AboutWindowController