Plugins/Displays/WebKit/GrowlWebKitWindowController.m
author boredzo
Sun Jul 06 15:10:15 2008 +0000 (2008-07-06)
changeset 4135 9d0747a53f45
parent 4063 ffdf2e948e91
child 4167 fbb86d40b0af
child 4771 d398be175a6e
permissions -rw-r--r--
CFRelease the duration preference value if we get one. Found by the clang static analyzer.
boredzo@2402
     1
//
boredzo@2402
     2
//  GrowlWebKitWindowController.m
boredzo@2402
     3
//  Growl
boredzo@2402
     4
//
boredzo@2402
     5
//  Created by Ingmar Stein on Thu Apr 14 2005.
ingmarstein@3040
     6
//  Copyright 2005-2006 The Growl Project. All rights reserved.
boredzo@2402
     7
//
boredzo@2402
     8
boredzo@2402
     9
#import "GrowlWebKitWindowController.h"
boredzo@2402
    10
#import "GrowlWebKitWindowView.h"
boredzo@2402
    11
#import "GrowlWebKitPrefsController.h"
boredzo@2402
    12
#import "GrowlWebKitDefines.h"
boredzo@2402
    13
#import "NSWindow+Transforms.h"
boredzo@2402
    14
#import "GrowlPluginController.h"
boredzo@2429
    15
#import "NSViewAdditions.h"
boredzo@2402
    16
#import "GrowlDefines.h"
ingmarstein@2470
    17
#import "GrowlPathUtilities.h"
ingmarstein@2699
    18
#import "GrowlApplicationNotification.h"
ingmarstein@2632
    19
#include "CFGrowlAdditions.h"
ingmarstein@2641
    20
#include "CFDictionaryAdditions.h"
ingmarstein@2627
    21
#include "CFMutableStringAdditions.h"
eridius@2773
    22
#import "GrowlNotificationDisplayBridge.h"
eridius@2773
    23
#import "GrowlDisplayPlugin.h"
ingmarstein@3037
    24
#import "GrowlFadingWindowTransition.h"
boredzo@2402
    25
ingmarstein@2711
    26
/*
ingmarstein@2711
    27
 * A panel that always pretends to be the key window.
ingmarstein@2711
    28
 */
ingmarstein@2711
    29
@interface KeyPanel : NSPanel {
boredzo@2402
    30
}
boredzo@2402
    31
@end
boredzo@2402
    32
ingmarstein@2711
    33
@implementation KeyPanel
ingmarstein@2711
    34
- (BOOL) isKeyWindow {
boredzo@2402
    35
	return YES;
boredzo@2402
    36
}
boredzo@2402
    37
@end
boredzo@2402
    38
evands@4035
    39
@interface NSData (Base64Additions)
evands@4035
    40
- (NSString *)base64Encoding;
evands@4035
    41
@end
evands@4035
    42
evands@4035
    43
@interface NSImage (PNGRepAddition)
evands@4035
    44
- (NSData *)PNGRepresentation;
evands@4035
    45
@end
evands@4035
    46
boredzo@2402
    47
@implementation GrowlWebKitWindowController
boredzo@2402
    48
evands@3636
    49
#define GrowlWebKitDurationPrefDefault				4.0
boredzo@2402
    50
#define ADDITIONAL_LINES_DISPLAY_TIME	0.5
boredzo@2402
    51
#define MAX_DISPLAY_TIME				10.0
boredzo@2402
    52
#define GrowlWebKitPadding				5.0f
boredzo@2402
    53
boredzo@2402
    54
#pragma mark -
boredzo@2402
    55
eridius@2773
    56
