// // 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 #import "SUConstants.h" @interface WebView (SUTenFiveProperty) -(void) setDrawsBackground: (BOOL)state; @end @implementation SUUpdateAlert - (id)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)aHost { self = [super initWithHost:host windowNibName:@"SUUpdateAlert"]; if (self) { host = [aHost retain]; updateItem = [item retain]; [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], [host bundlePath]]; } - (void)dealloc { [updateItem release]; [host release]; [super dealloc]; } - (void)setVersionDisplayer: (id)disp { versionDisplayer = disp; } - (void)endWithSelection:(SUUpdateAlertChoice)choice { [releaseNotesView stopLoading:self]; [releaseNotesView setFrameLoadDelegate:nil]; [releaseNotesView setPolicyDelegate:nil]; [releaseNotesView removeFromSuperview]; // Otherwise it gets sent Esc presses (why?!) and gets very confused. [self close]; if ([delegate respondsToSelector:@selector(updateAlert:finishedWithChoice:)]) [delegate updateAlert:self finishedWithChoice:choice]; } - (IBAction)installUpdate: (id)sender { [self endWithSelection:SUInstallUpdateChoice]; } - (IBAction)openInfoURL: (id)sender { [self endWithSelection:SUOpenInfoURLChoice]; } - (IBAction)skipThisVersion: (id)sender { [self endWithSelection:SUSkipThisVersionChoice]; } - (IBAction)remindMeLater: (id)sender { [self endWithSelection:SURemindMeLaterChoice]; } - (void)displayReleaseNotes { // Set the default font [releaseNotesView setPreferencesIdentifier:[SPARKLE_BUNDLE bundleIdentifier]]; [[releaseNotesView preferences] setStandardFontFamily:[[NSFont systemFontOfSize:8] familyName]]; [[releaseNotesView preferences] setDefaultFontSize:(int)[NSFont systemFontSizeForControlSize:NSSmallControlSize]]; [releaseNotesView setFrameLoadDelegate:self]; [releaseNotesView setPolicyDelegate:self]; // Stick a nice big spinner in the middle of the web view until the page is loaded. NSRect frame = [[releaseNotesView superview] frame]; releaseNotesSpinner = [[[NSProgressIndicator alloc] initWithFrame:NSMakeRect(NSMidX(frame)-16, NSMidY(frame)-16, 32, 32)] autorelease]; [releaseNotesSpinner setStyle:NSProgressIndicatorSpinningStyle]; [releaseNotesSpinner startAnimation:self]; webViewFinishedLoading = NO; [[releaseNotesView superview] addSubview:releaseNotesSpinner]; // If there's a release notes URL, load it; otherwise, just stick the contents of the description into the web view. if ([updateItem releaseNotesURL]) { if ([[updateItem releaseNotesURL] isFileURL]) { [[releaseNotesView mainFrame] loadHTMLString:@"Release notes with file:// URLs are not supported for security reasons—Javascript would be able to read files on your file system." baseURL:nil]; } else { [[releaseNotesView mainFrame] loadRequest:[NSURLRequest requestWithURL:[updateItem releaseNotesURL] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30]]; } } else { [[releaseNotesView mainFrame] loadHTMLString:[updateItem itemDescription] baseURL:nil]; } } - (BOOL)showsReleaseNotes { NSNumber *shouldShowReleaseNotes = [host objectForInfoDictionaryKey:SUShowReleaseNotesKey]; if (shouldShowReleaseNotes == nil) { // UK 2007-09-18: Don't show release notes if RSS item contains no description and no release notes URL: return( ([updateItem itemDescription] != nil && [[[updateItem itemDescription] stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]] length] > 0) || [updateItem releaseNotesURL] != nil ); } else return [shouldShowReleaseNotes boolValue]; } - (BOOL)allowsAutomaticUpdates { BOOL allowAutoUpdates = YES; // Defaults to YES. if( [host objectForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey] ) allowAutoUpdates = [host boolForInfoDictionaryKey: SUAllowsAutomaticUpdatesKey]; // UK 2007-08-31: Give delegate a chance to modify this choice: if( delegate && [delegate respondsToSelector: @selector(updateAlert:shouldAllowAutoUpdate:)] ) [delegate updateAlert: self shouldAllowAutoUpdate: &allowAutoUpdates]; return allowAutoUpdates; } - (void)awakeFromNib { NSString* sizeStr = [host objectForInfoDictionaryKey:SUFixedHTMLDisplaySizeKey]; if( [host isBackgroundApplication] ) [[self window] setLevel:NSFloatingWindowLevel]; // This means the window will float over all other apps, if our app is switched out ?! UK 2007-09-04 [[self window] setFrameAutosaveName: sizeStr ? @"" : @"SUUpdateAlertFrame"]; // We're gonna do some frame magic to match the window's size to the description field and the presence of the release notes view. NSRect frame = [[self window] frame]; BOOL showReleaseNotes = [self showsReleaseNotes]; // UK 2007-09-18 if (!showReleaseNotes) // UK 2007-09-18 { // Resize the window to be appropriate for not having a huge release notes view. frame.size.height -= [releaseNotesView frame].size.height + 40; // Extra 40 is for the release notes label and margin. if ([self allowsAutomaticUpdates]) frame.size.height += 10; // Make room for the check box. // Hiding the resize handles is not enough on 10.5, you can still click // where they would be, so we set the min/max sizes to be equal to // inhibit resizing completely: [[self window] setShowsResizeIndicator: NO]; [[self window] setMinSize: frame.size]; [[self window] setMaxSize: frame.size]; } else if (![self allowsAutomaticUpdates]) { NSRect boxFrame = [[[releaseNotesView superview] superview] frame]; boxFrame.origin.y -= 20; boxFrame.size.height += 20; [[[releaseNotesView superview] superview] setFrame:boxFrame]; } if( [updateItem fileURL] == nil ) // UK 2007-08-31 (whole if clause) { [installButton setTitle: SULocalizedString( @"Learn More...", @"Alternate title for 'Install Update' button when there's no download in RSS feed." )]; [installButton setAction: @selector(openInfoURL:)]; } // Make sure button widths are OK: #define DISTANCE_BETWEEN_BUTTONS 3 #define DISTANCE_BETWEEN_BUTTON_GROUPS 12 CGFloat minimumWindowWidth = [[self window] frame].size.width -NSMaxX([installButton frame]) +NSMinX([skipButton frame]); // Distance between contents and left/right edge. NSDictionary* attrs = [NSDictionary dictionaryWithObjectsAndKeys: [installButton font], NSFontAttributeName, nil]; NSSize titleSize = [[installButton title] sizeWithAttributes: attrs]; titleSize.width += (16 + 8) * 2; // 16 px for the end caps plus 8 px padding at each end or it'll look as ugly as calling -sizeToFit. NSRect installBtnBox = [installButton frame]; installBtnBox.origin.x += installBtnBox.size.width -titleSize.width; installBtnBox.size.width = titleSize.width; [installButton setFrame: installBtnBox]; minimumWindowWidth += titleSize.width; titleSize = [[laterButton title] sizeWithAttributes: attrs]; titleSize.width += (16 + 8) * 2; // 16 px for the end caps plus 8 px padding at each end or it'll look as ugly as calling -sizeToFit. NSRect laterBtnBox = [installButton frame]; laterBtnBox.origin.x = installBtnBox.origin.x -DISTANCE_BETWEEN_BUTTONS -titleSize.width; laterBtnBox.size.width = titleSize.width; [laterButton setFrame: laterBtnBox]; minimumWindowWidth += DISTANCE_BETWEEN_BUTTONS +titleSize.width; titleSize = [[skipButton title] sizeWithAttributes: attrs]; titleSize.width += (16 + 8) * 2; // 16 px for the end caps plus 8 px padding at each end or it'll look as ugly as calling -sizeToFit. NSRect skipBtnBox = [skipButton frame]; skipBtnBox.size.width = titleSize.width; [skipButton setFrame: skipBtnBox]; minimumWindowWidth += DISTANCE_BETWEEN_BUTTON_GROUPS +titleSize.width; if( showReleaseNotes ) // UK 2007-09-18 (whole block) { if( sizeStr ) { NSSize desiredSize = NSSizeFromString( sizeStr ); NSSize sizeDiff = NSZeroSize; // NSBox* boxView = (NSBox*)[[releaseNotesView superview] superview]; //[boxView setBorderType: NSNoBorder]; [releaseNotesView setDrawsBackground: NO]; sizeDiff.width = desiredSize.width -[releaseNotesView frame].size.width; sizeDiff.height = desiredSize.height -[releaseNotesView frame].size.height; frame.size.width += sizeDiff.width; frame.size.height += sizeDiff.height; // No resizing: [[self window] setShowsResizeIndicator:NO]; [[self window] setMinSize:frame.size]; [[self window] setMaxSize:frame.size]; } } if( frame.size.width < minimumWindowWidth ) frame.size.width = minimumWindowWidth; [[self window] setFrame: frame display: NO]; [[self window] center]; if (showReleaseNotes) // UK 2007-09-18 { [self displayReleaseNotes]; } [[[releaseNotesView superview] superview] setHidden: !showReleaseNotes]; // UK 2007-09-18 } -(BOOL)showsReleaseNotesText { return( [host objectForInfoDictionaryKey:SUFixedHTMLDisplaySizeKey] == nil ); } - (BOOL)windowShouldClose:note { [self endWithSelection:SURemindMeLaterChoice]; return YES; } - (NSImage *)applicationIcon { return [host icon]; } - (NSString *)titleText { return [NSString stringWithFormat:SULocalizedString(@"A new version of %@ is available!", nil), [host name]]; } - (NSString *)descriptionText { NSString *updateItemVersion = [updateItem displayVersionString]; NSString *hostVersion = [host displayVersion]; // Display more info if the version strings are the same; useful for betas. if( !versionDisplayer && [updateItemVersion isEqualToString:hostVersion] ) { updateItemVersion = [updateItemVersion stringByAppendingFormat:@" (%@)", [updateItem versionString]]; hostVersion = [hostVersion stringByAppendingFormat:@" (%@)", [host version]]; } else [versionDisplayer formatVersion: &updateItemVersion andVersion: &hostVersion]; return [NSString stringWithFormat:SULocalizedString(@"%@ %@ is now available--you have %@. Would you like to download it now?", nil), [host name], updateItemVersion, hostVersion]; } - (void)webView:(WebView *)sender didFinishLoadForFrame:frame { if ([frame parentFrame] == nil) { webViewFinishedLoading = YES; [releaseNotesSpinner setHidden:YES]; [sender display]; // necessary to prevent weird scroll bar artifacting } } - (void)webView:sender decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:frame decisionListener:listener { if (webViewFinishedLoading) { [[NSWorkspace sharedWorkspace] openURL:[request URL]]; [listener ignore]; } else { [listener use]; } } // Clean up the contextual menu. - (NSArray *)webView:(WebView *)sender contextMenuItemsForElement:(NSDictionary *)element defaultMenuItems:(NSArray *)defaultMenuItems { NSMutableArray *webViewMenuItems = [[defaultMenuItems mutableCopy] autorelease]; if (webViewMenuItems) { NSEnumerator *itemEnumerator = [defaultMenuItems objectEnumerator]; NSMenuItem *menuItem = nil; while ((menuItem = [itemEnumerator nextObject])) { 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; } - (void)setDelegate:del { delegate = del; } @end