Cog/Frameworks/Sparkle/Sparkle/SUUpdateAlert.m
2014-09-03 09:47:40 -07:00

283 lines
9.7 KiB
Objective-C

//
// SUUpdateAlert.m
// Sparkle
//
// Created by Andy Matuschak on 3/12/06.
// Copyright 2006 Andy Matuschak. All rights reserved.
//
// -----------------------------------------------------------------------------
// Headers:
// -----------------------------------------------------------------------------
#import "SUUpdateAlert.h"
#import "SUHost.h"
#import <WebKit/WebKit.h>
#import "SUConstants.h"
@interface SUUpdateAlert ()
@property (strong) SUAppcastItem *updateItem;
@property (strong) SUHost *host;
@property (strong) NSProgressIndicator *releaseNotesSpinner;
@property (assign) BOOL webViewFinishedLoading;
@property (weak) IBOutlet WebView *releaseNotesView;
@property (weak) IBOutlet NSView *releaseNotesContainerView;
@property (weak) IBOutlet NSTextField *descriptionField;
@property (weak) IBOutlet NSButton *installButton;
@property (weak) IBOutlet NSButton *skipButton;
@property (weak) IBOutlet NSButton *laterButton;
@end
@implementation SUUpdateAlert
@synthesize delegate;
@synthesize versionDisplayer;
@synthesize updateItem;
@synthesize host;
@synthesize releaseNotesSpinner;
@synthesize webViewFinishedLoading;
@synthesize releaseNotesView;
@synthesize releaseNotesContainerView;
@synthesize descriptionField;
@synthesize installButton;
@synthesize skipButton;
@synthesize laterButton;
- (instancetype)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)aHost
{
self = [super initWithHost:host windowNibName:@"SUUpdateAlert"];
if (self)
{
host = aHost;
updateItem = item;
[self setShouldCascadeWindows:NO];
// Alex: This dummy line makes sure that the binary is linked against WebKit.
// The SUUpdateAlert.xib file contains a WebView and if we don't link against WebKit,
// we will get a runtime crash when decoding the NIB. It is better to get a link error.
[WebView MIMETypesShownAsHTML];
}
return self;
}
- (NSString *)description { return [NSString stringWithFormat:@"%@ <%@>", [self class], [self.host bundlePath]]; }
- (void)endWithSelection:(SUUpdateAlertChoice)choice
{
[self.releaseNotesView stopLoading:self];
[self.releaseNotesView setFrameLoadDelegate:nil];
[self.releaseNotesView setPolicyDelegate:nil];
[self.releaseNotesView removeFromSuperview]; // Otherwise it gets sent Esc presses (why?!) and gets very confused.
[self close];
if ([self.delegate respondsToSelector:@selector(updateAlert:finishedWithChoice:)])
[self.delegate updateAlert:self finishedWithChoice:choice];
}
- (IBAction)installUpdate:(id)__unused sender
{
[self endWithSelection:SUInstallUpdateChoice];
}
- (IBAction)openInfoURL:(id)__unused sender
{
[self endWithSelection:SUOpenInfoURLChoice];
}
- (IBAction)skipThisVersion:(id)__unused sender
{
[self endWithSelection:SUSkipThisVersionChoice];
}
- (IBAction)remindMeLater:(id)__unused sender
{
[self endWithSelection:SURemindMeLaterChoice];
}
- (void)displayReleaseNotes
{
// Set the default font
[self.releaseNotesView setPreferencesIdentifier:SUBundleIdentifier];
WebPreferences *prefs = [self.releaseNotesView preferences];
NSString *familyName = [[NSFont systemFontOfSize:8] familyName];
if ([familyName hasPrefix:@"."]) { // 10.9 returns ".Lucida Grande UI", which isn't a valid name for the WebView
familyName = @"Lucida Grande";
}
[prefs setStandardFontFamily:familyName];
[prefs setDefaultFontSize:(int)[NSFont systemFontSizeForControlSize:NSSmallControlSize]];
[prefs setPlugInsEnabled:NO];
[self.releaseNotesView setFrameLoadDelegate:self];
[self.releaseNotesView setPolicyDelegate:self];
// Stick a nice big spinner in the middle of the web view until the page is loaded.
NSRect frame = [[self.releaseNotesView superview] frame];
self.releaseNotesSpinner = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(NSMidX(frame) - 16, NSMidY(frame) - 16, 32, 32)];
[self.releaseNotesSpinner setStyle:NSProgressIndicatorSpinningStyle];
[self.releaseNotesSpinner startAnimation:self];
self.webViewFinishedLoading = NO;
[[self.releaseNotesView superview] addSubview:self.releaseNotesSpinner];
// If there's a release notes URL, load it; otherwise, just stick the contents of the description into the web view.
if ([self.updateItem releaseNotesURL])
{
if ([[self.updateItem releaseNotesURL] isFileURL])
{
[[self.releaseNotesView mainFrame] loadHTMLString:@"Release notes with file:// URLs are not supported for security reasons&mdash;Javascript would be able to read files on your file system." baseURL:nil];
}
else
{
[[self.releaseNotesView mainFrame] loadRequest:[NSURLRequest requestWithURL:[self.updateItem releaseNotesURL] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30]];
}
}
else
{
[[self.releaseNotesView mainFrame] loadHTMLString:[self.updateItem itemDescription] baseURL:nil];
}
}
- (BOOL)showsReleaseNotes
{
NSNumber *shouldShowReleaseNotes = [self.host objectForInfoDictionaryKey:SUShowReleaseNotesKey];
if (shouldShowReleaseNotes == nil)
{
// Don't show release notes if RSS item contains no description and no release notes URL:
return (([self.updateItem itemDescription] != nil
&& [[[self.updateItem itemDescription] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length] > 0)
|| [self.updateItem releaseNotesURL] != nil);
}
else
return [shouldShowReleaseNotes boolValue];
}
- (BOOL)allowsAutomaticUpdates
{
BOOL allowAutoUpdates = YES; // Defaults to YES.
if ([self.host objectForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey])
allowAutoUpdates = [self.host boolForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey];
// Give delegate a chance to modify this choice:
if (self.delegate && [self.delegate respondsToSelector:@selector(updateAlert:shouldAllowAutoUpdate:)])
[self.delegate updateAlert:self shouldAllowAutoUpdate:&allowAutoUpdates];
return allowAutoUpdates;
}
- (void)awakeFromNib
{
BOOL showReleaseNotes = [self showsReleaseNotes];
[self.window setFrameAutosaveName: showReleaseNotes ? @"SUUpdateAlert" : @"SUUpdateAlertSmall" ];
if ([self.host isBackgroundApplication]) {
[self.window setLevel:NSFloatingWindowLevel]; // This means the window will float over all other apps, if our app is switched out ?!
}
if ([self.updateItem fileURL] == nil) {
[self.installButton setTitle:SULocalizedString(@"Learn More...", @"Alternate title for 'Install Update' button when there's no download in RSS feed.")];
[self.installButton setAction:@selector(openInfoURL:)];
}
if (showReleaseNotes) {
[self displayReleaseNotes];
} else {
[self.releaseNotesContainerView removeFromSuperview];
}
[self.window.contentView setNeedsLayout:YES]; // Prod autolayout to place everything
[self.window center];
}
- (BOOL)windowShouldClose:(NSNotification *) __unused note
{
[self endWithSelection:SURemindMeLaterChoice];
return YES;
}
- (NSImage *)applicationIcon
{
return [self.host icon];
}
- (NSString *)titleText
{
return [NSString stringWithFormat:SULocalizedString(@"A new version of %@ is available!", nil), [self.host name]];
}
- (NSString *)descriptionText
{
NSString *updateItemVersion = [self.updateItem displayVersionString];
NSString *hostVersion = [self.host displayVersion];
// Display more info if the version strings are the same; useful for betas.
if (!self.versionDisplayer && [updateItemVersion isEqualToString:hostVersion] )
{
updateItemVersion = [updateItemVersion stringByAppendingFormat:@" (%@)", [self.updateItem versionString]];
hostVersion = [hostVersion stringByAppendingFormat:@" (%@)", [self.host version]];
}
else {
[self.versionDisplayer formatVersion:&updateItemVersion andVersion:&hostVersion];
}
return [NSString stringWithFormat:SULocalizedString(@"%@ %@ is now available--you have %@. Would you like to download it now?", nil), [self.host name], updateItemVersion, hostVersion];
}
- (void)webView:(WebView *)sender didFinishLoadForFrame:frame
{
if ([frame parentFrame] == nil) {
self.webViewFinishedLoading = YES;
[self.releaseNotesSpinner setHidden:YES];
[sender display]; // necessary to prevent weird scroll bar artifacting
}
}
- (void)webView:(WebView *)__unused sender decidePolicyForNavigationAction:(NSDictionary *)__unused actionInformation request:(NSURLRequest *)request frame:(WebFrame *)__unused frame decisionListener:(id<WebPolicyDecisionListener>)listener
{
if (self.webViewFinishedLoading) {
[[NSWorkspace sharedWorkspace] openURL:[request URL]];
[listener ignore];
}
else {
[listener use];
}
}
// Clean up the contextual menu.
- (NSArray *)webView:(WebView *)__unused sender contextMenuItemsForElement:(NSDictionary *)__unused element defaultMenuItems:(NSArray *)defaultMenuItems
{
NSMutableArray *webViewMenuItems = [defaultMenuItems mutableCopy];
if (webViewMenuItems)
{
for (NSMenuItem *menuItem in defaultMenuItems)
{
NSInteger tag = [menuItem tag];
switch (tag)
{
case WebMenuItemTagOpenLinkInNewWindow:
case WebMenuItemTagDownloadLinkToDisk:
case WebMenuItemTagOpenImageInNewWindow:
case WebMenuItemTagDownloadImageToDisk:
case WebMenuItemTagOpenFrameInNewWindow:
case WebMenuItemTagGoBack:
case WebMenuItemTagGoForward:
case WebMenuItemTagStop:
case WebMenuItemTagReload:
[webViewMenuItems removeObjectIdenticalTo:menuItem];
}
}
}
return webViewMenuItems;
}
@end