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