2 // GrowlBubblesWindowView.m
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.
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>
18 /* to get the limit pref */
19 #import "GrowlBubblesPrefsController.h"
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)
37 static void GrowlBubblesShadeInterpolate( void *info, const CGFloat *inData, CGFloat *outData ) {
38 CGFloat *colors = (CGFloat *) info;
40 register CGFloat a = inData[0];
41 register CGFloat a_coeff = 1.0 - a;
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];
53 @implementation GrowlBubblesWindowView
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];
65 int size = GrowlBubblesSizePrefDefault;
66 READ_GROWL_PREF_INT(GrowlBubblesSizePref, GrowlBubblesPrefDomain, &size);
67 if (size == GrowlBubblesSizeLarge) {
68 iconSize = ICON_SIZE_LARGE_PX;
70 iconSize = ICON_SIZE_PX;
83 [borderColor release];
84 [highlightColor release];
85 [textStorage release];
86 [titleStorage release];
87 [textLayoutManager release];
88 [titleLayoutManager release];
93 - (CGFloat) titleHeight {
94 return haveTitle ? titleHeight : 0.0;
98 - (void) drawRect:(NSRect) 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);
106 CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
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);
113 CGContextSaveGState(context);
114 CGContextClip(context);
116 // Create a callback function to generate the
117 // fill clipped graphics context with our gradient
118 struct CGFunctionCallbacks callbacks = { 0U, GrowlBubblesShadeInterpolate, NULL };
121 [lightColor getRed:&colors[0]
126 [bgColor getRed:&colors[4]
131 CGFunctionRef function = CGFunctionCreate( (void *) colors,
137 CGColorSpaceRef cspace = CGColorSpaceCreateDeviceRGB();
140 src.x = CGRectGetMinX(bounds);
141 src.y = CGRectGetMaxY(bounds);
143 dst.y = CGRectGetMinY(bounds);
144 CGShadingRef shading = CGShadingCreateAxial(cspace, src, dst,
145 function, false, false);
147 CGContextDrawShading(context, shading);
149 CGShadingRelease(shading);
150 CGColorSpaceRelease(cspace);
151 CGFunctionRelease(function);
153 CGContextRestoreGState(context);
155 addRoundedRectToPath(context, shape, BORDER_RADIUS_PX);
156 CGContextSetLineWidth(context, BORDER_WIDTH_PX);
158 [highlightColor set];
161 CGContextStrokePath(context);
164 drawRect.origin.x = PANEL_HSPACE_PX;
165 drawRect.origin.y = PANEL_VSPACE_PX;
166 drawRect.size.width = iconSize;
167 drawRect.size.height = iconSize;
169 [icon setFlipped:YES];
170 [icon drawScaledInRect:drawRect
171 operation:NSCompositeSourceOver
174 drawRect.origin.x += iconSize + ICON_HSPACE_PX;
177 [titleLayoutManager drawGlyphsForGlyphRange:titleRange atPoint:drawRect.origin];
178 drawRect.origin.y += titleHeight + TITLE_VSPACE_PX;
182 [textLayoutManager drawGlyphsForGlyphRange:textRange atPoint:drawRect.origin];
184 [[self window] invalidateShadow];
185 [super drawRect:rect];
191 - (void) setPriority:(int)priority {
198 key = GrowlBubblesVeryLowColor;
199 textKey = GrowlBubblesVeryLowTextColor;
200 topKey = GrowlBubblesVeryLowTopColor;
203 key = GrowlBubblesModerateColor;
204 textKey = GrowlBubblesModerateTextColor;
205 topKey = GrowlBubblesModerateTopColor;
208 key = GrowlBubblesHighColor;
209 textKey = GrowlBubblesHighTextColor;
210 topKey = GrowlBubblesHighTopColor;
213 key = GrowlBubblesEmergencyColor;
214 textKey = GrowlBubblesEmergencyTextColor;
215 topKey = GrowlBubblesEmergencyTopColor;
219 key = GrowlBubblesNormalColor;
220 textKey = GrowlBubblesNormalTextColor;
221 topKey = GrowlBubblesNormalTopColor;
227 CGFloat backgroundAlpha = 95.0;
228 READ_GROWL_PREF_FLOAT(GrowlBubblesOpacity, GrowlBubblesPrefDomain, &backgroundAlpha);
229 backgroundAlpha *= 0.01;
231 Class NSDataClass = [NSData class];
232 READ_GROWL_PREF_VALUE(key, GrowlBubblesPrefDomain, NSData *, &data);
234 CFMakeCollectable(data);
235 if (data && [data isKindOfClass:NSDataClass]) {
236 bgColor = [NSUnarchiver unarchiveObjectWithData:data];
237 bgColor = [bgColor colorWithAlphaComponent:backgroundAlpha];
239 bgColor = [NSColor colorWithCalibratedRed:0.69412
242 alpha:backgroundAlpha];
248 READ_GROWL_PREF_VALUE(textKey, GrowlBubblesPrefDomain, NSData *, &data);
250 CFMakeCollectable(data);
251 if (data && [data isKindOfClass:NSDataClass]) {
252 textColor = [NSUnarchiver unarchiveObjectWithData:data];
254 textColor = [NSColor controlTextColor];
260 READ_GROWL_PREF_VALUE(topKey, GrowlBubblesPrefDomain, NSData *, &data);
262 CFMakeCollectable(data);
263 if (data && [data isKindOfClass:NSDataClass]) {
264 lightColor = [NSUnarchiver unarchiveObjectWithData:data];
265 lightColor = [lightColor colorWithAlphaComponent:backgroundAlpha];
267 lightColor = [NSColor colorWithCalibratedRed:0.93725
270 alpha:backgroundAlpha];
276 - (void) setIcon:(NSImage *) anIcon {
278 icon = [anIcon retain];
279 [self setNeedsDisplay:YES];
282 - (void) setTitle:(NSString *) aTitle {
283 haveTitle = [aTitle length] != 0;
286 [self setNeedsDisplay:YES];
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];
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,
309 [paragraphStyle release];
311 [[titleStorage mutableString] setString:aTitle];
312 [titleStorage setAttributes:defaultAttributes range:NSMakeRange(0, [titleStorage length])];
314 [defaultAttributes release];
316 titleRange = [titleLayoutManager glyphRangeForTextContainer:titleContainer]; // force layout
317 titleHeight = [titleLayoutManager usedRectForTextContainer:titleContainer].size.height;
319 [self setNeedsDisplay:YES];
322 - (void) setText:(NSString *) aText {
323 haveText = [aText length] != 0;
326 [self setNeedsDisplay:YES];
331 NSSize containerSize;
332 BOOL limitPref = YES;
333 READ_GROWL_PREF_BOOL(GrowlBubblesLimitPref, GrowlBubblesPrefDomain, &limitPref);
334 containerSize.width = TEXT_AREA_WIDTH;
336 containerSize.height = lineHeight * MAX_TEXT_ROWS;
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];
347 NSDictionary *defaultAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:
348 textFont, NSFontAttributeName,
349 textColor, NSForegroundColorAttributeName,
352 [[textStorage mutableString] setString:aText];
353 [textStorage setAttributes:defaultAttributes range:NSMakeRange(0, [textStorage length])];
355 [defaultAttributes release];
357 textRange = [textLayoutManager glyphRangeForTextContainer:textContainer]; // force layout
358 textHeight = [textLayoutManager usedRectForTextContainer:textContainer].size.height;
360 [self setNeedsDisplay:YES];
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;
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];
378 [self removeTrackingRect:trackingRectTag];
379 trackingRectTag = [self addTrackingRect:[self frame] owner:self userData:NULL assumeInside:NO];
383 // Coordinates are based on top left corner
387 - (CGFloat) descriptionHeight {
388 return haveText ? textHeight : 0.0;
391 - (NSInteger) descriptionRowCount {
392 NSInteger rowCount = textHeight / lineHeight;
393 BOOL limitPref = YES;
394 READ_GROWL_PREF_BOOL(GrowlBubblesLimitPref, GrowlBubblesPrefDomain, &limitPref);
396 return MIN(rowCount, MAX_TEXT_ROWS);