- (id) initWithBridge:(GrowlNotificationDisplayBridge *)displayBridge {
eridius@2773
    57
	// init the window used to init
ingmarstein@2711
    58
	NSPanel *panel = [[KeyPanel alloc] initWithContentRect:NSMakeRect(0.0f, 0.0f, 270.0f, 1.0f)
ingmarstein@2711
    59
												 styleMask:NSBorderlessWindowMask | NSNonactivatingPanelMask
ingmarstein@2711
    60
												   backing:NSBackingStoreBuffered
rudy@3005
    61
													 defer:YES];
ingmarstein@3036
    62
	if (!(self = [super initWithWindow:panel]))
eridius@2773
    63
		return nil;
ingmarstein@2943
    64
ingmarstein@3036
    65
	GrowlDisplayPlugin *plugin = [displayBridge display];
ingmarstein@3036
    66
eridius@2773
    67
	// Read the template file....exit on error...
eridius@2773
    68
	NSError *error = nil;
ingmarstein@3036
    69
	NSBundle *displayBundle = [plugin bundle];
ingmarstein@3035
    70
	NSString *templateFile = [displayBundle pathForResource:@"template" ofType:@"html"];
ingmarstein@3036
    71
	if (![[NSFileManager defaultManager] fileExistsAtPath:templateFile])
ingmarstein@3036
    72
		templateFile = [[NSBundle mainBundle] pathForResource:@"template" ofType:@"html"];
eridius@2773
    73
	templateHTML = [[NSString alloc] initWithContentsOfFile:templateFile
eridius@2773
    74
												   encoding:NSUTF8StringEncoding
eridius@2773
    75
													  error:&error];
ingmarstein@2943
    76
	if (!templateHTML) {
eridius@2773
    77
		NSLog(@"ERROR: could not read template '%@' - %@", templateFile,error);
eridius@2773
    78
		[self release];
eridius@2773
    79
		return nil;
eridius@2773
    80
	}
evands@3576
    81
	baseURL = [[NSURL fileURLWithPath:[displayBundle resourcePath]] retain];
ingmarstein@2943
    82
eridius@2773
    83
	// Read the prefs for the plugin...
eridius@2773
    84
	unsigned theScreenNo = 0U;
eridius@2773
    85
	READ_GROWL_PREF_INT(GrowlWebKitScreenPref, [plugin prefDomain], &theScreenNo);
eridius@2773
    86
	[self setScreenNumber:theScreenNo];
ingmarstein@2943
    87
evands@3636
    88
	CFNumberRef prefsDuration = NULL;
evands@3638
    89
	READ_GROWL_PREF_VALUE(GrowlWebKitDurationPref, [plugin prefDomain], CFNumberRef, &prefsDuration);
evands@3636
    90
	[self setDisplayDuration:(prefsDuration ?
evands@3636
    91
							  [(NSNumber *)prefsDuration doubleValue] :
evands@3636
    92
							  GrowlWebKitDurationPrefDefault)];
boredzo@4135
    93
	if (prefsDuration) CFRelease(prefsDuration);
evands@3636
    94
	
eridius@2773
    95
	// Read the plugin specifics from the info.plist
eridius@2773
    96
	NSDictionary *styleInfo = [[plugin bundle] infoDictionary];
eridius@2773
    97
	BOOL hasShadow = NO;
eridius@2773
    98
	hasShadow =	[(NSNumber *)[styleInfo valueForKey:@"GrowlHasShadow"] boolValue];
eridius@2773
    99
	paddingX = GrowlWebKitPadding;
eridius@2773
   100
	paddingY = GrowlWebKitPadding;
eridius@2773
   101
	NSNumber *xPad = [styleInfo valueForKey:@"GrowlPaddingX"];
eridius@2773
   102
	NSNumber *yPad = [styleInfo valueForKey:@"GrowlPaddingY"];
eridius@2773
   103
	if (xPad)
eridius@2773
   104
		paddingX = [xPad floatValue];
eridius@2773
   105
	if (yPad)
eridius@2773
   106
		paddingY = [yPad floatValue];
ingmarstein@2943
   107
eridius@2773
   108
	// Configure the window
boredzo@2402
   109
	[panel setBecomesKeyOnlyIfNeeded:YES];
boredzo@2402
   110
	[panel setHidesOnDeactivate:NO];
boredzo@2402
   111
	[panel setBackgroundColor:[NSColor clearColor]];
boredzo@2402
   112
	[panel setLevel:NSStatusWindowLevel];
boredzo@2402
   113
	[panel setSticky:YES];
boredzo@2402
   114
	[panel setAlphaValue:0.0f];
boredzo@2402
   115
	[panel setOpaque:NO];
boredzo@2402
   116
	[panel setCanHide:NO];
boredzo@2402
   117
	[panel setOneShot:YES];
boredzo@2402
   118
	[panel useOptimizedDrawing:YES];
boredzo@2402
   119
	[panel disableCursorRects];
eridius@2773
   120
	[panel setHasShadow:hasShadow];
evands@3502
   121
	[panel setDelegate:self];
ingmarstein@2943
   122
eridius@2773
   123
	// Configure the view
eridius@2773
   124
	NSRect panelFrame = [panel frame];
boredzo@2402
   125
	GrowlWebKitWindowView *view = [[GrowlWebKitWindowView alloc] initWithFrame:panelFrame
boredzo@2402
   126
																	 frameName:nil
boredzo@2402
   127
																	 groupName:nil];
boredzo@2402
   128
	[view setMaintainsBackForwardList:NO];
boredzo@2402
   129
	[view setTarget:self];
boredzo@2402
   130
	[view setAction:@selector(notificationClicked:)];
boredzo@2402
   131
	[view setPolicyDelegate:self];
boredzo@2402
   132
	[view setFrameLoadDelegate:self];
boredzo@2402
   133
	if ([view respondsToSelector:@selector(setDrawsBackground:)])
boredzo@2402
   134
		[view setDrawsBackground:NO];
boredzo@2402
   135
	[panel setContentView:view];
ingmarstein@2711
   136
	[panel makeFirstResponder:[[[view mainFrame] frameView] documentView]];
evands@4032
   137
	[view release];
evands@4032
   138
ingmarstein@3036
   139
	[self setBridge:displayBridge];
ingmarstein@2943
   140
ingmarstein@3037
   141
	// set up the transitions...
ingmarstein@3037
   142
	GrowlFadingWindowTransition *fader = [[GrowlFadingWindowTransition alloc] initWithWindow:panel];
ingmarstein@3037
   143
	[self addTransition:fader];
ingmarstein@3037
   144
	[self setStartPercentage:0 endPercentage:100 forTransition:fader];
ingmarstein@3037
   145
	[fader setAutoReverses:YES];
ingmarstein@3037
   146
	[fader release];
evands@4032
   147
evands@3543
   148
	[panel release];
evands@3543
   149
boredzo@2402
   150
	return self;
boredzo@2402
   151
}
boredzo@2402
   152
