Plugins/Displays/iCal/GrowliCalWindowView.m
author boredzo
Sun Jul 06 15:14:04 2008 +0000 (2008-07-06)
changeset 4136 642c27e46937
parent 3983 6820c1d7096a
child 4246 4f52d1d98978
permissions -rw-r--r--
Release the bar color after creating it. Fixes a leak that I found using leaks.
     1 //
     2 //  GrowliCalWindowView.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 //	Adapted for iCal by Takumi Murayama on Thu Aug 17 2006.
     8 //  Copyright (c) 2004-2006 The Growl Project. All rights reserved.
     9 //
    10 
    11 #import "GrowliCalWindowView.h"
    12 #import "GrowlDefinesInternal.h"
    13 #import "GrowliCalDefines.h"
    14 #import "GrowlImageAdditions.h"
    15 #import "GrowlBezierPathAdditions.h"
    16 #import "NSMutableAttributedStringAdditions.h"
    17 #import <WebKit/WebPreferences.h>
    18 
    19 /* to get the limit pref */
    20 #import "GrowliCalPrefsController.h"
    21 
    22 /* Hardcoded geometry values */
    23 #define PANEL_WIDTH_PX			270.0f /*!< Total width of the panel, including border */
    24 #define BORDER_WIDTH_PX			  1.0f
    25 #define BORDER_RADIUS_PX		  6.0f
    26 #define PANEL_VSPACE_PX			  1.0f /*!< Vertical padding from bounds to content area */
    27 #define PANEL_HSPACE_PX			  6.0f /*!< Horizontal padding from bounds to content area */
    28 #define ICON_SIZE_PX			 32.0f /*!< The width and height of the (square) icon */
    29 #define ICON_SIZE_LARGE_PX		 48.0f /*!< The width and height of the (square) icon */
    30 #define ICON_HSPACE_PX			  3.0f /*!< Horizontal space between icon and title/description */
    31 #define TITLE_VSPACE_PX			  5.0f /*!< Vertical space between title and description */
    32 #define TITLE_FONT_SIZE_PTS		 11.0f
    33 #define DESCR_FONT_SIZE_PTS		 11.0f
    34 #define MAX_TEXT_ROWS				5  /*!< The maximum number of rows of text, used only if the limit preference is set. */
    35 #define MIN_TEXT_HEIGHT			(PANEL_VSPACE_PX + PANEL_VSPACE_PX + iconSize + 1)
    36 #define TEXT_AREA_WIDTH			(PANEL_WIDTH_PX - PANEL_HSPACE_PX - PANEL_HSPACE_PX - iconSize - ICON_HSPACE_PX)
    37 
    38 static void GrowliCalShadeInterpolate( void *info, const float *inData, float *outData ) {
    39 	float *colors = (float *) info;
    40 
    41 	register float a = inData[0];
    42 	register float a_coeff = 1.0f - a;
    43 
    44 	// SIMD could come in handy here
    45 	// outData[0..3] = a_coeff * colors[4..7] + a * colors[0..3]
    46 	outData[0] = (a_coeff * colors[4]) + (a * colors[0]);
    47 	outData[1] = (a_coeff * colors[5]) + (a * colors[1]);
    48 	outData[2] = (a_coeff * colors[6]) + (a * colors[2]);
    49 	outData[3] = (a_coeff * colors[7]) + (a * colors[3]);
    50 }
    51 
    52 static void addTopRoundedRectToPath(CGContextRef context, CGRect rect, float radius) {
    53 	float minX = CGRectGetMinX(rect);
    54 	float minY = CGRectGetMinY(rect);
    55 	float maxX = CGRectGetMaxX(rect);
    56 	float maxY = CGRectGetMaxY(rect);
    57 	float midX = CGRectGetMidX(rect);
    58 	float midY = CGRectGetMidY(rect);
    59 	
    60 	CGContextBeginPath(context);
    61 	CGContextMoveToPoint(context, maxX, midY);
    62 	CGContextAddArcToPoint(context, maxX, maxY, midX, maxY, 0);		// Bottom Right
    63 	CGContextAddArcToPoint(context, minX, maxY, minX, midY, 0);		// Bottom Left
    64 	CGContextAddArcToPoint(context, minX, minY, midX, minY, radius);// Top Left
    65 	CGContextAddArcToPoint(context, maxX, minY, maxX, midY, radius);// Top Right
    66 	CGContextClosePath(context);
    67 }
    68 
    69 #pragma mark -
    70 
    71 @implementation GrowliCalWindowView
    72 
    73 - (id) initWithFrame:(NSRect) frame {
    74 	if ((self = [super initWithFrame:frame])) {
    75 		titleFont = [[NSFont systemFontOfSize:TITLE_FONT_SIZE_PTS] retain];
    76 		textFont = [[NSFont systemFontOfSize:DESCR_FONT_SIZE_PTS] retain];
    77 		textLayoutManager = [[NSLayoutManager alloc] init];
    78 		titleLayoutManager = [[NSLayoutManager alloc] init];
    79 		lineHeight = [textLayoutManager defaultLineHeightForFont:textFont];
    80 
    81 		int size = GrowliCalSizePrefDefault;
    82 		READ_GROWL_PREF_INT(GrowliCalSizePref, GrowliCalPrefDomain, &size);
    83 		if (size == GrowliCalSizeLarge) {
    84 			iconSize = ICON_SIZE_LARGE_PX;
    85 		} else {
    86 			iconSize = ICON_SIZE_PX;
    87 		}
    88 	}
    89 	return self;
    90 }
    91 
    92 - (void) dealloc {
    93 	[titleFont          release];
    94 	[textFont           release];
    95 	[icon               release];
    96 	[textColor          release];
    97 	[bgColor            release];
    98 	[lightColor         release];
    99 	[borderColor        release];
   100 	[textStorage        release];
   101 	[titleStorage       release];
   102 	[textLayoutManager  release];
   103 	[titleLayoutManager release];
   104 
   105 	[super dealloc];
   106 }
   107 
   108 - (float) titleHeight {
   109 	return haveTitle ? titleHeight : 0.0f;
   110 }
   111 
   112 
   113 - (void) drawRect:(NSRect) rect {
   114 #pragma unused(rect)
   115 	//Make sure that we don't draw in the main thread
   116 	//if ([super dispatchDrawingToThread:rect]) {
   117 		NSRect b = [self bounds];
   118 		CGRect bounds = CGRectMake(b.origin.x, b.origin.y, b.size.width, b.size.height);
   119 		CGRect shape = CGRectInset(bounds, BORDER_WIDTH_PX*0.5f, BORDER_WIDTH_PX*0.5f);
   120 
   121 		CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
   122 
   123 		// Create a path with enough room to strike the border and remain inside our frame.
   124 		// Since the path is in the middle of the line, this means we must inset it by half the border width.
   125 		addRoundedRectToPath(context, shape, BORDER_RADIUS_PX);
   126 		CGContextSetLineWidth(context, BORDER_WIDTH_PX);
   127 
   128 		CGContextSaveGState(context);
   129 		CGContextClip(context);
   130 
   131 		// Create a callback function to generate the
   132 		// fill clipped graphics context with our gradient
   133 		struct CGFunctionCallbacks callbacks = { 0U, GrowliCalShadeInterpolate, NULL };
   134 		float colors[8];
   135 
   136 		[lightColor getRed:&colors[0]
   137 					 green:&colors[1]
   138 					  blue:&colors[2]
   139 					 alpha:&colors[3]];
   140 
   141 		[bgColor getRed:&colors[4]
   142 				  green:&colors[5]
   143 				   blue:&colors[6]
   144 				  alpha:&colors[7]];
   145 
   146 		CGFunctionRef function = CGFunctionCreate( (void *) colors,
   147 												   1U,
   148 												   /*domain*/ NULL,
   149 												   4U,
   150 												   /*range*/ NULL,
   151 												   &callbacks );
   152 		CGColorSpaceRef cspace = CGColorSpaceCreateDeviceRGB();
   153 
   154 		CGPoint src, dst;
   155 		src.x = CGRectGetMaxX(bounds);
   156 		src.y = CGRectGetMaxY(bounds);
   157 		dst.x = CGRectGetMinX(bounds);
   158 		dst.y = src.y;
   159 		CGShadingRef shading = CGShadingCreateAxial(cspace, dst, src,
   160 													function, false, false);
   161 
   162 		CGContextDrawShading(context, shading);
   163 
   164 		CGShadingRelease(shading);
   165 		CGFunctionRelease(function);
   166 
   167 		CGContextRestoreGState(context);
   168 
   169 		float tbcolor[4]; 
   170 		tbcolor[0] = [borderColor redComponent];
   171 		tbcolor[1] = [borderColor greenComponent];
   172 		tbcolor[2] = [borderColor blueComponent];
   173 		tbcolor[3] = [borderColor alphaComponent];
   174 		CGColorRef barcolor = CGColorCreate(cspace,tbcolor);
   175 		if (barcolor) {
   176 			CGContextSetFillColorWithColor(context,barcolor);
   177 			CFRelease(barcolor);
   178 		}
   179 		CGRect titlebar = CGRectMake(0,CGRectGetMinY(shape),CGRectGetWidth(shape),15);
   180 		addTopRoundedRectToPath(context,titlebar,BORDER_RADIUS_PX);
   181 		CGContextFillPath(context);
   182 		CGColorSpaceRelease(cspace);
   183 
   184 		addRoundedRectToPath(context, shape, BORDER_RADIUS_PX);
   185 		CGContextSetLineWidth(context, BORDER_WIDTH_PX);
   186 		[borderColor set];
   187 		CGContextStrokePath(context);
   188 
   189 		NSRect drawRect;
   190 		drawRect.origin.x = CGRectGetMaxX(shape) - iconSize - ICON_HSPACE_PX;
   191 		drawRect.origin.y = PANEL_VSPACE_PX;
   192 		drawRect.size.width = iconSize;
   193 		drawRect.size.height = iconSize;
   194 
   195 		[icon setFlipped:YES];
   196 		[icon drawScaledInRect:drawRect
   197 					 operation:NSCompositeSourceOver
   198 					  fraction:1.0f];
   199 
   200 		drawRect.origin.x = PANEL_HSPACE_PX;
   201 
   202 		if (haveTitle) {
   203 			[titleLayoutManager drawGlyphsForGlyphRange:titleRange atPoint:drawRect.origin];
   204 			drawRect.origin.y += titleHeight + TITLE_VSPACE_PX;
   205 		}
   206 
   207 		if (haveText)
   208 			[textLayoutManager drawGlyphsForGlyphRange:textRange atPoint:drawRect.origin];
   209 
   210 		[[self window] invalidateShadow];
   211 		[super drawRect:rect];
   212 	//}
   213 }
   214 
   215 #pragma mark -
   216 
   217 - (void) setPriority:(int)priority {
   218 	/* What is going on here? setPriority is the preference reader and completely ignores the priority? */
   219 	/* This method is completely ridiculous */
   220 	float backgroundAlpha = 95.0f;
   221 	READ_GROWL_PREF_FLOAT(GrowliCalOpacity, GrowliCalPrefDomain, &backgroundAlpha);
   222 	backgroundAlpha *= 0.01f;
   223 
   224 	[textColor release];
   225 	textColor = [[NSColor whiteColor] retain];
   226 
   227 	[bgColor release];
   228 	[lightColor release];
   229 	[borderColor release];
   230 
   231 	GrowliCalColorType color = GrowliCalPurple;
   232 	READ_GROWL_PREF_INT(GrowliCalColor, GrowliCalPrefDomain, &color);
   233 	switch (color) {
   234 		case GrowliCalPurple:
   235 			bgColor = [NSColor colorWithCalibratedRed:0.4000f green:0.1804f blue:0.7569f alpha:backgroundAlpha];		
   236 			lightColor = [NSColor colorWithCalibratedRed:0.6863f green:0.5294f blue:0.9765f alpha:backgroundAlpha];
   237 			borderColor = [NSColor colorWithCalibratedRed:0.3216f green:0.0588f blue:0.6902f alpha:backgroundAlpha];
   238 			break;
   239 		
   240 		case GrowliCalPink:
   241 			bgColor = [NSColor colorWithCalibratedRed:0.7804f green:0.1098f blue:0.7725f alpha:backgroundAlpha];		
   242 			lightColor = [NSColor colorWithCalibratedRed:0.8157f green:0.2471f blue:0.8078f alpha:backgroundAlpha];
   243 			borderColor = [NSColor colorWithCalibratedRed:0.7412f green:0.0000f blue:0.7294f alpha:backgroundAlpha];
   244 			break;
   245 
   246 		case GrowliCalGreen:
   247 			bgColor = [NSColor colorWithCalibratedRed:0.1490f green:0.7333f blue:0.0000f alpha:backgroundAlpha];		
   248 			lightColor = [NSColor colorWithCalibratedRed:0.3765f green:0.8039f blue:0.2549f alpha:backgroundAlpha];
   249 			borderColor = [NSColor colorWithCalibratedRed:0.0000f green:0.6824f blue:0.0000f alpha:backgroundAlpha];
   250 			break;
   251 
   252 		case GrowliCalBlue:
   253 			bgColor = [NSColor colorWithCalibratedRed:0.1255f green:0.3765f blue:0.9529f alpha:backgroundAlpha];		
   254 			lightColor = [NSColor colorWithCalibratedRed:0.3529f green:0.5647f blue:1.0000f alpha:backgroundAlpha];
   255 			borderColor = [NSColor colorWithCalibratedRed:0.0588f green:0.2784f blue:0.9137f alpha:backgroundAlpha];
   256 			break;
   257 
   258 		case GrowliCalOrange:
   259 			bgColor = [NSColor colorWithCalibratedRed:1.0000f green:0.4510f blue:0.0000f alpha:backgroundAlpha];
   260 			lightColor = [NSColor colorWithCalibratedRed:1.0000f green:0.6235f blue:0.0941f alpha:backgroundAlpha];
   261 			borderColor = [NSColor colorWithCalibratedRed:1.0000f green:0.4314f blue:0.0000f alpha:backgroundAlpha];
   262 			break;
   263 
   264 		case GrowliCalRed:
   265 			bgColor = [NSColor colorWithCalibratedRed:1.0000f green:0.0000f blue:0.0000f alpha:backgroundAlpha];
   266 			lightColor = [NSColor colorWithCalibratedRed:1.0000f green:0.2941f blue:0.3137f alpha:backgroundAlpha];
   267 			borderColor = [NSColor colorWithCalibratedRed:0.9529f green:0.0000f blue:0.0000f alpha:backgroundAlpha];
   268 			break;
   269 
   270 		default:
   271 		{
   272 			/* When could this ever happen?!? -eds */
   273 			if (priority == -2) {
   274 				bgColor = [NSColor colorWithCalibratedRed:0.4000f green:0.1804f blue:0.7569f alpha:backgroundAlpha];		
   275 				lightColor = [NSColor colorWithCalibratedRed:0.6863f green:0.5294f blue:0.9765f alpha:backgroundAlpha];
   276 				borderColor = [NSColor colorWithCalibratedRed:0.3216f green:0.0588f blue:0.6902f alpha:backgroundAlpha];
   277 			} else if (priority == -1) {
   278 				bgColor = [NSColor colorWithCalibratedRed:0.1490f green:0.7333f blue:0.0000f alpha:backgroundAlpha];
   279 				lightColor = [NSColor colorWithCalibratedRed:0.3765f green:0.8039f blue:0.2549f alpha:backgroundAlpha];
   280 				borderColor = [NSColor colorWithCalibratedRed:0.0000f green:0.6824f blue:0.0000f alpha:backgroundAlpha];
   281 			} else if (priority == 1) {
   282 				bgColor = [NSColor colorWithCalibratedRed:1.0000f green:0.4510f blue:0.0000f alpha:backgroundAlpha];
   283 				lightColor = [NSColor colorWithCalibratedRed:1.0000f green:0.6235f blue:0.0941f alpha:backgroundAlpha];
   284 				borderColor = [NSColor colorWithCalibratedRed:1.0000f green:0.4314f blue:0.0000f alpha:backgroundAlpha];
   285 			} else if (priority == 2) {
   286 				bgColor = [NSColor colorWithCalibratedRed:1.0000f green:0.0000f blue:0.0000f alpha:backgroundAlpha];
   287 				lightColor = [NSColor colorWithCalibratedRed:1.0000f green:0.2941f blue:0.3137f alpha:backgroundAlpha];
   288 				borderColor = [NSColor colorWithCalibratedRed:0.9529f green:0.0000f blue:0.0000f alpha:backgroundAlpha];
   289 			} else {
   290 				bgColor = [NSColor colorWithCalibratedRed:0.1255f green:0.3765f blue:0.9529f alpha:backgroundAlpha];
   291 				lightColor = [NSColor colorWithCalibratedRed:0.3529f green:0.5647f blue:1.0000f alpha:backgroundAlpha];
   292 				borderColor = [NSColor colorWithCalibratedRed:0.0588f green:0.2784f blue:0.9137f alpha:backgroundAlpha];
   293 			}
   294 		}
   295 	}
   296 	
   297 	[bgColor retain];
   298 	[lightColor retain];
   299 	[borderColor retain];
   300 }
   301 
   302 - (void) setIcon:(NSImage *) anIcon {
   303 	[icon release];
   304 	icon = [anIcon retain];
   305 	[self setNeedsDisplay:YES];
   306 }
   307 
   308 - (void) setTitle:(NSString *) aTitle {
   309 	haveTitle = [aTitle length] != 0;
   310 
   311 	if (!haveTitle) {
   312 		[self setNeedsDisplay:YES];
   313 		return;
   314 	}
   315 
   316 	if (!titleStorage) {
   317 		NSSize containerSize;
   318 		containerSize.width = TEXT_AREA_WIDTH;
   319 		containerSize.height = FLT_MAX;
   320 		titleStorage = [[NSTextStorage alloc] init];
   321 		titleContainer = [[NSTextContainer alloc] initWithContainerSize:containerSize];
   322 		[titleLayoutManager addTextContainer:titleContainer];	// retains textContainer
   323 		[titleContainer release];
   324 		[titleStorage addLayoutManager:titleLayoutManager];	// retains layoutManager
   325 		[titleContainer setLineFragmentPadding:0.0f];
   326 	}
   327 
   328 	NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
   329 	[paragraphStyle setLineBreakMode:NSLineBreakByTruncatingTail];
   330 	NSDictionary *defaultAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:
   331 		titleFont,      NSFontAttributeName,
   332 		textColor,      NSForegroundColorAttributeName,
   333 		paragraphStyle, NSParagraphStyleAttributeName,
   334 		nil];
   335 	[paragraphStyle release];
   336 
   337 	[[titleStorage mutableString] setString:aTitle];
   338 	[titleStorage setAttributes:defaultAttributes range:NSMakeRange(0, [titleStorage length])];
   339 
   340 	[defaultAttributes release];
   341 
   342 	titleRange = [titleLayoutManager glyphRangeForTextContainer:titleContainer];	// force layout
   343 	titleHeight = [titleLayoutManager usedRectForTextContainer:titleContainer].size.height;
   344 
   345 	[self setNeedsDisplay:YES];
   346 }
   347 
   348 - (void) setText:(NSString *) aText {
   349 	haveText = [aText length] != 0;
   350 
   351 	if (!haveText) {
   352 		[self setNeedsDisplay:YES];
   353 		return;
   354 	}
   355 
   356 	if (!textStorage) {
   357 		NSSize containerSize;
   358 		BOOL limitPref = YES;
   359 		READ_GROWL_PREF_BOOL(GrowliCalLimitPref, GrowliCalPrefDomain, &limitPref);
   360 		containerSize.width = TEXT_AREA_WIDTH;
   361 		if (limitPref)
   362 			containerSize.height = lineHeight * MAX_TEXT_ROWS;
   363 		else
   364 			containerSize.height = FLT_MAX;
   365 		textStorage = [[NSTextStorage alloc] init];
   366   		textContainer = [[NSTextContainer alloc] initWithContainerSize:containerSize];
   367 		[textLayoutManager addTextContainer:textContainer];	// retains textContainer
   368 		[textContainer release];
   369 		[textStorage addLayoutManager:textLayoutManager];	// retains layoutManager
   370 		[textContainer setLineFragmentPadding:0.0f];
   371 	}
   372 
   373 	NSDictionary *defaultAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:
   374 		textFont,  NSFontAttributeName,
   375 		textColor, NSForegroundColorAttributeName,
   376 		nil];
   377 
   378 	[[textStorage mutableString] setString:aText];
   379 	[textStorage setAttributes:defaultAttributes range:NSMakeRange(0, [textStorage length])];
   380 
   381 	[defaultAttributes release];
   382 
   383 	textRange = [textLayoutManager glyphRangeForTextContainer:textContainer];	// force layout
   384 	textHeight = [textLayoutManager usedRectForTextContainer:textContainer].size.height;
   385 
   386 	[self setNeedsDisplay:YES];
   387 }
   388 
   389 - (void) sizeToFit {
   390 	float height = PANEL_VSPACE_PX + PANEL_VSPACE_PX + [self titleHeight] + [self descriptionHeight];
   391 	if (haveTitle && haveText)
   392 		height += TITLE_VSPACE_PX;
   393 	if (height < MIN_TEXT_HEIGHT)
   394 		height = MIN_TEXT_HEIGHT;
   395 
   396 	// resize the window so that it contains the tracking rect
   397 	NSWindow *window = [self window];
   398 	NSRect windowRect = [window frame];
   399 	windowRect.origin.y -= height - windowRect.size.height;
   400 	windowRect.size.height = height;
   401 	[window setFrame:windowRect display:NO];
   402 
   403 	if (trackingRectTag)
   404 		[self removeTrackingRect:trackingRectTag];
   405 	trackingRectTag = [self addTrackingRect:[self frame] owner:self userData:NULL assumeInside:NO];
   406 }
   407 
   408 - (BOOL) isFlipped {
   409 	// Coordinates are based on top left corner
   410     return YES;
   411 }
   412 
   413 - (float) descriptionHeight {
   414 	return haveText ? textHeight : 0.0f;
   415 }
   416 
   417 - (int) descriptionRowCount {
   418 	int rowCount = textHeight / lineHeight;
   419 	BOOL limitPref = YES;
   420 	READ_GROWL_PREF_BOOL(GrowliCalLimitPref, GrowliCalPrefDomain, &limitPref);
   421 	if (limitPref)
   422 		return MIN(rowCount, MAX_TEXT_ROWS);
   423 	else
   424 		return rowCount;
   425 }
   426 
   427 @end