| Index: chrome/browser/cocoa/about_window_controller.mm
|
| ===================================================================
|
| --- chrome/browser/cocoa/about_window_controller.mm (revision 33240)
|
| +++ chrome/browser/cocoa/about_window_controller.mm (working copy)
|
| @@ -2,16 +2,17 @@
|
| // Use of this source code is governed by a BSD-style license that can be
|
| // found in the LICENSE file.
|
|
|
| +#import "chrome/browser/cocoa/about_window_controller.h"
|
| +
|
| #include "app/l10n_util_mac.h"
|
| #include "app/resource_bundle.h"
|
| #include "base/logging.h"
|
| #include "base/mac_util.h"
|
| #include "base/string_util.h"
|
| #include "base/sys_string_conversions.h"
|
| -#import "chrome/app/keystone_glue.h"
|
| #include "chrome/browser/browser_list.h"
|
| -#import "chrome/browser/cocoa/about_window_controller.h"
|
| #import "chrome/browser/cocoa/background_tile_view.h"
|
| +#import "chrome/browser/cocoa/keystone_glue.h"
|
| #include "chrome/browser/cocoa/restart_browser.h"
|
| #include "grit/chromium_strings.h"
|
| #include "grit/generated_resources.h"
|
| @@ -59,6 +60,15 @@
|
| // 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;
|
| @@ -106,11 +116,11 @@
|
| // 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 and
|
| -// recentShownInstallFailedStatus is NO, the failure needs to be shown instead
|
| -// of launching a new update check. recentShownInstallFailedStatus is
|
| +// 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 recentShownInstallFailedStatus = NO;
|
| +static BOOL recentShownUserActionFailedStatus = NO;
|
|
|
| - (void)awakeFromNib {
|
| NSBundle* bundle = mac_util::MainAppBundle();
|
| @@ -146,7 +156,7 @@
|
| // 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.
|
| + // this build doesn't have Keystone.
|
| NSRect oldLegalRect = [legalBlock_ frame];
|
| [legalText_ sizeToFit];
|
| NSRect newRect = oldLegalRect;
|
| @@ -154,19 +164,39 @@
|
| [legalBlock_ setFrame:newRect];
|
| CGFloat legalShift = newRect.size.height - oldLegalRect.size.height;
|
|
|
| - KeystoneGlue* keystoneGlue = [KeystoneGlue defaultKeystoneGlue];
|
| - CGFloat updateShift;
|
| - if (keystoneGlue) {
|
| + 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] ||
|
| - ([keystoneGlue recentStatus] == kAutoupdateInstallFailed &&
|
| - !recentShownInstallFailedStatus)) {
|
| + 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 a previous update attempt was unsuccessful but no About box was
|
| - // around to report the error, show it now, and allow another chance to
|
| - // install the update.
|
| + // 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
|
| @@ -174,34 +204,174 @@
|
| // in the background since the last time an About box was displayed.
|
| [self checkForUpdate];
|
| }
|
| + }
|
|
|
| - updateShift = 0.0;
|
| + [[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 {
|
| - // Hide all the update UI
|
| - [updateBlock_ setHidden:YES];
|
| + // There is no glue, or the application is on a read-only filesystem.
|
| + // Updates and promotions are impossible.
|
| + allowUpdate = false;
|
| + allowPromotion = false;
|
| + }
|
|
|
| - // Figure out the amount being removed by taking out the update block
|
| - // and its spacing.
|
| - updateShift = NSMinY([legalBlock_ frame]) - NSMinY([updateBlock_ frame]);
|
| + [self setAllowsUpdate:allowUpdate allowsPromotion:allowPromotion];
|
| +}
|
|
|
| - NSRect legalFrame = [legalBlock_ frame];
|
| - legalFrame.origin.y -= updateShift;
|
| - [legalBlock_ setFrame:legalFrame];
|
| +- (void)setAllowsUpdate:(bool)update allowsPromotion:(bool)promotion {
|
| + bool oldUpdate = ![updateBlock_ isHidden];
|
| + bool oldPromotion = ![promotionBlock_ isHidden];
|
| +
|
| + if (promotion == oldPromotion && update == oldUpdate) {
|
| + return;
|
| }
|
|
|
| - NSRect backgroundFrame = [backgroundView_ frame];
|
| - backgroundFrame.origin.y += legalShift - updateShift;
|
| - [backgroundView_ setFrame:backgroundFrame];
|
| + NSRect updateFrame = [updateBlock_ frame];
|
| + NSRect promotionFrame = [promotionBlock_ frame];
|
|
|
| - NSSize windowDelta = NSMakeSize(0.0, legalShift - updateShift);
|
| + // This routine assumes no space between the update and promotion blocks.
|
| + DCHECK_EQ(NSMinY(updateFrame), NSMaxY(promotionFrame));
|
|
|
| - [GTMUILocalizerAndLayoutTweaker
|
| - resizeWindowWithoutAutoResizingSubViews:[self window]
|
| - delta:windowDelta];
|
| -}
|
| + bool promotionOnly = !update && promotion;
|
| + bool oldPromotionOnly = !oldUpdate && oldPromotion;
|
| + if (promotionOnly != oldPromotionOnly) {
|
| + // The window is transitioning from having a promotion block and no update
|
| + // block to any other state, or the other way around. Move the promotion
|
| + // frame up so that its top edge is in the same position as the update
|
| + // frame's top edge, or move it back down to its original location.
|
| + promotionFrame.origin.y += (promotionOnly ? 1.0 : -1.0) *
|
| + NSHeight(updateFrame);
|
| + }
|
|
|
| -- (void)windowWillClose:(NSNotification*)notification {
|
| - [self autorelease];
|
| + CGFloat delta = 0.0;
|
| +
|
| + if (update != oldUpdate) {
|
| + [updateBlock_ setHidden:!update];
|
| + delta += (update ? 1.0 : -1.0) * NSHeight(updateFrame);
|
| + }
|
| +
|
| + if (promotion != oldPromotion) {
|
| + [promotionBlock_ setHidden:!promotion];
|
| + delta += (promotion ? 1.0 : -1.0) * NSHeight(promotionFrame);
|
| + }
|
| +
|
| + NSRect legalFrame = [legalBlock_ frame];
|
| +
|
| + bool updateOrPromotion = update || promotion;
|
| + bool oldUpdateOrPromotion = oldUpdate || oldPromotion;
|
| + if (updateOrPromotion != oldUpdateOrPromotion) {
|
| + // The window is transitioning from having an update or promotion block or
|
| + // both to not having either, or the other way around. Adjust delta to
|
| + // account for the space between the legal block and the update or
|
| + // promotion block. When the update and promotion blocks are not visible,
|
| + // this extra spacing is not used.
|
| + delta += (updateOrPromotion ? 1.0 : -1.0) *
|
| + (NSMinY(legalFrame) - NSMaxY(updateFrame));
|
| + }
|
| +
|
| + // The promotion frame may have changed even if delta is 0, so always reset
|
| + // its frame.
|
| + promotionFrame.origin.y += delta;
|
| + [promotionBlock_ setFrame:promotionFrame];
|
| +
|
| + 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 {
|
| @@ -238,8 +408,6 @@
|
| }
|
|
|
| - (IBAction)updateNow:(id)sender {
|
| - updateTriggered_ = YES;
|
| -
|
| [[KeystoneGlue defaultKeystoneGlue] installUpdate];
|
|
|
| // Immediately, kAutoupdateStatusNotification will be posted, and
|
| @@ -250,8 +418,23 @@
|
| // 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 {
|
| - recentShownInstallFailedStatus = NO;
|
| + recentShownUserActionFailedStatus = NO;
|
|
|
| NSDictionary* dictionary = [notification userInfo];
|
| AutoupdateStatus status = static_cast<AutoupdateStatus>(
|
| @@ -260,14 +443,48 @@
|
| // 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,
|
| + IntToString16(status));
|
| +
|
| + break;
|
| +
|
| case kAutoupdateChecking:
|
| throbber = true;
|
| message = l10n_util::GetNSStringWithFixup(IDS_UPGRADE_CHECK_STARTED);
|
| + enablePromoteButton = false;
|
|
|
| break;
|
|
|
| @@ -284,16 +501,14 @@
|
| imageID = IDR_UPDATE_AVAILABLE;
|
| message = l10n_util::GetNSStringFWithFixup(
|
| IDS_UPGRADE_AVAILABLE, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
|
| - [updateNowButton_ setEnabled:YES];
|
| + enableUpdateButton = true;
|
|
|
| break;
|
|
|
| case kAutoupdateInstalling:
|
| - // Don't let anyone click "Update Now" twice.
|
| - [updateNowButton_ setEnabled:NO];
|
| -
|
| throbber = true;
|
| message = l10n_util::GetNSStringWithFixup(IDS_UPGRADE_STARTED);
|
| + enablePromoteButton = false;
|
|
|
| break;
|
|
|
| @@ -320,15 +535,80 @@
|
|
|
| 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,
|
| + IntToString16(status));
|
| +
|
| + break;
|
| +
|
| + case kAutoupdateRegisterFailed:
|
| + imageID = IDR_UPDATE_FAIL;
|
| + message = l10n_util::GetNSStringFWithFixup(IDS_UPGRADE_ERROR,
|
| + IntToString16(status));
|
| + enablePromoteButton = false;
|
| +
|
| + break;
|
| +
|
| + case kAutoupdateCheckFailed:
|
| + imageID = IDR_UPDATE_FAIL;
|
| + message = l10n_util::GetNSStringFWithFixup(IDS_UPGRADE_ERROR,
|
| + IntToString16(status));
|
| +
|
| + break;
|
| +
|
| case kAutoupdateInstallFailed:
|
| - recentShownInstallFailedStatus = YES;
|
| + recentShownUserActionFailedStatus = YES;
|
|
|
| + imageID = IDR_UPDATE_FAIL;
|
| + message = l10n_util::GetNSStringFWithFixup(IDS_UPGRADE_ERROR,
|
| + IntToString16(status));
|
| +
|
| // Allow another chance.
|
| - [updateNowButton_ setEnabled:YES];
|
| + enableUpdateButton = true;
|
|
|
| - // Fall through.
|
| + break;
|
|
|
| - case kAutoupdateCheckFailed:
|
| + case kAutoupdatePromoteFailed:
|
| + recentShownUserActionFailedStatus = YES;
|
| +
|
| imageID = IDR_UPDATE_FAIL;
|
| message = l10n_util::GetNSStringFWithFixup(IDS_UPGRADE_ERROR,
|
| IntToString16(status));
|
| @@ -337,15 +617,24 @@
|
|
|
| default:
|
| NOTREACHED();
|
| +
|
| return;
|
| }
|
|
|
| - if (throbber) {
|
| - [self setUpdateThrobberMessage:message];
|
| - } else {
|
| - DCHECK_NE(imageID, 0);
|
| - [self setUpdateImage:imageID message:message];
|
| + 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
|
|
|