boredzo@2411
   153
- (void) dealloc {
evands@3645
   154
	GrowlWebKitWindowView *webView = [[self window] contentView];
eridius@2773
   155
	[webView      setPolicyDelegate:nil];
eridius@2773
   156
	[webView      setFrameLoadDelegate:nil];
evands@3645
   157
	[webView      setTarget:nil];
evands@4034
   158
eridius@2773
   159
	[templateHTML release];
evands@3576
   160
	[baseURL	  release];
evands@3576
   161
	
boredzo@2411
   162
	[super dealloc];
boredzo@2411
   163
}
boredzo@2411
   164
bgannin@3404
   165
- (void) setTitle:(NSString *)title text:(NSString *)text icon:(NSImage *)icon priority:(int)priority forView:(WebView *)view {
ingmarstein@2627
   166
	CFStringRef priorityName;
boredzo@2402
   167
	switch (priority) {
boredzo@2402
   168
		case -2:
ingmarstein@2627
   169
			priorityName = CFSTR("verylow");
boredzo@2402
   170
			break;
boredzo@2402
   171
		case -1:
ingmarstein@2627
   172
			priorityName = CFSTR("moderate");
boredzo@2402
   173
			break;
boredzo@2402
   174
		default:
boredzo@2402
   175
		case 0:
ingmarstein@2627
   176
			priorityName = CFSTR("normal");
boredzo@2402
   177
			break;
boredzo@2402
   178
		case 1:
ingmarstein@2627
   179
			priorityName = CFSTR("high");
boredzo@2402
   180
			break;
boredzo@2402
   181
		case 2:
ingmarstein@2627
   182
			priorityName = CFSTR("emergency");
boredzo@2402
   183
			break;
boredzo@2402
   184
	}
ingmarstein@2943
   185
eridius@2773
   186
	CFMutableStringRef htmlString = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, (CFStringRef)templateHTML);
