2 // GrowlSmokeWindowView.m
5 // Created by Matthew Walton on 11/09/2004.
6 // Copyright 2004-2006 The Growl Project. All rights reserved.
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>
17 #define GrowlSmokeTextAreaWidth (GrowlSmokeNotificationWidth - GrowlSmokePadding - iconSize - GrowlSmokeIconTextPadding - GrowlSmokePadding)
18 #define GrowlSmokeMinTextHeight (GrowlSmokePadding + iconSize + GrowlSmokePadding)
20 @interface ISProgressIndicator : NSProgressIndicator {
23 @implementation ISProgressIndicator
24 - (void) startAnimation:(id)sender {
25 #pragma unused(sender)
27 - (void) stopAnimation:(id)sender {
28 #pragma unused(sender)
30 - (void) animate:(id)sender {
31 #pragma unused(sender)
35 @implementation GrowlSmokeWindowView
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];
47 int size = GrowlSmokeSizePrefDefault;
48 READ_GROWL_PREF_INT(GrowlSmokeSizePref, GrowlSmokePrefDomain, &size);
49 if (size == GrowlSmokeSizeLarge)
50 iconSize = GrowlSmokeIconSizeLarge;
52 iconSize = GrowlSmokeIconSize;
54 [self setCloseBoxOrigin:NSMakePoint(2,3)];
58 - (void) setProgress:(NSNumber *)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];
70 [progressIndicator setDoubleValue:[value doubleValue]];
71 [self setNeedsDisplay:YES];
72 } else if (progressIndicator) {
73 [progressIndicator removeFromSuperview];
74 progressIndicator = nil;
84 [textStorage release];
85 [textLayoutManager release];
86 [titleStorage release];
87 [titleLayoutManager release];
93 // Coordinates are based on top left corner
97 - (void) drawRect:(NSRect)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);
104 CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
106 // calculate bounds based on icon-float pref on or off
108 BOOL floatIcon = GrowlSmokeFloatIconPrefDefault;
109 READ_GROWL_PREF_BOOL(GrowlSmokeFloatIconPref, GrowlSmokePrefDomain, &floatIcon);
111 CGFloat sizeReduction = GrowlSmokePadding + iconSize + (GrowlSmokeIconTextPadding * 0.5);
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);
118 shadedBounds = CGRectInset(bounds, 1.0, 1.0);
121 // set up bezier path for rounded corners
122 addRoundedRectToPath(context, shadedBounds, GrowlSmokeBorderRadius);
123 CGContextSetLineWidth(context, 2.0);
126 CGPathDrawingMode drawingMode;
128 drawingMode = kCGPathFillStroke;
130 [textColor setStroke];
132 drawingMode = kCGPathFill;
135 CGContextDrawPath(context, drawingMode);
137 // draw the title and the text
139 drawRect.origin.x = GrowlSmokePadding;
140 drawRect.origin.y = GrowlSmokePadding;
141 drawRect.size.width = iconSize;
142 drawRect.size.height = iconSize;
144 [icon setFlipped:YES];
145 [icon drawScaledInRect:drawRect
146 operation:NSCompositeSourceOver
149 drawRect.origin.x += iconSize + GrowlSmokeIconTextPadding;
152 [titleLayoutManager drawGlyphsForGlyphRange:titleRange atPoint:drawRect.origin];
153 drawRect.origin.y += titleHeight + GrowlSmokeTitleTextPadding;
157 [textLayoutManager drawGlyphsForGlyphRange:textRange atPoint:drawRect.origin];
159 [[self window] invalidateShadow];
160 [super drawRect:rect];
164 - (void) setIcon:(NSImage *)anIcon {
166 icon = [anIcon retain];
167 [self setNeedsDisplay:YES];
170 - (void) setTitle:(NSString *)aTitle {
171 haveTitle = [aTitle length] != 0;
174 [self setNeedsDisplay:YES];
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];
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,
200 [paragraphStyle release];
202 [[titleStorage mutableString] setString:aTitle];
203 [titleStorage setAttributes:defaultAttributes range:NSMakeRange(0U, [aTitle length])];
205 [defaultAttributes release];
207 titleRange = [titleLayoutManager glyphRangeForTextContainer:titleContainer]; // force layout
208 titleHeight = [titleLayoutManager usedRectForTextContainer:titleContainer].size.height;
210 [self setNeedsDisplay:YES];
213 - (void) setText:(NSString *)aText {
214 haveText = [aText length] != 0;
217 [self setNeedsDisplay:YES];
222 NSSize containerSize;
223 BOOL limitPref = GrowlSmokeLimitPrefDefault;
224 READ_GROWL_PREF_BOOL(GrowlSmokeLimitPref, GrowlSmokePrefDomain, &limitPref);
225 containerSize.width = GrowlSmokeTextAreaWidth;
227 containerSize.height = lineHeight * GrowlSmokeMaxLines;
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];
238 // construct default attributes for the description text
239 NSDictionary *defaultAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:
240 textFont, NSFontAttributeName,
241 textColor, NSForegroundColorAttributeName,
242 textShadow, NSShadowAttributeName,
245 [[textStorage mutableString] setString:aText];
246 [textStorage setAttributes:defaultAttributes range:NSMakeRange(0U, [aText length])];
248 [defaultAttributes release];
250 textRange = [textLayoutManager glyphRangeForTextContainer:textContainer]; // force layout
251 textHeight = [textLayoutManager usedRectForTextContainer:textContainer].size.height;
253 [self setNeedsDisplay:YES];
256 - (void) setPriority:(int)priority {
261 key = GrowlSmokeVeryLowColor;
262 textKey = GrowlSmokeVeryLowTextColor;
265 key = GrowlSmokeModerateColor;
266 textKey = GrowlSmokeModerateTextColor;
269 key = GrowlSmokeHighColor;
270 textKey = GrowlSmokeHighTextColor;
273 key = GrowlSmokeEmergencyColor;
274 textKey = GrowlSmokeEmergencyTextColor;
278 key = GrowlSmokeNormalColor;
279 textKey = GrowlSmokeNormalTextColor;
283 CGFloat backgroundAlpha = GrowlSmokeAlphaPrefDefault;
284 READ_GROWL_PREF_FLOAT(GrowlSmokeAlphaPref, GrowlSmokePrefDomain, &backgroundAlpha);
285 backgroundAlpha *= 0.01;
289 Class NSDataClass = [NSData class];
292 READ_GROWL_PREF_VALUE(key, GrowlSmokePrefDomain, NSData *, &data);
294 CFMakeCollectable(data);
295 if (data && [data isKindOfClass:NSDataClass]) {
296 bgColor = [NSUnarchiver unarchiveObjectWithData:data];
297 bgColor = [bgColor colorWithAlphaComponent:backgroundAlpha];
299 bgColor = [NSColor colorWithCalibratedWhite:0.1 alpha:backgroundAlpha];
306 READ_GROWL_PREF_VALUE(textKey, GrowlSmokePrefDomain, NSData *, &data);
308 CFMakeCollectable(data);
309 if (data && [data isKindOfClass:NSDataClass]) {
310 textColor = [NSUnarchiver unarchiveObjectWithData:data];
312 textColor = [NSColor whiteColor];
318 [textShadow setShadowColor:[bgColor blendedColorWithFraction:0.5 ofColor:[NSColor blackColor]]];
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;
330 NSRect rect = [self frame];
331 rect.size.height = height;
332 [self setFrame:rect];
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];
342 [self removeTrackingRect:trackingRectTag];
343 trackingRectTag = [self addTrackingRect:rect owner:self userData:NULL assumeInside:NO];
346 - (CGFloat) titleHeight {
347 return haveTitle ? titleHeight : 0.0;
350 - (CGFloat) descriptionHeight {
351 return haveText ? textHeight : 0.0;
354 - (NSInteger) descriptionRowCount {
355 NSInteger rowCount = textHeight / lineHeight;
356 BOOL limitPref = GrowlSmokeLimitPrefDefault;
357 READ_GROWL_PREF_BOOL(GrowlSmokeLimitPref, GrowlSmokePrefDomain, &limitPref);
359 return MIN(rowCount, GrowlSmokeMaxLines);
370 - (void) setTarget:(id) object {
380 - (void) setAction:(SEL) selector {