Plugins/Displays/Bubbles/GrowlBubblesWindowView.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 //  GrowlBubblesWindowView.m
     3 //  Growl
     4 //
     5 //  Created by Nelson Elhage on Wed Jun 09 2004.
     6 //  Name changed from KABubbleWindowView.m by Justin Burns on Fri Nov 05 2004.
     7 //  Copyright (c) 2004-2006 The Growl Project. All rights reserved.
     8 //
     9 
    10 #import "GrowlBubblesWindowView.h"
    11 #import "GrowlDefinesInternal.h"
    12 #import "GrowlBubblesDefines.h"
    13 #import "GrowlImageAdditions.h"
    14 #import "GrowlBezierPathAdditions.h"
    15 #import "NSMutableAttributedStringAdditions.h"
    16 #import <WebKit/WebPreferences.h>
    17 
    18 /* to get the limit pref */
    19 #import "GrowlBubblesPrefsController.h"
    20 
    21 /* Hardcoded geometry values */
    22 #define PANEL_WIDTH_PX			270.0 /*!< Total width of the panel, including border */
    23 #define BORDER_WIDTH_PX			  4.0
    24 #define BORDER_RADIUS_PX		  9.0
    25 #define PANEL_VSPACE_PX			 10.0 /*!< Vertical padding from bounds to content area */
    26 #define PANEL_HSPACE_PX			 15.0 /*!< Horizontal padding from bounds to content area */
    27 #define ICON_SIZE_PX			 32.0 /*!< The width and height of the (square) icon */
    28 #define ICON_SIZE_LARGE_PX		 48.0 /*!< The width and height of the (square) icon */
    29 #define ICON_HSPACE_PX			  8.0 /*!< Horizontal space between icon and title/description */
    30 #define TITLE_VSPACE_PX			  5.0 /*!< Vertical space between title and description */
    31 #define TITLE_FONT_SIZE_PTS		 13.0
    32 #define DESCR_FONT_SIZE_PTS		 11.0
    33 #define MAX_TEXT_ROWS				5  /*!< The maximum number of rows of text, used only if the limit preference is set. */
    34 #define MIN_TEXT_HEIGHT			(PANEL_VSPACE_PX + PANEL_VSPACE_PX + iconSize)
    35 #define TEXT_AREA_WIDTH			(PANEL_WIDTH_PX - PANEL_HSPACE_PX - PANEL_HSPACE_PX - iconSize - ICON_HSPACE_PX)
    36 
    37 static void GrowlBubblesShadeInterpolate( void *info, const CGFloat *inData, CGFloat *outData ) {
    38 	CGFloat *colors = (CGFloat *) info;
    39 
    40 	register CGFloat a = inData[0];
    41 	register CGFloat a_coeff = 1.0 - a;
    42 
    43 	// SIMD could come in handy here
    44 	// outData[0..3] = a_coeff * colors[4..7] + a * colors[0..3]
    45 	outData[0] = a_coeff * colors[4] + a * colors[0];
    46 	outData[1] = a_coeff * colors[5] + a * colors[1];
    47 	outData[2] = a_coeff * colors[6] + a * colors[2];
    48 	outData[3] = a_coeff * colors[7] + a * colors[3];
    49 }
    50 
    51 #pragma mark -
    52 
    53 @implementation GrowlBubblesWindowView
    54 
    55 - (id) initWithFrame:(NSRect) frame {
    56 	if ((self = [super initWithFrame:frame])) {
    57 		titleFont = [[NSFont boldSystemFontOfSize:TITLE_FONT_SIZE_PTS] retain];
    58 		textFont = [[NSFont messageFontOfSize:DESCR_FONT_SIZE_PTS] retain];
    59 		borderColor = [[NSColor colorWithCalibratedWhite:0.0 alpha:0.5] retain];
    60 		highlightColor = [[NSColor colorWithCalibratedWhite:0.0 alpha:0.75] retain];
    61 		textLayoutManager = [[NSLayoutManager alloc] init];
    62 		titleLayoutManager = [[NSLayoutManager alloc] init];
    63 		lineHeight = [textLayoutManager defaultLineHeightForFont:textFont];
    64 
    65 		int size = GrowlBubblesSizePrefDefault;
    66 		READ_GROWL_PREF_INT(GrowlBubblesSizePref, GrowlBubblesPrefDomain, &size);
    67 		if (size == GrowlBubblesSizeLarge) {
    68 			iconSize = ICON_SIZE_LARGE_PX;
    69 		} else {
    70 			iconSize = ICON_SIZE_PX;
    71 		}
    72 	}
    73 	return self;
    74 }
    75 
    76 - (void) dealloc {
    77 	[titleFont          release];
    78 	[textFont           release];
    79 	[icon               release];
    80 	[textColor          release];
    81 	[bgColor            release];
    82 	[lightColor         release];
    83 	[borderColor        release];
    84 	[highlightColor     release];
    85 	[textStorage        release];
    86 	[titleStorage       release];
    87 	[textLayoutManager  release];
    88 	[titleLayoutManager release];
    89 
    90 	[super dealloc];
    91 }
    92 
    93 - (CGFloat) titleHeight {
    94 	return haveTitle ? titleHeight : 0.0;
    95 }
    96 
    97 
    98 - (void) drawRect:(NSRect) rect {
    99 #pragma unused (rect)
   100 	//Make sure that we don't draw in the main thread
   101 	//if ([super dispatchDrawingToThread:rect]) {
   102 		NSRect b = [self bounds];
   103 		CGRect bounds = CGRectMake(b.origin.x, b.origin.y, b.size.width, b.size.height);
   104 		CGRect shape = CGRectInset(bounds, BORDER_WIDTH_PX*0.5, BORDER_WIDTH_PX*0.5);
   105 
   106 		CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
   107 
   108 		// Create a path with enough room to strike the border and remain inside our frame.
   109 		// Since the path is in the middle of the line, this means we must inset it by half the border width.
   110 		addRoundedRectToPath(context, shape, BORDER_RADIUS_PX);
   111 		CGContextSetLineWidth(context, BORDER_WIDTH_PX);
   112 
   113 		CGContextSaveGState(context);
   114 		CGContextClip(context);
   115 
   116 		// Create a callback function to generate the
   117 		// fill clipped graphics context with our gradient
   118 		struct CGFunctionCallbacks callbacks = { 0U, GrowlBubblesShadeInterpolate, NULL };
   119 		CGFloat colors[8];
   120 
   121 		[lightColor getRed:&colors[0]
   122 					 green:&colors[1]
   123 					  blue:&colors[2]
   124 					 alpha:&colors[3]];
   125 
   126 		[bgColor getRed:&colors[4]
   127 				  green:&colors[5]
   128 				   blue:&colors[6]
   129 				  alpha:&colors[7]];
   130 
   131 		CGFunctionRef function = CGFunctionCreate( (void *) colors,
   132 												   1U,
   133 												   /*domain*/ NULL,
   134 												   4U,
   135 												   /*range*/ NULL,
   136 												   &callbacks );
   137 		CGColorSpaceRef cspace = CGColorSpaceCreateDeviceRGB();
   138 
   139 		CGPoint src, dst;
   140 		src.x = CGRectGetMinX(bounds);
   141 		src.y = CGRectGetMaxY(bounds);
   142 		dst.x = src.x;
   143 		dst.y = CGRectGetMinY(bounds);
   144 		CGShadingRef shading = CGShadingCreateAxial(cspace, src, dst,
   145 													function, false, false);
   146 
   147 		CGContextDrawShading(context, shading);
   148 
   149 		CGShadingRelease(shading);
   150 		CGColorSpaceRelease(cspace);
   151 		CGFunctionRelease(function);
   152 
   153 		CGContextRestoreGState(context);
   154 
   155 		addRoundedRectToPath(context, shape, BORDER_RADIUS_PX);
   156 		CGContextSetLineWidth(context, BORDER_WIDTH_PX);
   157 		if (mouseOver)
   158 			[highlightColor set];
   159 		else
   160 			[borderColor set];
   161 		CGContextStrokePath(context);
   162 
   163 		NSRect drawRect;
   164 		drawRect.origin.x = PANEL_HSPACE_PX;
   165 		drawRect.origin.y = PANEL_VSPACE_PX;
   166 		drawRect.size.width = iconSize;
   167 		drawRect.size.height = iconSize;
   168 
   169 		[icon setFlipped:YES];
   170 		[icon drawScaledInRect:drawRect
   171 					 operation:NSCompositeSourceOver
   172 					  fraction:1.0];
   173 
   174 		drawRect.origin.x += iconSize + ICON_HSPACE_PX;
   175 
   176 		if (haveTitle) {
   177 			[titleLayoutManager drawGlyphsForGlyphRange:titleRange atPoint:drawRect.origin];
   178 			drawRect.origin.y += titleHeight + TITLE_VSPACE_PX;
   179 		}
   180 
   181 		if (haveText)
   182 			[textLayoutManager drawGlyphsForGlyphRange:textRange atPoint:drawRect.origin];
   183 
   184 		[[self window] invalidateShadow];
   185 		[super drawRect:rect];
   186 	//}
   187 }
   188 
   189 #pragma mark -
   190 
   191 - (void) setPriority:(int)priority {
   192 	NSString *key;
   193 	NSString *textKey;
   194 	NSString *topKey;
   195 
   196 	switch (priority) {
   197 		case -2:
   198 			key = GrowlBubblesVeryLowColor;
   199 			textKey = GrowlBubblesVeryLowTextColor;
   200 			topKey = GrowlBubblesVeryLowTopColor;
   201 			break;
   202 		case -1:
   203 			key = GrowlBubblesModerateColor;
   204 			textKey = GrowlBubblesModerateTextColor;
   205 			topKey = GrowlBubblesModerateTopColor;
   206 			break;
   207 		case 1:
   208 			key = GrowlBubblesHighColor;
   209 			textKey = GrowlBubblesHighTextColor;
   210 			topKey = GrowlBubblesHighTopColor;
   211 			break;
   212 		case 2:
   213 			key = GrowlBubblesEmergencyColor;
   214 			textKey = GrowlBubblesEmergencyTextColor;
   215 			topKey = GrowlBubblesEmergencyTopColor;
   216 			break;
   217 		case 0:
   218 		default:
   219 			key = GrowlBubblesNormalColor;
   220 			textKey = GrowlBubblesNormalTextColor;
   221 			topKey = GrowlBubblesNormalTopColor;
   222 			break;
   223 	}
   224 
   225 	NSData *data = nil;
   226 
   227 	CGFloat backgroundAlpha = 95.0;
   228 	READ_GROWL_PREF_FLOAT(GrowlBubblesOpacity, GrowlBubblesPrefDomain, &backgroundAlpha);
   229 	backgroundAlpha *= 0.01;
   230 	
   231 	Class NSDataClass = [NSData class];
   232 	READ_GROWL_PREF_VALUE(key, GrowlBubblesPrefDomain, NSData *, &data);
   233 	if(data)
   234 		CFMakeCollectable(data);		
   235 	if (data && [data isKindOfClass:NSDataClass]) {
   236 			bgColor = [NSUnarchiver unarchiveObjectWithData:data];
   237 			bgColor = [bgColor colorWithAlphaComponent:backgroundAlpha];
   238 	} else {
   239 		bgColor = [NSColor colorWithCalibratedRed:0.69412
   240 											green:0.83147
   241 											 blue:0.96078
   242 											alpha:backgroundAlpha];
   243 	}
   244 	[bgColor retain];
   245 	[data release];
   246 
   247 	data = nil;
   248 	READ_GROWL_PREF_VALUE(textKey, GrowlBubblesPrefDomain, NSData *, &data);
   249 	if(data)
   250 		CFMakeCollectable(data);		
   251 	if (data && [data isKindOfClass:NSDataClass]) {
   252 		textColor = [NSUnarchiver unarchiveObjectWithData:data];
   253 	} else {
   254 		textColor = [NSColor controlTextColor];
   255 	}
   256 	[textColor retain];
   257 	[data release];
   258 
   259 	data = nil;
   260 	READ_GROWL_PREF_VALUE(topKey, GrowlBubblesPrefDomain, NSData *, &data);
   261 	if(data)
   262 		CFMakeCollectable(data);		
   263 	if (data && [data isKindOfClass:NSDataClass]) {
   264 		lightColor = [NSUnarchiver unarchiveObjectWithData:data];
   265 		lightColor = [lightColor colorWithAlphaComponent:backgroundAlpha];
   266 	} else {
   267 		lightColor = [NSColor colorWithCalibratedRed:0.93725
   268 											   green:0.96863
   269 												blue:0.99216
   270 											   alpha:backgroundAlpha];
   271 	}
   272 	[lightColor retain];
   273 	[data release];
   274 }
   275 
   276 - (void) setIcon:(NSImage *) anIcon {
   277 	[icon release];
   278 	icon = [anIcon retain];
   279 	[self setNeedsDisplay:YES];
   280 }
   281 
   282 - (void) setTitle:(NSString *) aTitle {
   283 	haveTitle = [aTitle length] != 0;
   284 
   285 	if (!haveTitle) {
   286 		[self setNeedsDisplay:YES];
   287 		return;
   288 	}
   289 
   290 	if (!titleStorage) {
   291 		NSSize containerSize;
   292 		containerSize.width = TEXT_AREA_WIDTH;
   293 		containerSize.height = FLT_MAX;
   294 		titleStorage = [[NSTextStorage alloc] init];
   295 		titleContainer = [[NSTextContainer alloc] initWithContainerSize:containerSize];
   296 		[titleLayoutManager addTextContainer:titleContainer];	// retains textContainer
   297 		[titleContainer release];
   298 		[titleStorage addLayoutManager:titleLayoutManager];	// retains layoutManager
   299 		[titleContainer setLineFragmentPadding:0.0];
   300 	}
   301 
   302 	NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
   303 	[paragraphStyle setLineBreakMode:NSLineBreakByTruncatingTail];
   304 	NSDictionary *defaultAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:
   305 		titleFont,      NSFontAttributeName,
   306 		textColor,      NSForegroundColorAttributeName,
   307 		paragraphStyle, NSParagraphStyleAttributeName,
   308 		nil];
   309 	[paragraphStyle release];
   310 
   311 	[[titleStorage mutableString] setString:aTitle];
   312 	[titleStorage setAttributes:defaultAttributes range:NSMakeRange(0, [titleStorage length])];
   313 
   314 	[defaultAttributes release];
   315 
   316 	titleRange = [titleLayoutManager glyphRangeForTextContainer:titleContainer];	// force layout
   317 	titleHeight = [titleLayoutManager usedRectForTextContainer:titleContainer].size.height;
   318 
   319 	[self setNeedsDisplay:YES];
   320 }
   321 
   322 - (void) setText:(NSString *) aText {
   323 	haveText = [aText length] != 0;
   324 
   325 	if (!haveText) {
   326 		[self setNeedsDisplay:YES];
   327 		return;
   328 	}
   329 
   330 	if (!textStorage) {
   331 		NSSize containerSize;
   332 		BOOL limitPref = YES;
   333 		READ_GROWL_PREF_BOOL(GrowlBubblesLimitPref, GrowlBubblesPrefDomain, &limitPref);
   334 		containerSize.width = TEXT_AREA_WIDTH;
   335 		if (limitPref)
   336 			containerSize.height = lineHeight * MAX_TEXT_ROWS;
   337 		else
   338 			containerSize.height = FLT_MAX;
   339 		textStorage = [[NSTextStorage alloc] init];
   340   		textContainer = [[NSTextContainer alloc] initWithContainerSize:containerSize];
   341 		[textLayoutManager addTextContainer:textContainer];	// retains textContainer
   342 		[textContainer release];
   343 		[textStorage addLayoutManager:textLayoutManager];	// retains layoutManager
   344 		[textContainer setLineFragmentPadding:0.0];
   345 	}
   346 
   347 	NSDictionary *defaultAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:
   348 		textFont,  NSFontAttributeName,
   349 		textColor, NSForegroundColorAttributeName,
   350 		nil];
   351 
   352 	[[textStorage mutableString] setString:aText];
   353 	[textStorage setAttributes:defaultAttributes range:NSMakeRange(0, [textStorage length])];
   354 		
   355 	[defaultAttributes release];
   356 
   357 	textRange = [textLayoutManager glyphRangeForTextContainer:textContainer];	// force layout
   358 	textHeight = [textLayoutManager usedRectForTextContainer:textContainer].size.height;
   359 
   360 	[self setNeedsDisplay:YES];
   361 }
   362 
   363 - (void) sizeToFit {
   364 	CGFloat height = PANEL_VSPACE_PX + PANEL_VSPACE_PX + [self titleHeight] + [self descriptionHeight];
   365 	if (haveTitle && haveText)
   366 		height += TITLE_VSPACE_PX;
   367 	if (height < MIN_TEXT_HEIGHT)
   368 		height = MIN_TEXT_HEIGHT;
   369 
   370 	// resize the window so that it contains the tracking rect
   371 	NSWindow *window = [self window];
   372 	NSRect windowRect = [window frame];
   373 	windowRect.origin.y -= height - windowRect.size.height;
   374 	windowRect.size.height = height;
   375 	[window setFrame:windowRect display:YES animate:YES];
   376 
   377 	if (trackingRectTag)
   378 		[self removeTrackingRect:trackingRectTag];
   379 	trackingRectTag = [self addTrackingRect:[self frame] owner:self userData:NULL assumeInside:NO];
   380 }
   381 
   382 - (BOOL) isFlipped {
   383 	// Coordinates are based on top left corner
   384     return YES;
   385 }
   386 
   387 - (CGFloat) descriptionHeight {
   388 	return haveText ? textHeight : 0.0;
   389 }
   390 
   391 - (NSInteger) descriptionRowCount {
   392 	NSInteger rowCount = textHeight / lineHeight;
   393 	BOOL limitPref = YES;
   394 	READ_GROWL_PREF_BOOL(GrowlBubblesLimitPref, GrowlBubblesPrefDomain, &limitPref);
   395 	if (limitPref)
   396 		return MIN(rowCount, MAX_TEXT_ROWS);
   397 	else
   398 		return rowCount;
   399 }
   400 
   401 @end