boredzo@4063
   187
boredzo@4063
   188
	NSString *imageMediaType = @"image/png";
boredzo@4063
   189
	NSData *imageData = [icon PNGRepresentation];
boredzo@4063
   190
	if (!imageData) {
boredzo@4063
   191
		//Couldn't create a PNG, so fall back on TIFF.
boredzo@4063
   192
		imageMediaType = @"image/tiff";
boredzo@4063
   193
		imageData = [icon TIFFRepresentation];
boredzo@4063
   194
	}
boredzo@4063
   195
	NSString *growlImageString = [NSString stringWithFormat:@"data:%@;base64,%@", imageMediaType, [imageData base64Encoding]];
boredzo@2402
   196
boredzo@2402
   197
	float opacity = 95.0f;
eridius@2773
   198
	READ_GROWL_PREF_FLOAT(GrowlWebKitOpacityPref, [[bridge display] prefDomain], &opacity);
boredzo@2402
   199
	opacity *= 0.01f;
ingmarstein@2627
   200
boredzo@3655
   201
	CFStringRef titleHTML = createStringByEscapingForHTML((CFStringRef)title);
boredzo@3655
   202
	CFStringRef textHTML = createStringByEscapingForHTML((CFStringRef)text);
ingmarstein@2627
   203
	CFStringRef opacityString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%f"), opacity);
ingmarstein@2627
   204
evands@3576
   205
	CFStringFindAndReplace(htmlString, CFSTR("%baseurl%"),  (CFStringRef)[baseURL absoluteString], CFRangeMake(0, CFStringGetLength(htmlString)), 0);
evands@3576
   206
	CFStringFindAndReplace(htmlString, CFSTR("%opacity%"),  opacityString,				 CFRangeMake(0, CFStringGetLength(htmlString)), 0);
evands@3576
   207
	CFStringFindAndReplace(htmlString, CFSTR("%priority%"), priorityName,				 CFRangeMake(0, CFStringGetLength(htmlString)), 0);
evands@4035
   208
	CFStringFindAndReplace(htmlString, CFSTR("growlimage://%image%"), (CFStringRef)growlImageString, CFRangeMake(0, CFStringGetLength(htmlString)), 0);
boredzo@3655
   209
	CFStringFindAndReplace(htmlString, CFSTR("%title%"),    (CFStringRef)titleHTML,		 CFRangeMake(0, CFStringGetLength(htmlString)), 0);
boredzo@3655
   210
	CFStringFindAndReplace(htmlString, CFSTR("%text%"),     (CFStringRef)textHTML,		 CFRangeMake(0, CFStringGetLength(htmlString)), 0);
ingmarstein@2627
   211
ingmarstein@2627
   212
	CFRelease(opacityString);
boredzo@3655
   213
	CFRelease(titleHTML);
boredzo@3655
   214
	CFRelease(textHTML);
boredzo@2402
   215
	WebFrame *webFrame = [view mainFrame];
boredzo@2402
   216
	[[self window] disableFlushWindow];
evands@3502
   217
ingmarstein@2652
   218
	[webFrame loadHTMLString:(NSString *)htmlString baseURL:nil];
boredzo@2402
   219
	[[webFrame frameView] setAllowsScrolling:NO];
