Plugins/Displays/Smoke/GrowlSmokeWindowView.m
author Rudy Richter
Sat Aug 01 20:43:39 2009 -0400 (2009-08-01)
changeset 4259 0e9b6b0b1e25
parent 4246 4f52d1d98978
child 4541 9a290d3de636
permissions -rw-r--r--
Plugins: clang warnings
     1 //
     2 //  GrowlSmokeWindowView.m
     3 //  Display Plugins
     4 //
     5 //  Created by Matthew Walton on 11/09/2004.
     6 //  Copyright 2004-2006 The Growl Project. All rights reserved.
     7 //
     8 
     9 #import "GrowlSmokeWindowView.h"
    10 #import "GrowlSmokeDefines.h"
    11 #import "GrowlDefinesInternal.h"
    12 #import "GrowlImageAdditions.h"
    13 #import "GrowlBezierPathAdditions.h"
    14 #import "NSMutableAttributedStringAdditions.h"
    15 #import <WebKit/WebPreferences.h>
    16 
    17 #define GrowlSmokeTextAreaWidth (GrowlSmokeNotificationWidth - GrowlSmokePadding - iconSize - GrowlSmokeIconTextPadding - GrowlSmokePadding)
    18 #define GrowlSmokeMinTextHeight	(GrowlSmokePadding + iconSize + GrowlSmokePadding)
    19 
    20 @interface ISProgressIndicator : NSProgressIndicator {
    21 }
    22 @end
    23 @implementation ISProgressIndicator
    24 - (void) startAnimation:(id)sender {
    25 #pragma unused(sender)
    26 }
    27 - (void) stopAnimation:(id)sender {
    28 #pragma unused(sender)
    29 }
    30 - (void) animate:(id)sender {
    31 #pragma unused(sender)
    32 }
    33 @end
    34 
    35 @implementation GrowlSmokeWindowView
    36 
    37 - (id) initWithFrame:(NSRect)frame {
    38 	if ((self = [super initWithFrame:frame])) {
    39 		textFont = [[NSFont systemFontOfSize:GrowlSmokeTextFontSize] retain];
    40 		textLayoutManager = [[NSLayoutManager alloc] init];
    41 		titleLayoutManager = [[NSLayoutManager alloc] init];
    42 		lineHeight = [textLayoutManager defaultLineHeightForFont:textFont];
    43 		textShadow = [[NSShadow alloc] init];
    44 		[textShadow setShadowOffset:NSMakeSize(0.0, -2.0)];
    45 		[textShadow setShadowBlurRadius:3.0];
    46 
    47 		int size = GrowlSmokeSizePrefDefault;
    48 		READ_GROWL_PREF_INT(GrowlSmokeSizePref, GrowlSmokePrefDomain, &size);
    49 		if (size == GrowlSmokeSizeLarge)
    50 			iconSize = GrowlSmokeIconSizeLarge;
    51 		else
    52 			iconSize = GrowlSmokeIconSize;
    53 	}
    54 	[self setCloseBoxOrigin:NSMakePoint(2,3)];
    55 	return self;
    56 }
    57 
    58 - (void) setProgress:(NSNumber *)value {
    59 	if (value) {
    60 		if (!progressIndicator) {
    61 			progressIndicator = [[ISProgressIndicator alloc] initWithFrame:NSMakeRect(GrowlSmokePadding, GrowlSmokePadding + iconSize + GrowlSmokeIconProgressPadding, iconSize, NSProgressIndicatorPreferredSmallThickness)];
    62 			[progressIndicator setStyle:NSProgressIndicatorBarStyle];
    63 			[progressIndicator setControlSize:NSSmallControlSize];
    64 			[progressIndicator setBezeled:NO];
    65 			[progressIndicator setControlTint:NSDefaultControlTint];
    66 			[progressIndicator setIndeterminate:NO];
    67 			[self addSubview:progressIndicator];
    68 			[progressIndicator release];
    69 		}
    70 		[progressIndicator setDoubleValue:[value doubleValue]];
    71 		[self setNeedsDisplay:YES];
    72 	} else if (progressIndicator) {
    73 		[progressIndicator removeFromSuperview];
    74 		progressIndicator = nil;
    75 	}
    76 }
    77 
    78 - (void) dealloc {
    79 	[textFont           release];
    80 	[icon               release];
    81 	[bgColor            release];
    82 	[textColor          release];
    83 	[textShadow         release];
    84 	[textStorage        release];
    85 	[textLayoutManager  release];
    86 	[titleStorage       release];
    87 	[titleLayoutManager release];
    88 
    89 	[super dealloc];
    90 }
    91 
    92 - (BOOL) isFlipped {
    93 	// Coordinates are based on top left corner
    94     return YES;
    95 }
    96 
    97 - (void) drawRect:(NSRect)rect {
    98 #pragma unused(rect)
    99 	//Make sure that we don't draw in the main thread
   100 	//if ([super dispatchDrawingToThread:rect]) {
   101 		NSRect b = [self bounds];
   102 		CGRect bounds = CGRectMake(b.origin.x, b.origin.y, b.size.width, b.size.height);
   103 
   104 		CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
   105 
   106 		// calculate bounds based on icon-float pref on or off
   107 		CGRect shadedBounds;
   108 		BOOL floatIcon = GrowlSmokeFloatIconPrefDefault;
   109 		READ_GROWL_PREF_BOOL(GrowlSmokeFloatIconPref, GrowlSmokePrefDomain, &floatIcon);
   110 		if (floatIcon) {
   111 			CGFloat sizeReduction = GrowlSmokePadding + iconSize + (GrowlSmokeIconTextPadding * 0.5);
   112 
   113 			shadedBounds = CGRectMake(bounds.origin.x + sizeReduction + 1.0,
   114 									  bounds.origin.y + 1.0,
   115 									  bounds.size.width - sizeReduction - 2.0,
   116 									  bounds.size.height - 2.0);
   117 		} else {
   118 			shadedBounds = CGRectInset(bounds, 1.0, 1.0);
   119 		}
   120 
   121 		// set up bezier path for rounded corners
   122 		addRoundedRectToPath(context, shadedBounds, GrowlSmokeBorderRadius);
   123 		CGContextSetLineWidth(context, 2.0);
   124 
   125 		// draw background
   126 		CGPathDrawingMode drawingMode;
   127 		if (mouseOver) {
   128 			drawingMode = kCGPathFillStroke;
   129 			[bgColor setFill];
   130 			[textColor setStroke];
   131 		} else {
   132 			drawingMode = kCGPathFill;
   133 			[bgColor set];
   134 		}
   135 		CGContextDrawPath(context, drawingMode);
   136 
   137 		// draw the title and the text
   138 		NSRect drawRect;
   139 		drawRect.origin.x = GrowlSmokePadding;
   140 		drawRect.origin.y = GrowlSmokePadding;
   141 		drawRect.size.width = iconSize;
   142 		drawRect.size.height = iconSize;
   143 
   144 		[icon setFlipped:YES];
   145 		[icon drawScaledInRect:drawRect
   146 					 operation:NSCompositeSourceOver
   147 					  fraction:1.0];
   148 
   149 		drawRect.origin.x += iconSize + GrowlSmokeIconTextPadding;
   150 
   151 		if (haveTitle) {
   152 			[titleLayoutManager drawGlyphsForGlyphRange:titleRange atPoint:drawRect.origin];
   153 			drawRect.origin.y += titleHeight + GrowlSmokeTitleTextPadding;
   154 		}
   155 
   156 		if (haveText)
   157 			[textLayoutManager drawGlyphsForGlyphRange:textRange atPoint:drawRect.origin];
   158 
   159 		[[self window] invalidateShadow];
   160 		[super drawRect:rect];
   161 	//}
   162 }
   163 
   164 - (void) setIcon:(NSImage *)anIcon {
   165 	[icon release];
   166 	icon = [anIcon retain];
   167 	[self setNeedsDisplay:YES];
   168 }
   169 
   170 - (void) setTitle:(NSString *)aTitle {
   171 	haveTitle = [aTitle length] != 0;
   172 
   173 	if (!haveTitle) {
   174 		[self setNeedsDisplay:YES];
   175 		return;
   176 	}
   177 
   178 	if (!titleStorage) {
   179 		NSSize containerSize;
   180 		containerSize.width = GrowlSmokeTextAreaWidth;
   181 		containerSize.height = FLT_MAX;
   182 		titleStorage = [[NSTextStorage alloc] init];
   183 		titleContainer = [[NSTextContainer alloc] initWithContainerSize:containerSize];
   184 		[titleLayoutManager addTextContainer:titleContainer];	// retains textContainer
   185 		[titleContainer release];
   186 		[titleStorage addLayoutManager:titleLayoutManager];	// retains layoutManager
   187 		[titleContainer setLineFragmentPadding:0.0];
   188 	}
   189 
   190 	// construct attributes for the title
   191 	NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
   192 	[paragraphStyle setLineBreakMode:NSLineBreakByTruncatingTail];
   193 	NSFont *titleFont = [NSFont boldSystemFontOfSize:GrowlSmokeTitleFontSize];
   194 	NSDictionary *defaultAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:
   195 		titleFont,		NSFontAttributeName,
   196 		textColor,		NSForegroundColorAttributeName,
   197 		textShadow,     NSShadowAttributeName,
   198 		paragraphStyle, NSParagraphStyleAttributeName,
   199 		nil];
   200 	[paragraphStyle release];
   201 
   202 	[[titleStorage mutableString] setString:aTitle];
   203 	[titleStorage setAttributes:defaultAttributes range:NSMakeRange(0U, [aTitle length])];
   204 
   205 	[defaultAttributes release];
   206 
   207 	titleRange = [titleLayoutManager glyphRangeForTextContainer:titleContainer];	// force layout
   208 	titleHeight = [titleLayoutManager usedRectForTextContainer:titleContainer].size.height;
   209 
   210 	[self setNeedsDisplay:YES];
   211 }
   212 
   213 - (void) setText:(NSString *)aText {
   214 	haveText = [aText length] != 0;
   215 
   216 	if (!haveText) {
   217 		[self setNeedsDisplay:YES];
   218 		return;
   219 	}
   220 
   221 	if (!textStorage) {
   222 		NSSize containerSize;
   223 		BOOL limitPref = GrowlSmokeLimitPrefDefault;
   224 		READ_GROWL_PREF_BOOL(GrowlSmokeLimitPref, GrowlSmokePrefDomain, &limitPref);
   225 		containerSize.width = GrowlSmokeTextAreaWidth;
   226 		if (limitPref)
   227 			containerSize.height = lineHeight * GrowlSmokeMaxLines;
   228 		else
   229 			containerSize.height = FLT_MAX;
   230 		textStorage = [[NSTextStorage alloc] init];
   231 		textContainer = [[NSTextContainer alloc] initWithContainerSize:containerSize];
   232 		[textLayoutManager addTextContainer:textContainer];	// retains textContainer
   233 		[textContainer release];
   234 		[textStorage addLayoutManager:textLayoutManager];	// retains layoutManager
   235 		[textContainer setLineFragmentPadding:0.0];
   236 	}
   237 
   238 	// construct default attributes for the description text
   239 	NSDictionary *defaultAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:
   240 		textFont,	NSFontAttributeName,
   241 		textColor,  NSForegroundColorAttributeName,
   242 		textShadow, NSShadowAttributeName,
   243 		nil];
   244 
   245 	[[textStorage mutableString] setString:aText];
   246 	[textStorage setAttributes:defaultAttributes range:NSMakeRange(0U, [aText length])];
   247 
   248 	[defaultAttributes release];
   249 
   250 	textRange = [textLayoutManager glyphRangeForTextContainer:textContainer];	// force layout
   251 	textHeight = [textLayoutManager usedRectForTextContainer:textContainer].size.height;
   252 
   253 	[self setNeedsDisplay:YES];
   254 }
   255 
   256 - (void) setPriority:(int)priority {
   257 	NSString *key;
   258 	NSString *textKey;
   259 	switch (priority) {
   260 		case -2:
   261 			key = GrowlSmokeVeryLowColor;
   262 			textKey = GrowlSmokeVeryLowTextColor;
   263 			break;
   264 		case -1:
   265 			key = GrowlSmokeModerateColor;
   266 			textKey = GrowlSmokeModerateTextColor;
   267 			break;
   268 		case 1:
   269 			key = GrowlSmokeHighColor;
   270 			textKey = GrowlSmokeHighTextColor;
   271 			break;
   272 		case 2:
   273 			key = GrowlSmokeEmergencyColor;
   274 			textKey = GrowlSmokeEmergencyTextColor;
   275 			break;
   276 		case 0:
   277 		default:
   278 			key = GrowlSmokeNormalColor;
   279 			textKey = GrowlSmokeNormalTextColor;
   280 			break;
   281 	}
   282 
   283 	CGFloat backgroundAlpha = GrowlSmokeAlphaPrefDefault;
   284 	READ_GROWL_PREF_FLOAT(GrowlSmokeAlphaPref, GrowlSmokePrefDomain, &backgroundAlpha);
   285 	backgroundAlpha *= 0.01;
   286 
   287 	[bgColor release];
   288 
   289 	Class NSDataClass = [NSData class];
   290 	NSData *data = nil;
   291 
   292 	READ_GROWL_PREF_VALUE(key, GrowlSmokePrefDomain, NSData *, &data);
   293 	if(data)
   294 		CFMakeCollectable(data);		
   295 	if (data && [data isKindOfClass:NSDataClass]) {
   296 			bgColor = [NSUnarchiver unarchiveObjectWithData:data];
   297 			bgColor = [bgColor colorWithAlphaComponent:backgroundAlpha];
   298 	} else {
   299 		bgColor = [NSColor colorWithCalibratedWhite:0.1 alpha:backgroundAlpha];
   300 	}
   301 	[bgColor retain];
   302 	[data release];
   303 	data = nil;
   304 
   305 	[textColor release];
   306 	READ_GROWL_PREF_VALUE(textKey, GrowlSmokePrefDomain, NSData *, &data);
   307 	if(data)
   308 		CFMakeCollectable(data);		
   309 	if (data && [data isKindOfClass:NSDataClass]) {
   310 			textColor = [NSUnarchiver unarchiveObjectWithData:data];
   311 	} else {
   312 		textColor = [NSColor whiteColor];
   313 	}
   314 	[textColor retain];
   315 	[data release];
   316 	data = nil;
   317 	
   318 	[textShadow setShadowColor:[bgColor blendedColorWithFraction:0.5 ofColor:[NSColor blackColor]]];
   319 }
   320 
   321 - (void) sizeToFit {
   322 	CGFloat height = GrowlSmokePadding + GrowlSmokePadding + [self titleHeight] + [self descriptionHeight];
   323 	if (haveTitle && haveText)
   324 		height += GrowlSmokeTitleTextPadding;
   325 	if (progressIndicator)
   326 		height += GrowlSmokeIconProgressPadding + [progressIndicator bounds].size.height;
   327 	if (height < GrowlSmokeMinTextHeight)
   328 		height = GrowlSmokeMinTextHeight;
   329 
   330 	NSRect rect = [self frame];
   331 	rect.size.height = height;
   332 	[self setFrame:rect];
   333 
   334 	// resize the window so that it contains the tracking rect
   335 	NSWindow *window = [self window];
   336 	NSRect windowRect = [window frame];
   337 	windowRect.origin.y -= height - windowRect.size.height;
   338 	windowRect.size.height = height;
   339 	[window setFrame:windowRect display:YES animate:YES];
   340 
   341 	if (trackingRectTag)
   342 		[self removeTrackingRect:trackingRectTag];
   343 	trackingRectTag = [self addTrackingRect:rect owner:self userData:NULL assumeInside:NO];
   344 }
   345 
   346 - (CGFloat) titleHeight {
   347 	return haveTitle ? titleHeight : 0.0;
   348 }
   349 
   350 - (CGFloat) descriptionHeight {
   351 	return haveText ? textHeight : 0.0;
   352 }
   353 
   354 - (NSInteger) descriptionRowCount {
   355 	NSInteger rowCount = textHeight / lineHeight;
   356 	BOOL limitPref = GrowlSmokeLimitPrefDefault;
   357 	READ_GROWL_PREF_BOOL(GrowlSmokeLimitPref, GrowlSmokePrefDomain, &limitPref);
   358 	if (limitPref)
   359 		return MIN(rowCount, GrowlSmokeMaxLines);
   360 	else
   361 		return rowCount;
   362 }
   363 
   364 #pragma mark -
   365 
   366 - (id) target {
   367 	return target;
   368 }
   369 
   370 - (void) setTarget:(id) object {
   371 	target = object;
   372 }
   373 
   374 #pragma mark -
   375 
   376 - (SEL) action {
   377 	return action;
   378 }
   379 
   380 - (void) setAction:(SEL) selector {
   381 	action = selector;
   382 }
   383 
   384 @end