ingmarstein@2652
   220
	CFRelease(htmlString);
boredzo@2402
   221
}
boredzo@2402
   222
boredzo@2402
   223
/*!
boredzo@2402
   224
 * @brief Prevent the webview from following external links.  We direct these to the users web browser.
boredzo@2402
   225
 */
boredzo@2402
   226
- (void) webView:(WebView *)sender
boredzo@2402
   227
	decidePolicyForNavigationAction:(NSDictionary *)actionInformation
boredzo@2402
   228
		request:(NSURLRequest *)request
boredzo@2402
   229
		  frame:(WebFrame *)frame
boredzo@2402
   230
	decisionListener:(id<WebPolicyDecisionListener>)listener
boredzo@2402
   231
{
boredzo@2402
   232
#pragma unused(sender, request, frame)
ingmarstein@2641
   233
	int actionKey = getIntegerForKey(actionInformation, WebActionNavigationTypeKey);
boredzo@2402
   234
	if (actionKey == WebNavigationTypeOther) {
boredzo@2402
   235
		[listener use];
boredzo@2402
   236
	} else {
ingmarstein@2641
   237
		NSURL *url = getObjectForKey(actionInformation, WebActionOriginalURLKey);
boredzo@2402
   238
boredzo@2402
   239
		//Ignore file URLs, but open anything else
boredzo@2402
   240
		if (![url isFileURL])
boredzo@2402
   241
			[[NSWorkspace sharedWorkspace] openURL:url];
boredzo@2402
   242
boredzo@2402
   243
		[listener ignore];
boredzo@2402
   244
	}
boredzo@2402
   245
}
boredzo@2402
   246
boredzo@2402
   247
/*!
boredzo@2402
   248
 * @brief Invoked once the webview has loaded and is ready to accept content
boredzo@2402
   249
 */
boredzo@2402
   250
- (void) webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
boredzo@2402
   251
#pragma unused(frame)
boredzo@2402
   252
	NSWindow *myWindow = [self window];
boredzo@2402
   253
	if ([myWindow isFlushWindowDisabled])
boredzo@2402
   254
		[myWindow enableFlushWindow];
boredzo@2402
   255
boredzo@2402
   256
	GrowlWebKitWindowView *view = (GrowlWebKitWindowView *)sender;
boredzo@2402
   257
	[view sizeToFit];
evands@3546
   258
evands@3576
   259
	//Update our new frame
evands@3576
   260
	[[GrowlPositionController sharedInstance] positionDisplay:self];
evands@3576
   261
ingmarstein@2580
   262
	[myWindow invalidateShadow];
boredzo@2402
   263
}
boredzo@2402
   264
ingmarstein@2943
   265
- (void) setNotification:(GrowlApplicationNotification *)theNotification {
eridius@2773
   266
    if (notification == theNotification)
eridius@2773
   267
		return;
ingmarstein@2943
   268
ingmarstein@3036
   269
	[super setNotification:theNotification];
ingmarstein@3036
   270
eridius@2773
   271
	// Extract the new details from the notification
eridius@2773
   272
	NSDictionary *noteDict = [notification dictionaryRepresentation];
bgannin@3404
   273
	NSString *title = [notification title];
bgannin@3404
   274
	NSString *text  = [notification notificationDescription];
eridius@2773
   275
	NSImage *icon   = getObjectForKey(noteDict, GROWL_NOTIFICATION_ICON);
eridius@2773
   276
	int priority    = getIntegerForKey(noteDict, GROWL_NOTIFICATION_PRIORITY);
ingmarstein@2943
   277
eridius@2773
   278
	NSPanel *panel = (NSPanel *)[self window];
eridius@2773
   279
	WebView *view = [panel contentView];
bgannin@3404
   280
	[self setTitle:title text:text icon:icon priority:priority forView:view];
ingmarstein@2943
   281
evands@3576
   282
//	NSRect panelFrame = [view frame];
evands@3576
   283
	
evands@3576
   284
//	[panel setFrame:panelFrame display:NO];
eridius@2773
   285
}
eridius@2773
   286
ingmarstein@3037
   287
#pragma mark -
ingmarstein@3037
   288
#pragma mark positioning methods
ingmarstein@3037
   289
ingmarstein@3037
   290
- (NSPoint) idealOriginInRect:(NSRect)rect {
ingmarstein@3037
   291
	NSRect viewFrame = [[[self window] contentView] frame];
bgannin@3317
   292
	enum GrowlPosition originatingPosition = [[GrowlPositionController sharedInstance] originPosition];
bgannin@3273
   293
	NSPoint idealOrigin;
evands@4032
   294
bgannin@3273
   295
	switch(originatingPosition){
bgannin@3273
   296
		case GrowlTopRightPosition:
bgannin@3273
   297
			idealOrigin = NSMakePoint(NSMaxX(rect) - NSWidth(viewFrame) - paddingX,
bgannin@3273
   298
									  NSMaxY(rect) - paddingY - NSHeight(viewFrame));
bgannin@3273
   299
			break;
bgannin@3273
   300
		case GrowlTopLeftPosition:
bgannin@3273
   301
			idealOrigin = NSMakePoint(NSMinX(rect) + paddingX,
bgannin@3273
   302
									  NSMaxY(rect) - paddingY - NSHeight(viewFrame));
bgannin@3273
   303
			break;
bgannin@3273
   304
		case GrowlBottomLeftPosition:
bgannin@3273
   305
			idealOrigin = NSMakePoint(NSMinX(rect) + paddingX,
bgannin@3273
   306
									  NSMinY(rect) + paddingY);
bgannin@3273
   307
			break;
bgannin@3273
   308
		case GrowlBottomRightPosition:
bgannin@3273
   309
			idealOrigin = NSMakePoint(NSMaxX(rect) - NSWidth(viewFrame) - paddingX,
bgannin@3273
   310
									  NSMinY(rect) + paddingY);
bgannin@3273
   311
			break;
bgannin@3273
   312
		default:
bgannin@3273
   313
			idealOrigin = NSMakePoint(NSMaxX(rect) - NSWidth(viewFrame) - paddingX,
bgannin@3273
   314
									  NSMaxY(rect) - paddingY - NSHeight(viewFrame));
bgannin@3273
   315
			break;			
bgannin@3273
   316
	}
evands@4032
   317
bgannin@3273
   318
	return idealOrigin;	
ingmarstein@3037
   319
}
ingmarstein@3037
   320
boredzo@3095
   321
- (enum GrowlExpansionDirection) primaryExpansionDirection {
bgannin@3317
   322
	enum GrowlPosition originatingPosition = [[GrowlPositionController sharedInstance] originPosition];
bgannin@3273
   323
	enum GrowlExpansionDirection directionToExpand;
bgannin@3273
   324
	
bgannin@3273
   325
	switch(originatingPosition){
bgannin@3273
   326
		case GrowlTopLeftPosition:
bgannin@3273
   327
			directionToExpand = GrowlDownExpansionDirection;
bgannin@3273
   328
			break;
bgannin@3273
   329
		case GrowlTopRightPosition:
bgannin@3273
   330
			directionToExpand = GrowlDownExpansionDirection;
bgannin@3273
   331
			break;
bgannin@3273
   332
		case GrowlBottomLeftPosition:
bgannin@3273
   333
			directionToExpand = GrowlUpExpansionDirection;
bgannin@3273
   334
			break;
bgannin@3273
   335
		case GrowlBottomRightPosition:
bgannin@3273
   336
			directionToExpand = GrowlUpExpansionDirection;
bgannin@3273
   337
			break;
bgannin@3273
   338
		default:
bgannin@3273
   339
			directionToExpand = GrowlDownExpansionDirection;
bgannin@3273
   340
			break;			
bgannin@3273
   341
	}
bgannin@3273
   342
	
bgannin@3273
   343
	return directionToExpand;
ingmarstein@3037
   344
}
ingmarstein@3037
   345
boredzo@3095
   346
- (enum GrowlExpansionDirection) secondaryExpansionDirection {
bgannin@3317
   347
	enum GrowlPosition originatingPosition = [[GrowlPositionController sharedInstance] originPosition];
bgannin@3273
   348
	enum GrowlExpansionDirection directionToExpand;
bgannin@3273
   349
	
bgannin@3273
   350
	switch(originatingPosition){
bgannin@3273
   351
		case GrowlTopLeftPosition:
bgannin@3273
   352
			directionToExpand = GrowlRightExpansionDirection;
bgannin@3273
   353
			break;
bgannin@3273
   354
		case GrowlTopRightPosition:
bgannin@3273
   355
			directionToExpand = GrowlLeftExpansionDirection;
bgannin@3273
   356
			break;
bgannin@3273
   357
		case GrowlBottomLeftPosition:
bgannin@3273
   358
			directionToExpand = GrowlRightExpansionDirection;
bgannin@3273
   359
			break;
bgannin@3273
   360
		case GrowlBottomRightPosition:
bgannin@3273
   361
			directionToExpand = GrowlLeftExpansionDirection;
bgannin@3273
   362
			break;
bgannin@3273
   363
		default:
bgannin@3273
   364
			directionToExpand = GrowlRightExpansionDirection;
bgannin@3273
   365
			break;
bgannin@3273
   366
	}
bgannin@3273
   367
	
bgannin@3273
   368
	return directionToExpand;
ingmarstein@3037
   369
}
ingmarstein@3037
   370
ingmarstein@3037
   371
- (float) requiredDistanceFromExistingDisplays {
ingmarstein@3037
   372
	return paddingY;
ingmarstein@3037
   373
}
ingmarstein@3037
   374
eridius@2773
   375
@end
evands@4035
   376
evands@4035
   377
@implementation NSData (Base64Additions)
evands@4035
   378
evands@4035
   379
static char encodingTable[64] = {
evands@4035
   380
	'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
evands@4035
   381
	'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
evands@4035
   382
	'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
evands@4035
   383
'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' };
evands@4035
   384
evands@4035
   385
- (NSString *) base64EncodingWithLineLength:(unsigned int) lineLength {
evands@4035
   386
	const unsigned char	*bytes = [self bytes];
evands@4035
   387
	NSMutableString *result = [NSMutableString stringWithCapacity:[self length]];
evands@4035
   388
	unsigned long ixtext = 0;
evands@4035
   389
	unsigned long lentext = [self length];
evands@4035
   390
	long ctremaining = 0;
evands@4035
   391
	unsigned char inbuf[3], outbuf[4];
evands@4035
   392
	unsigned short i = 0;
evands@4035
   393
	unsigned short charsonline = 0, ctcopy = 0;
evands@4035
   394
	unsigned long ix = 0;
evands@4035
   395
	
evands@4035
   396
	while( YES ) {
evands@4035
   397
		ctremaining = lentext - ixtext;
evands@4035
   398
		if( ctremaining <= 0 ) break;
evands@4035
   399
		
evands@4035
   400
		for( i = 0; i < 3; i++ ) {
evands@4035
   401
			ix = ixtext + i;
evands@4035
   402
			if( ix < lentext ) inbuf[i] = bytes[ix];
evands@4035
   403
			else inbuf [i] = 0;
evands@4035
   404
		}
evands@4035
   405
		
evands@4035
   406
		outbuf [0] = (inbuf [0] & 0xFC) >> 2;
evands@4035
   407
		outbuf [1] = ((inbuf [0] & 0x03) << 4) | ((inbuf [1] & 0xF0) >> 4);
evands@4035
   408
		outbuf [2] = ((inbuf [1] & 0x0F) << 2) | ((inbuf [2] & 0xC0) >> 6);
evands@4035
   409
		outbuf [3] = inbuf [2] & 0x3F;
evands@4035
   410
		ctcopy = 4;
evands@4035
   411
		
evands@4035
   412
		switch( ctremaining ) {
evands@4035
   413
			case 1:
evands@4035
   414
				ctcopy = 2;
evands@4035
   415
				break;
evands@4035
   416
			case 2:
evands@4035
   417
				ctcopy = 3;
evands@4035
   418
				break;
evands@4035
   419
		}
evands@4035
   420
		
evands@4035
   421
		for( i = 0; i < ctcopy; i++ )
evands@4035
   422
			[result appendFormat:@"%c", encodingTable[outbuf[i]]];
evands@4035
   423
		
evands@4035
   424
		for( i = ctcopy; i < 4; i++ )
evands@4035
   425
			[result appendString:@"="];
evands@4035
   426
		
evands@4035
   427
		ixtext += 3;
evands@4035
   428
		charsonline += 4;
evands@4035
   429
		
evands@4035
   430
		if( lineLength > 0 ) {
evands@4035
   431
			if( charsonline >= lineLength ) {
evands@4035
   432
				charsonline = 0;
evands@4035
   433
				[result appendString:@"\n"];
evands@4035
   434
			}
evands@4035
   435
		}
evands@4035
   436
	}
evands@4035
   437
	
evands@4035
   438
	return result;
evands@4035
   439
}
evands@4035
   440
evands@4035
   441
- (NSString *) base64Encoding {
evands@4035
   442
	return [self base64EncodingWithLineLength:0];
evands@4035
   443
}
evands@4035
   444
evands@4035
   445
@end
evands@4035
   446
evands@4035
   447
@implementation NSImage (PNGRepAddition)
boredzo@4063
   448
- (NSBitmapImageRep *)GrowlBitmapImageRepForPNG
boredzo@4037
   449
{
evands@4048
   450
	//Find the biggest image
boredzo@4037
   451
	NSEnumerator *repsEnum = [[self representations] objectEnumerator];
evands@4048
   452
	NSBitmapImageRep *bestRep = nil;
boredzo@4037
   453
	NSImageRep *rep;
boredzo@4037
   454
	Class NSBitmapImageRepClass = [NSBitmapImageRep class];
evands@4048
   455
	float maxWidth = 0;
boredzo@4037
   456
	while ((rep = [repsEnum nextObject])) {
boredzo@4037
   457
		if ([rep isKindOfClass:NSBitmapImageRepClass]) {
boredzo@4063
   458
			//We can't convert a 1-bit image to PNG format (libpng throws an error), so ignore any 1-bit image reps, regardless of size.
boredzo@4063
   459
			if ([rep bitsPerSample] > 1) {
boredzo@4063
   460
				float width = [rep size].width;
boredzo@4063
   461
				if (width >= maxWidth) {
boredzo@4063
   462
					//Cast explanation: GCC warns about us returning an NSImageRep here, presumably because it could be some other kind of NSImageRep if we don't check the class. Fortunately, we have such a check. This cast silences the warning.
boredzo@4063
   463
					bestRep = (NSBitmapImageRep *)rep;
boredzo@4063
   464
boredzo@4063
   465
					maxWidth = width;
boredzo@4063
   466
				}
evands@4048
   467
			}
boredzo@4037
   468
		}
boredzo@4037
   469
	}
evands@4048
   470
	
evands@4048
   471
	return bestRep;
boredzo@4037
   472
}
boredzo@4037
   473
evands@4035
   474
- (NSData *)PNGRepresentation
evands@4035
   475
{
boredzo@4063
   476
	/* PNG is easy; it supports almost everything TIFF does (not 1-bit images), and NSImage's PNG support is great. */
boredzo@4063
   477
	return ([(NSBitmapImageRep *)[self GrowlBitmapImageRepForPNG] representationUsingType:NSPNGFileType properties:nil]);
boredzo@4063
   478
}
boredzo@4063
   479
boredzo@4063
   480
@end