Plugins/Displays/GrowlDisplayWindowController.m
author Peter Hosey
Fri Jun 05 21:49:20 2009 -0700 (2009-06-05)
changeset 4208 10d1c49d4df1
parent 4142 5deddffb750a
child 4222 c6e8a12dd818
permissions -rw-r--r--
Fix MusicVideo not showing non-first notifications on displays that don't have the menu bar on them.

The problem was that GrowlDisplayWindowController and GrowlPositionController were both trying to get the screen of a window that wasn't on a screen (yet|anymore), both before and after displaying the notification. The solution is to get the screen from the display window controller, not from the window.
boredzo@2405
     1
//
boredzo@2405
     2
//  GrowlDisplayWindowController.m
boredzo@2405
     3
//  Display Plugins
boredzo@2405
     4
//
boredzo@2405
     5
//  Created by Mac-arena the Bored Zo on 2005-06-03.
ingmarstein@3040
     6
//  Copyright 2004-2006 The Growl Project. All rights reserved.
boredzo@2405
     7
//
boredzo@2405
     8
boredzo@2405
     9
#import "GrowlDisplayWindowController.h"
ingmarstein@2470
    10
#import "GrowlPathUtilities.h"
boredzo@2405
    11
#import "GrowlDefines.h"
ofri@2503
    12
#import "GrowlWindowTransition.h"
ofri@2657
    13
#import "GrowlPositionController.h"
boredzo@2428
    14
#import "NSViewAdditions.h"
eridius@2773
    15
#import "GrowlNotificationDisplayBridge.h"
eridius@2773
    16
#import "GrowlApplicationNotification.h"
evands@3641
    17
#import "GrowlNotificationView.h"
boredzo@2405
    18
boredzo@3137
    19
#include "GrowlLog.h"
boredzo@3137
    20
evands@3829
    21
#define DEFAULT_TRANSITION_DURATION	0.75
evands@3829
    22
jkp@2758
    23
static NSMutableDictionary *existingInstances;
ofri@2657
    24
evands@3499
    25
@interface GrowlDisplayWindowController (PRIVATE)
evands@3499
    26
- (void)cancelDisplayDelayedPerforms;
evands@3581
    27
- (BOOL)supportsStickyNotifications;
evands@3499
    28
@end
rudy@2828
    29
evands@4005
    30
@interface NSWindow (LeopardMethods)
evands@4005
    31
- (void)setCollectionBehavior:(int)collectionBehavior;
evands@4005
    32
@end
evands@4005
    33
boredzo@2405
    34
@implementation GrowlDisplayWindowController
boredzo@2405
    35
jkp@2758
    36
#pragma mark -
boredzo@3070
    37
#pragma mark Caching
jkp@2758
    38
jkp@2758
    39
+ (void) registerInstance:(id)instance withIdentifier:(NSString *)ident {
jkp@2758
    40
	if (!existingInstances)
jkp@2758
    41
		existingInstances = [[NSMutableDictionary alloc] init];
ingmarstein@2943
    42
ingmarstein@3008
    43
	NSDictionary *classInstances = [existingInstances objectForKey:self];
ingmarstein@3008
    44
	if (!classInstances) {
jkp@2758
    45
		classInstances = [[NSMutableDictionary alloc] init];
jkp@2758
    46
		[existingInstances setObject:classInstances forKey:self];
boredzo@4142
    47
		[classInstances release];
jkp@2758
    48
	}
jkp@2758
    49
	[classInstances setValue:instance forKey:ident];
jkp@2758
    50
}
jkp@2758
    51
eridius@2762
    52
+ (id) instanceWithIdentifier:(NSString *)ident {
jkp@2758
    53
	NSMutableDictionary *classInstances = [existingInstances objectForKey:self];
jkp@2758
    54
	if (classInstances)
jkp@2758
    55
		return [classInstances objectForKey:ident];
jkp@2758
    56
	else
jkp@2758
    57
		return nil;
jkp@2758
    58
}
jkp@2758
    59
jkp@2758
    60
+ (void) unregisterInstanceWithIdentifier:(NSString *)ident {
jkp@2758
    61
	NSMutableDictionary *classInstances = [existingInstances objectForKey:self];
jkp@2758
    62
	if (classInstances)
jkp@2758
    63
		[classInstances removeObjectForKey:ident];
jkp@2758
    64
}
jkp@2758
    65
jkp@2758
    66
#pragma mark -
jkp@2758
    67
ingmarstein@2943
    68
- (id) initWithWindowNibName:(NSString *)windowNibName bridge:(GrowlNotificationDisplayBridge *)displayBridge {
eridius@2773
    69
	// NOTE: for completeness we ought to offer the other nib related init methods with the plugin as a param
ingmarstein@2943
    70
	if ((self = [self initWithWindowNibName:windowNibName owner:displayBridge])) {
ingmarstein@2943
    71
		[self setBridge:displayBridge]; // weak reference
ingmarstein@2943
    72
	}
eridius@2773
    73
	return self;
eridius@2773
    74
}
eridius@2773
    75
ingmarstein@2943
    76
- (id) initWithBridge:(GrowlNotificationDisplayBridge *)displayBridge {
ingmarstein@3036
    77
	/* Subclasses using this method should call initWithWindowNibName: from init */
ingmarstein@2943
    78
	if ((self = [self init])) {
ingmarstein@2943
    79
		[self setBridge:displayBridge]; // weak reference
ingmarstein@2943
    80
	}
eridius@2773
    81
	return self;
eridius@2773
    82
}
eridius@2773
    83
ingmarstein@2663
    84
- (id) initWithWindow:(NSWindow *)window {
ingmarstein@2663
    85
	if ((self = [super initWithWindow:window])) {
jkp@2744
    86
		windowTransitions = [[NSMutableDictionary alloc] init];
ofri@2658
    87
		ignoresOtherNotifications = NO;
eridius@2773
    88
		bridge = nil;
jkp@2816
    89
		startTimes = NSCreateMapTable(NSObjectMapKeyCallBacks, NSIntMapValueCallBacks, 0U);
jkp@2816
    90
		endTimes = NSCreateMapTable(NSObjectMapKeyCallBacks, NSIntMapValueCallBacks, 0U);
evands@3829
    91
		transitionDuration = DEFAULT_TRANSITION_DURATION;
evands@3829
    92
evands@4005
    93
		//Show notifications on all Spaces
evands@4005
    94
		if ([window respondsToSelector:@selector(setCollectionBehavior:)]) {
evands@4005
    95
#define NSWindowCollectionBehaviorCanJoinAllSpaces 1 << 0
evands@4005
    96
			[window setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
evands@4005
    97
		}
evands@4005
    98
evands@3644
    99
		//Respond to 'close all notifications' by closing
evands@3644
   100
		[[NSNotificationCenter defaultCenter] addObserver:self
evands@3644
   101
												 selector:@selector(stopDisplay)
evands@3644
   102
													 name:GROWL_CLOSE_ALL_NOTIFICATIONS
evands@3644
   103
												   object:nil];
ofri@2503
   104
	}
ingmarstein@2591
   105
ofri@2503
   106
	return self;
ofri@2503
   107
}
ofri@2503
   108
boredzo@2405
   109
- (void) dealloc {
ofri@2498
   110
	[self setDelegate:nil];
boredzo@3137
   111
	[[self bridge] removeObserver:self forKeyPath:@"notification"];
evands@3644
   112
	[[NSNotificationCenter defaultCenter] removeObserver:self];
evands@3645
   113
	[self stopAllTransitions];
eridius@2773
   114
ingmarstein@2943
   115
	NSFreeMapTable(startTimes);
ingmarstein@2943
   116
	NSFreeMapTable(endTimes);
ingmarstein@2943
   117
evands@3499
   118
	[bridge				 release];
ingmarstein@2416
   119
	[target              release];
ingmarstein@2416
   120
	[clickContext        release];
ingmarstein@2416
   121
	[clickHandlerEnabled release];
ingmarstein@2416
   122
	[appName             release];
ingmarstein@2416
   123
	[appPid              release];
jkp@2741
   124
	[windowTransitions   release];
evands@3536
   125
	[notification        release];
boredzo@2405
   126
boredzo@2405
   127
	[super dealloc];
boredzo@2405
   128
}
boredzo@2405
   129
boredzo@2405
   130
#pragma mark -
boredzo@2405
   131
#pragma mark Screenshot mode
boredzo@2405
   132
boredzo@2426
   133
- (void) takeScreenshot {
boredzo@2428
   134
	NSView *view = [[self window] contentView];
ofri@2683
   135
	NSString *path = [[[GrowlPathUtilities screenshotsDirectory] stringByAppendingPathComponent:[GrowlPathUtilities nextScreenshotName]] stringByAppendingPathExtension:@"png"];
boredzo@2428
   136
	[[view dataWithPNGInsideRect:[view frame]] writeToFile:path atomically:NO];
boredzo@2428
   137
}
boredzo@2405
   138
boredzo@2405
   139
#pragma mark -
boredzo@2405
   140
#pragma mark Display control
boredzo@2405
   141
evands@3578
   142
- (BOOL)reposition_startingDisplay:(BOOL)shouldStartDisplay
evands@3578
   143
{
ingmarstein@2663
   144
	NSWindow *window = [self window];
evands@3645
   145
ofri@2658
   146
	//Make sure we don't cover any other notification (or not)
jkp@2841
   147
	BOOL foundSpace = NO;
jkp@2841
   148
	GrowlPositionController *pc = [GrowlPositionController sharedInstance];
jkp@2841
   149
	if ([self respondsToSelector:@selector(idealOriginInRect:)])
jkp@2841
   150
		foundSpace = [pc positionDisplay:self];
jkp@2841
   151
	else
Peter@4208
   152
		foundSpace = (ignoresOtherNotifications || [pc reserveRect:[window frame] forDisplayController:self]);
evands@3580
   153
jkp@2841
   154
	if (foundSpace) {
evands@3578
   155
		if (shouldStartDisplay) {
evands@3578
   156
			[self cancelDisplayDelayedPerforms];
evands@3829
   157
evands@3578
   158
			[self willDisplayNotification];
evands@3829
   159
evands@3578
   160
			[window orderFront:nil];
evands@3578
   161
			
evands@3578
   162
			if ([self startAllTransitions]) {
evands@3578
   163
				[self performSelector:@selector(didFinishTransitionsBeforeDisplay)
evands@3578
   164
						   withObject:nil
evands@3578
   165
						   afterDelay:transitionDuration];
evands@3578
   166
			} else {
evands@3578
   167
				[self didFinishTransitionsBeforeDisplay];
evands@3578
   168
			}
evands@3578
   169
			
evands@3578
   170
			[self didDisplayNotification];
rudy@2828
   171
		}
evands@3567
   172
		
evands@3567
   173
	} else {
ingmarstein@2943
   174
		[[NSNotificationCenter defaultCenter] postNotificationName:GrowlDisplayWindowControllerNotificationBlockedNotification
jkp@2741
   175
															object:self];
evands@3578
   176
		
evands@3567
   177
		//Try again in 10 seconds
evands@3578
   178
		if (!shouldStartDisplay) {
evands@3578
   179
			//If we're restarting, get this display off-screen while we wait
evands@3578
   180
			//XXX This should be more fluid
evands@3578
   181
			[window orderOut:nil];
evands@3578
   182
evands@3578
   183
			[[GrowlPositionController sharedInstance] clearReservedRectForDisplayController:self];
evands@3578
   184
			
evands@3578
   185
		}
evands@3567
   186
		[self performSelector:@selector(startDisplay) withObject:nil afterDelay:5];
evands@3567
   187
	}
evands@3578
   188
	
evands@3567
   189
	return foundSpace;		
evands@3578
   190
}
evands@3578
   191
evands@3578
   192
- (BOOL) startDisplay {
evands@3581
   193
	return [self reposition_startingDisplay:YES];
boredzo@2405
   194
}
boredzo@2405
   195
evands@3641
   196
- (void) stopDisplay {	
evands@3641
   197
	id contentView = [[self window] contentView];
evands@3641
   198
	if ([contentView respondsToSelector:@selector(mouseOver)] &&
evands@3645
   199
		[contentView mouseOver] &&
evands@3645
   200
		!userRequestedClose) {
evands@3641
   201
		//The mouse is currently within the view; close when it exits
evands@3641
   202
		[contentView setCloseOnMouseExit:YES];
evands@3641
   203
rudy@2828
   204
	} else {
evands@3645
   205
		//If we're already transitioning out, just keep doing our thing
evands@3645
   206
		if (displayStatus != GrowlDisplayTransitioningOutStatus) {
evands@3645
   207
			[self cancelDisplayDelayedPerforms];
evands@3829
   208
evands@3645
   209
			[self willTakeDownNotification];
evands@3645
   210
			if ([self startAllTransitions]) {
evands@3645
   211
				[self performSelector:@selector(didFinishTransitionsAfterDisplay) 
evands@3645
   212
						   withObject:nil
evands@3645
   213
						   afterDelay:transitionDuration];
evands@3645
   214
			} else {
evands@3645
   215
				[self didFinishTransitionsAfterDisplay];
evands@3645
   216
			}
evands@3641
   217
		}
evands@3645
   218
	}
evands@3645
   219
}
evands@3645
   220
evands@3645
   221
- (void) clickedClose {
evands@3645
   222
	userRequestedClose = YES;
evands@3645
   223
	[self stopDisplay];
boredzo@2405
   224
}
boredzo@2405
   225
boredzo@2405
   226
#pragma mark -
boredzo@2405
   227
#pragma mark Display stages
boredzo@2405
   228
evands@3499
   229
- (void)cancelDisplayDelayedPerforms
evands@3499
   230
{
evands@3499
   231
	[[self class] cancelPreviousPerformRequestsWithTarget:self
evands@3499
   232
												 selector:@selector(didFinishTransitionsBeforeDisplay) 
evands@3499
   233
												   object:nil];
evands@3499
   234
	
evands@3499
   235
	[[self class] cancelPreviousPerformRequestsWithTarget:self
evands@3499
   236
												 selector:@selector(didFinishTransitionsAfterDisplay) 
evands@3499
   237
												   object:nil];
evands@3499
   238
evands@3499
   239
	[[self class] cancelPreviousPerformRequestsWithTarget:self
evands@3499
   240
												 selector:@selector(stopDisplay) 
evands@3499
   241
												   object:nil];	
evands@3499
   242
}
evands@3499
   243
boredzo@2405
   244
- (void) willDisplayNotification {
evands@3576
   245
	displayStatus = GrowlDisplayTransitioningInStatus;
evands@3576
   246
ingmarstein@2943
   247
	[[NSNotificationCenter defaultCenter] postNotificationName:GrowlDisplayWindowControllerWillDisplayWindowNotification
jkp@2741
   248
														object:self];
ingmarstein@2663
   249
}
ingmarstein@2663
   250
jkp@2816
   251
- (void) didFinishTransitionsBeforeDisplay {
evands@3499
   252
	[self cancelDisplayDelayedPerforms];
evands@3499
   253
evands@3580
   254
	if (![[[notification auxiliaryDictionary] objectForKey:GROWL_NOTIFICATION_STICKY] boolValue] ||
evands@3580
   255
		![self supportsStickyNotifications]) {
evands@3499
   256
		[self performSelector:@selector(stopDisplay)
evands@3499
   257
				   withObject:nil
evands@3499
   258
				   afterDelay:(displayDuration+transitionDuration)];		
evands@3499
   259
	}
evands@3576
   260
	
evands@3576
   261
	displayStatus = GrowlDisplayOnScreenStatus;
jkp@2816
   262
}
jkp@2816
   263
jkp@2816
   264
- (void) didFinishTransitionsAfterDisplay {
evands@3499
   265
	[self cancelDisplayDelayedPerforms];
evands@3499
   266
jkp@2816
   267
	//Clear the rect we reserved...
jkp@2816
   268
	NSWindow *window = [self window];
jkp@2816
   269
	[window orderOut:nil];
evands@3541
   270
evands@3541
   271
	//Release all window transitions immediately; they may have retained our window.
evands@3645
   272
	[self stopAllTransitions];
evands@3541
   273
	[windowTransitions release]; windowTransitions = nil;
evands@3541
   274
evands@3517
   275
	[[GrowlPositionController sharedInstance] clearReservedRectForDisplayController:self];
rudy@3135
   276
evands@3683
   277
	[self didTakeDownNotification];
evands@3683
   278
rudy@3135
   279
	if ((bridge) && ([bridge respondsToSelector:@selector(display)]))
rudy@3135
   280
		[[bridge display] displayWindowControllerDidTakeDownWindow:self];
evands@3499
   281
	else {
evands@3499
   282
		NSLog(@"%@ bridge does not respond to display",bridge);
evands@3499
   283
	}
jkp@2816
   284
}
jkp@2816
   285
ingmarstein@2663
   286
- (void) didDisplayNotification {
boredzo@2405
   287
	if (screenshotMode)
boredzo@2405
   288
		[self takeScreenshot];
boredzo@2405
   289
ingmarstein@2943
   290
	[[NSNotificationCenter defaultCenter] postNotificationName:GrowlDisplayWindowControllerDidDisplayWindowNotification
jkp@2741
   291
														object:self];
ingmarstein@2663
   292
}
ingmarstein@2663
   293
boredzo@2405
   294
- (void) willTakeDownNotification {
ingmarstein@2943
   295
	[[NSNotificationCenter defaultCenter] postNotificationName:GrowlDisplayWindowControllerWillTakeWindowDownNotification
jkp@2741
   296
														object:self];
evands@3576
   297
	displayStatus = GrowlDisplayTransitioningOutStatus;
ingmarstein@2663
   298
}
ingmarstein@2663
   299
ingmarstein@2663
   300
- (void) didTakeDownNotification {
jkp@2741
   301
	NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
boredzo@2405
   302
	if (clickContext) {
jkp@2741
   303
		NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] initWithCapacity:2U];
jkp@2741
   304
		[userInfo setValue:clickContext forKey:GROWL_KEY_CLICKED_CONTEXT];
ingmarstein@2663
   305
		if (appPid)
jkp@2741
   306
			[userInfo setValue:appPid forKey:GROWL_APP_PID];
jkp@2741
   307
		[nc postNotificationName:GROWL_NOTIFICATION_TIMED_OUT object:appName userInfo:userInfo];
jkp@2741
   308
		[userInfo release];
boredzo@2405
   309
boredzo@2405
   310
		//Avoid duplicate click messages by immediately clearing the clickContext
boredzo@2405
   311
		clickContext = nil;
boredzo@2405
   312
	}
evands@3499
   313
	[nc postNotificationName:GrowlDisplayWindowControllerDidTakeWindowDownNotification object:self];
evands@3499
   314
}
boredzo@2405
   315
#pragma mark -
boredzo@2405
   316
#pragma mark Click feedback
boredzo@2405
   317
ingmarstein@2663
   318
- (void) notificationClicked:(id)sender {
boredzo@2405
   319
#pragma unused(sender)
boredzo@2405
   320
	if (clickContext) {
jkp@2741
   321
		NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] initWithCapacity:3U];
jkp@2741
   322
		[userInfo setValue:clickHandlerEnabled forKey:@"ClickHandlerEnabled"];
jkp@2741
   323
		[userInfo setValue:clickContext forKey:GROWL_KEY_CLICKED_CONTEXT];
ingmarstein@2663
   324
		if (appPid)
jkp@2741
   325
			[userInfo setValue:appPid forKey:GROWL_APP_PID];
ingmarstein@2943
   326
		[[NSNotificationCenter defaultCenter] postNotificationName:GROWL_NOTIFICATION_CLICKED
jkp@2741
   327
															object:appName
jkp@2741
   328
														  userInfo:userInfo];
jkp@2741
   329
		[userInfo release];
boredzo@2405
   330
boredzo@2405
   331
		//Avoid duplicate click messages by immediately clearing the clickContext
ingmarstein@2663
   332
		clickContext = nil;
boredzo@2405
   333
	}
boredzo@2405
   334
boredzo@2405
   335
	if (target && action && [target respondsToSelector:action])
boredzo@2405
   336
		[target performSelector:action withObject:self];
ingmarstein@3009
   337
evands@3645
   338
	//Now that we've notified the clickContext and target, it's as if the user just clicked the close button
evands@3645
   339
	[self clickedClose];
boredzo@2405
   340
}
boredzo@2405
   341
boredzo@2405
   342
#pragma mark -
ofri@2503
   343
#pragma mark Window Transitions
ofri@2503
   344
jkp@2744
   345
- (BOOL) addTransition:(GrowlWindowTransition *)transition {
ofri@2503
   346
	[transition setWindow:[self window]];
ofri@2503
   347
	[transition setDelegate:self];
ingmarstein@3009
   348
	if (![windowTransitions objectForKey:[transition class]]) {
jkp@2744
   349
		[windowTransitions setObject:transition forKey:[transition class]];
jkp@2744
   350
		return TRUE;
jkp@2744
   351
	}
jkp@2744
   352
	return FALSE;
ofri@2503
   353
}
ofri@2503
   354
ofri@2503
   355
- (void) removeTransition:(GrowlWindowTransition *)transition {
ofri@2503
   356
	[transition setDelegate:nil];
ofri@2503
   357
	[transition setWindow:nil];
evands@3541
   358
	
evands@3541
   359
	[windowTransitions removeObjectForKey:[transition class]];
ofri@2503
   360
}
ofri@2503
   361
jkp@2816
   362
- (void) setStartPercentage:(unsigned)start endPercentage:(unsigned)end forTransition:(GrowlWindowTransition *)transition {
ingmarstein@2943
   363
	NSAssert1((start <= 100U || start < end),
jkp@2816
   364
			  @"The start parameter was invalid for the transition: %@",
jkp@2816
   365
			  transition);
ingmarstein@2943
   366
	NSAssert1((end <= 100U || start < end),
jkp@2816
   367
			  @"The end parameter was invalid for the transition: %@",
jkp@2816
   368
			  transition);
ingmarstein@2943
   369
jkp@2816
   370
	NSMapInsert(startTimes, transition, (void *)start);
jkp@2816
   371
	NSMapInsert(endTimes, transition, (void *)end);
jkp@2816
   372
}
jkp@2816
   373
jkp@2816
   374
#pragma mark-
jkp@2816
   375
ofri@2503
   376
- (NSArray *) allTransitions {
evands@3522
   377
	return [windowTransitions allValues];
ofri@2503
   378
}
ofri@2503
   379
ofri@2503
   380
- (NSArray *) activeTransitions {
jkp@2741
   381
	int count = [windowTransitions count];
jkp@2741
   382
	NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];
jkp@2744
   383
	NSArray *transitionArray = [windowTransitions allValues];
jkp@2741
   384
jkp@2741
   385
	int i;
jkp@2741
   386
	for (i=0; i<count; ++i) {
jkp@2744
   387
		GrowlWindowTransition *transition = [transitionArray objectAtIndex:i];
ofri@2503
   388
		if ([transition isAnimating])
jkp@2741
   389
			[result addObject:transition];
jkp@2741
   390
	}
jkp@2741
   391
evands@3522
   392
	return result;
ofri@2503
   393
}
ofri@2503
   394
ofri@2503
   395
- (NSArray *) inactiveTransitions {
jkp@2741
   396
	int count = [windowTransitions count];
jkp@2741
   397
	NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];
jkp@2744
   398
	NSArray *transitionArray = [windowTransitions allValues];
jkp@2741
   399
jkp@2741
   400
	int i;
jkp@2741
   401
	for (i=0; i<count; ++i) {
jkp@2744
   402
		GrowlWindowTransition *transition = [transitionArray objectAtIndex:i];
ofri@2503
   403
		if (![transition isAnimating])
ingmarstein@2943
   404
			[result addObject:transition];
jkp@2741
   405
	}
jkp@2741
   406
evands@3522
   407
	return result;
ofri@2503
   408
}
ofri@2503
   409
jkp@2816
   410
- (BOOL) startAllTransitions {
jkp@2816
   411
	BOOL result = NO;
jkp@2816
   412
	GrowlWindowTransition *transition;
jkp@2816
   413
	NSEnumerator *transitionEnum = [[windowTransitions allValues] objectEnumerator];
jkp@2816
   414
jkp@2816
   415
	while ((transition = [transitionEnum nextObject]))
jkp@2816
   416
		if ([self startTransition:transition])
jkp@2816
   417
			result = YES;
jkp@2816
   418
	return result;
jkp@2816
   419
}
jkp@2816
   420
jkp@2816
   421
- (BOOL) startTransition:(GrowlWindowTransition *)transition {
jkp@2816
   422
	int startPercentage = (int) NSMapGet(startTimes, transition);
jkp@2816
   423
	int endPercentage   = (int) NSMapGet(endTimes, transition);
ingmarstein@2943
   424
jkp@2816
   425
	// If there were no times set up then the end time would be NULL (0)...
jkp@2816
   426
	if (endPercentage == 0)
jkp@2816
   427
		return NO;
ingmarstein@2943
   428
jkp@2816
   429
	// Work out the start and the end times...
evands@3829
   430
	CFTimeInterval startTime = (float)startPercentage * ((float)transitionDuration * 0.01);
evands@3829
   431
	CFTimeInterval endTime = (float)endPercentage * ((float)transitionDuration * 0.01);
ingmarstein@2943
   432
jkp@2816
   433
	// Set up this transition...
jkp@2816
   434
	[transition setDuration: (endTime - startTime)];
evands@3499
   435
	[transition performSelector:@selector(startAnimation) 
evands@3499
   436
					 withObject:nil
evands@3499
   437
					 afterDelay:startTime];
ingmarstein@2943
   438
jkp@2816
   439
	return YES;
jkp@2816
   440
}
jkp@2816
   441
jkp@2816
   442
- (BOOL) startTransitionOfKind:(Class)transitionClass {
jkp@2744
   443
	GrowlWindowTransition *transition = [windowTransitions objectForKey:transitionClass];
jkp@2744
   444
	if (transition)
jkp@2816
   445
		return [self startTransition:transition];
jkp@2816
   446
	return NO;
ofri@2503
   447
}
ofri@2503
   448
ofri@2503
   449
- (void) stopAllTransitions {
jkp@2816
   450
	GrowlWindowTransition *transition;
jkp@2816
   451
	NSEnumerator *transitionEnum = [[windowTransitions allValues] objectEnumerator];
jkp@2816
   452
	while (( transition = [transitionEnum nextObject] ))
jkp@2816
   453
		[self stopTransition:transition];
jkp@2816
   454
}
jkp@2816
   455
jkp@2816
   456
- (void) stopTransition:(GrowlWindowTransition *)transition {
jkp@2816
   457
	[transition stopAnimation];
evands@3541
   458
	[self removeTransition:transition];
evands@3541
   459
evands@3499
   460
	[[self class] cancelPreviousPerformRequestsWithTarget:transition
evands@3499
   461
												 selector:@selector(startAnimation)
evands@3499
   462
												   object:nil];
jkp@2744
   463
}
jkp@2744
   464
jkp@2744
   465
- (void) stopTransitionOfKind:(Class)transitionClass {
jkp@2744
   466
	GrowlWindowTransition *transition = [windowTransitions objectForKey:transitionClass];
jkp@2744
   467
	if (transition)
jkp@2816
   468
		[self stopTransition:transition];
ofri@2503
   469
}
ofri@2503
   470
ingmarstein@3825
   471
- (void) animationDidEnd:(NSAnimation *)animation
evands@3524
   472
{
evands@3524
   473
	if ([animation isKindOfClass:[GrowlWindowTransition class]] &&
evands@3524
   474
		([(GrowlWindowTransition *)animation window] == [self window]) &&
rudy@3823
   475
		([(GrowlWindowTransition *)animation direction] == GrowlReverseTransition)) {
evands@3524
   476
		//A fade out nonrepeating animation finished. We don't need to wait on our timeout; we know we finished displaying a notification.
evands@3524
   477
		[self didFinishTransitionsAfterDisplay];
evands@3524
   478
	}
ingmarstein@3825
   479
}
ingmarstein@3825
   480
ingmarstein@3825
   481
- (void) reverseAllTransitions
evands@3576
   482
{
evands@3576
   483
	[[windowTransitions allValues] makeObjectsPerformSelector:@selector(reverse)];
evands@3576
   484
}
evands@3576
   485
evands@3576
   486
#pragma mark -
ingmarstein@3825
   487
- (void) mouseEnteredNotificationView:(GrowlNotificationView *)notificationView
evands@3645
   488
{
evands@3645
   489
#pragma unused (notificationView)
evands@3645
   490
	if (!userRequestedClose &&
evands@3645
   491
		(displayStatus == GrowlDisplayTransitioningOutStatus)) {
evands@3645
   492
		// We're transitioning out; we need to go back to transitioning in...
evands@3645
   493
		[self willDisplayNotification];
evands@3645
   494
		[self reverseAllTransitions];
evands@3645
   495
		[self didFinishTransitionsBeforeDisplay];
evands@3645
   496
evands@3645
   497
		// ...but when the mouse leaves, transition out again
evands@3645
   498
		[self stopDisplay];
evands@3645
   499
	}
evands@3645
   500
}
evands@3645
   501
ingmarstein@3825
   502
- (void) mouseExitedNotificationView:(GrowlNotificationView *)notificationView
evands@3645
   503
{
evands@3645
   504
#pragma unused (notificationView)
evands@3645
   505
	// Notifies us that the mouse left the notification view.
evands@3645
   506
}
evands@3645
   507
evands@3645
   508
#pragma mark -
evands@3576
   509
#pragma mark Notifications
boredzo@2405
   510
ingmarstein@2943
   511
- (GrowlApplicationNotification *) notification {
ingmarstein@2943
   512
	// Only here for binding conformance
ingmarstein@2943
   513
    return notification;
ingmarstein@2943
   514
}
ingmarstein@2943
   515
ingmarstein@2943
   516
- (void) setNotification:(GrowlApplicationNotification *)theNotification {
ingmarstein@3009
   517
    if (notification != theNotification) {
ingmarstein@3009
   518
		[notification release];
evands@3536
   519
		notification = [theNotification retain];
ingmarstein@3009
   520
	}
eridius@2773
   521
}
eridius@2773
   522
evands@3576
   523
- (void) updateToNotification:(GrowlApplicationNotification *)theNotification {
evands@3576
   524
	[self setNotification:theNotification];
evands@3576
   525
evands@3576
   526
	switch (displayStatus) {
evands@3576
   527
		case GrowlDisplayUnknownStatus:
evands@3576
   528
		case GrowlDisplayTransitioningInStatus:
evands@3576
   529
			//Do nothing; we're still transitioning in
evands@3576
   530
			break;
evands@3576
   531
			
evands@3576
   532
		case GrowlDisplayOnScreenStatus:
evands@3576
   533
			//We're on screen; reset our timer for transitioning out
evands@3576
   534
			[self didFinishTransitionsBeforeDisplay];
evands@3576
   535
			break;
evands@3576
   536
			
evands@3576
   537
		case GrowlDisplayTransitioningOutStatus:
evands@3645
   538
			//Reset userRequestedClose in case we were transitioning out via the user's request; we have new information!
evands@3645
   539
			userRequestedClose = NO;
evands@3645
   540
evands@3576
   541
			[self willDisplayNotification];
evands@3576
   542
			[self reverseAllTransitions];
evands@3576
   543
			[self didFinishTransitionsBeforeDisplay];
evands@3580
   544
evands@3576
   545
			break;
evands@3576
   546
	}
evands@3578
   547
evands@3578
   548
	[self reposition_startingDisplay:NO];
evands@3576
   549
}
evands@3576
   550
eridius@2773
   551
#pragma mark -
eridius@2773
   552
ingmarstein@2943
   553
- (GrowlNotificationDisplayBridge *) bridge {
ingmarstein@2943
   554
    return bridge;
ingmarstein@2943
   555
}
ingmarstein@2943
   556
ingmarstein@3036
   557
- (void) setBridge:(GrowlNotificationDisplayBridge *)theBridge {
evands@3499
   558
	if (bridge != theBridge) {
evands@3499
   559
		if (bridge) {
evands@3499
   560
			NSLog(@"*** This may be an error. %@ had its bridge reset", self);
evands@3499
   561
			[bridge removeObserver:self forKeyPath:@"notification"];
evands@3499
   562
		}
evands@3499
   563
		
evands@3499
   564
		bridge = [theBridge retain];
evands@3499
   565
		
evands@3499
   566
		[bridge addObserver:self forKeyPath:@"notification" options:NSKeyValueObservingOptionNew context:NULL];
evands@3499
   567
		[self observeValueForKeyPath:@"notification" ofObject:bridge change:nil context:NULL];
evands@3499
   568
	}
evands@3499
   569
}
evands@3499
   570
evands@3499
   571
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
evands@3499
   572
{
evands@3499
   573
#pragma unused(change)
evands@3499
   574
#pragma unused(context)
evands@3499
   575
	if ((object == bridge) &&
evands@3499
   576
		[keyPath isEqualToString:@"notification"]) {
evands@3499
   577
		[self setNotification:[bridge notification]];
evands@3499
   578
	}
eridius@2773
   579
}
eridius@2773
   580
eridius@2773
   581
#pragma mark -
eridius@2773
   582
jkp@2816
   583
- (CFTimeInterval) transitionDuration {
jkp@2816
   584
    return transitionDuration;
jkp@2816
   585
}
jkp@2816
   586
ingmarstein@3036
   587
- (void) setTransitionDuration:(CFTimeInterval)theTransitionDuration{
jkp@2816
   588
    transitionDuration = theTransitionDuration;
jkp@2816
   589
}
jkp@2816
   590
jkp@2816
   591
#pragma mark -
jkp@2816
   592
ingmarstein@2663
   593
- (CFTimeInterval) displayDuration {
boredzo@2405
   594
	return displayDuration;
boredzo@2405
   595
}
boredzo@2405
   596
ingmarstein@2663
   597
- (void) setDisplayDuration:(CFTimeInterval)newDuration {
boredzo@2405
   598
	displayDuration = newDuration;
boredzo@2405
   599
}
boredzo@2405
   600
boredzo@2405
   601
#pragma mark -
boredzo@2405
   602
boredzo@2405
   603
- (BOOL) screenshotModeEnabled {
boredzo@2405
   604
	return screenshotMode;
boredzo@2405
   605
}
boredzo@2405
   606
ingmarstein@2663
   607
- (void) setScreenshotModeEnabled:(BOOL)newScreenshotMode {
boredzo@2405
   608
	screenshotMode = newScreenshotMode;
boredzo@2405
   609
}
boredzo@2405
   610
boredzo@2405
   611
#pragma mark -
boredzo@2405
   612
boredzo@2405
   613
- (NSScreen *) screen {
boredzo@2405
   614
	NSArray *screens = [NSScreen screens];
boredzo@2405
   615
	if (screenNumber < [screens count])
boredzo@2405
   616
		return [screens objectAtIndex:screenNumber];
boredzo@2405
   617
	else
boredzo@2405
   618
		return [NSScreen mainScreen];
boredzo@2405
   619
}
ingmarstein@2663
   620
ingmarstein@2663
   621
- (void) setScreen:(NSScreen *)newScreen {
boredzo@2405
   622
	unsigned newScreenNumber = [[NSScreen screens] indexOfObjectIdenticalTo:newScreen];
ingmarstein@2416
   623
	if (newScreenNumber == NSNotFound)
boredzo@2405
   624
		[NSException raise:NSInternalInconsistencyException format:@"Tried to set %@ %p to a screen %p that isn't in the screen list", [self class], self, newScreen];
eridius@2773
   625
	[self setScreenNumber:newScreenNumber];
eridius@2773
   626
}
eridius@2773
   627
eridius@2773
   628
- (void) setScreenNumber:(unsigned)newScreenNumber {
boredzo@2405
   629
	[self willChangeValueForKey:@"screenNumber"];
boredzo@2405
   630
	screenNumber = newScreenNumber;
boredzo@2405
   631
	[self  didChangeValueForKey:@"screenNumber"];
boredzo@2405
   632
}
boredzo@2405
   633
evands@3580
   634
- (BOOL)supportsStickyNotifications
evands@3580
   635
{
evands@3580
   636
	return ![[self window] ignoresMouseEvents];
evands@3580
   637
}
evands@3580
   638
boredzo@2405
   639
#pragma mark -
boredzo@2405
   640
boredzo@2405
   641
- (id) target {
boredzo@2405
   642
	return target;
boredzo@2405
   643
}
boredzo@2405
   644
ingmarstein@2663
   645
- (void) setTarget:(id)object {
ingmarstein@2416
   646
	if (object != target) {
ingmarstein@2416
   647
		[target release];
ingmarstein@2416
   648
		target = [object retain];
ingmarstein@2416
   649
	}
boredzo@2405
   650
}
boredzo@2405
   651
boredzo@2405
   652
#pragma mark -
boredzo@2405
   653
boredzo@2405
   654
- (SEL) action {
boredzo@2405
   655
	return action;
boredzo@2405
   656
}
boredzo@2405
   657
boredzo@2405
   658
- (void) setAction:(SEL) selector {
boredzo@2405
   659
	action = selector;
boredzo@2405
   660
}
boredzo@2405
   661
boredzo@2405
   662
#pragma mark -
boredzo@2405
   663
boredzo@2405
   664
- (NSString *) notifyingApplicationName {
boredzo@2405
   665
	return appName;
boredzo@2405
   666
}
boredzo@2405
   667
ingmarstein@2663
   668
- (void) setNotifyingApplicationName:(NSString *)inAppName {
boredzo@2405
   669
	if (inAppName != appName) {
boredzo@2405
   670
		[appName release];
boredzo@2405
   671
		appName = [inAppName copy];
boredzo@2405
   672
	}
boredzo@2405
   673
}
boredzo@2405
   674
boredzo@2405
   675
#pragma mark -
boredzo@2405
   676
ingmarstein@2416
   677
- (NSNumber *) notifyingApplicationProcessIdentifier {
boredzo@2405
   678
	return appPid;
boredzo@2405
   679
}
boredzo@2405
   680
ingmarstein@2663
   681
- (void) setNotifyingApplicationProcessIdentifier:(NSNumber *)inAppPid {
boredzo@2405
   682
	if (inAppPid != appPid) {
ingmarstein@2416
   683
		[appPid release];
ingmarstein@2416
   684
		appPid = [inAppPid retain];
boredzo@2405
   685
	}
boredzo@2405
   686
}
boredzo@2405
   687
boredzo@2405
   688
#pragma mark -
boredzo@2405
   689
boredzo@2405
   690
- (id) clickContext {
boredzo@2405
   691
	return clickContext;
boredzo@2405
   692
}
boredzo@2405
   693
ingmarstein@2663
   694
- (void) setClickContext:(id)inClickContext {
ingmarstein@2663
   695
	if (clickContext != inClickContext) {
ingmarstein@2663
   696
		[clickContext release];
ingmarstein@2663
   697
		clickContext = [inClickContext retain];
ingmarstein@2663
   698
	}
boredzo@2405
   699
}
boredzo@2405
   700
boredzo@2405
   701
#pragma mark -
boredzo@2405
   702
ofri@2658
   703
- (BOOL) ignoresOtherNotifications {
ofri@2658
   704
	return ignoresOtherNotifications;
ofri@2658
   705
}
ofri@2658
   706
ingmarstein@2663
   707
- (void) setIgnoresOtherNotifications:(BOOL)flag {
ofri@2658
   708
	ignoresOtherNotifications = flag;
ofri@2658
   709
}
ofri@2658
   710
ofri@2658
   711
#pragma mark -
ofri@2658
   712
boredzo@2405
   713
- (id) delegate {
boredzo@2405
   714
	return delegate;
boredzo@2405
   715
}
ingmarstein@2663
   716
ingmarstein@2663
   717
- (void) setDelegate:(id)newDelegate {
boredzo@2405
   718
	if (delegate)
boredzo@2405
   719
		[self removeNotificationObserver:delegate];
boredzo@2405
   720
boredzo@2405
   721
	if (newDelegate)
boredzo@2405
   722
		[self addNotificationObserver:newDelegate];
boredzo@2405
   723
boredzo@2405
   724
	delegate = newDelegate;
boredzo@2405
   725
}
boredzo@2405
   726
boredzo@2405
   727
#pragma mark -
boredzo@2405
   728
ingmarstein@2416
   729
- (NSNumber *) clickHandlerEnabled {
ingmarstein@2416
   730
	return clickHandlerEnabled;
ingmarstein@2416
   731
}
ingmarstein@2416
   732
ingmarstein@2663
   733
- (void) setClickHandlerEnabled:(NSNumber *)flag {
ingmarstein@2416
   734
	if (flag != clickHandlerEnabled) {
ingmarstein@2416
   735
		[clickHandlerEnabled release];
ingmarstein@2416
   736
		clickHandlerEnabled = [flag retain];
ingmarstein@2416
   737
	}
ingmarstein@2416
   738
}
ingmarstein@2416
   739
ingmarstein@2416
   740
#pragma mark -
ingmarstein@2416
   741
ingmarstein@2663
   742
- (void) addNotificationObserver:(id)observer {
boredzo@2405
   743
	NSParameterAssert(observer != nil);
boredzo@2405
   744
boredzo@2405
   745
	NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
boredzo@2405
   746
boredzo@2405
   747
	if (observer) {
boredzo@2405
   748
		//register the new delegate.
ingmarstein@2416
   749
		if ([observer respondsToSelector:@selector(displayWindowControllerWillDisplayWindow:)])
boredzo@2405
   750
			[nc addObserver:observer
boredzo@2405
   751
				   selector:@selector(displayWindowControllerWillDisplayWindow:)
evands@3499
   752
					   name:GrowlDisplayWindowControllerWillDisplayWindowNotification
boredzo@2405
   753
					 object:self];
ingmarstein@2416
   754
		if ([observer respondsToSelector:@selector(displayWindowControllerDidDisplayWindow:)])
boredzo@2405
   755
			[nc addObserver:observer
boredzo@2405
   756
				   selector:@selector(displayWindowControllerDidDisplayWindow:)
evands@3499
   757
					   name:GrowlDisplayWindowControllerDidDisplayWindowNotification
boredzo@2405
   758
					 object:self];
ingmarstein@2416
   759
ingmarstein@2416
   760
		if ([observer respondsToSelector:@selector(displayWindowControllerWillTakeDownWindow:)])
boredzo@2405
   761
			[nc addObserver:observer
ofri@2719
   762
				   selector:@selector(displayWindowControllerWillTakeWindowDown:)
evands@3499
   763
					   name:GrowlDisplayWindowControllerWillTakeWindowDownNotification
boredzo@2405
   764
					 object:self];
ofri@2719
   765
		if ([observer respondsToSelector:@selector(displayWindowControllerDidTakeWindowDown:)])
boredzo@2405
   766
			[nc addObserver:observer
ofri@2719
   767
				   selector:@selector(displayWindowControllerDidTakeWindowDown:)
evands@3499
   768
					   name:GrowlDisplayWindowControllerDidTakeWindowDownNotification
boredzo@2405
   769
					 object:self];
ofri@2657
   770
		if ([observer respondsToSelector:@selector(displayWindowControllerNotificationBlocked:)])
ofri@2657
   771
			[nc addObserver:observer
ofri@2657
   772
				   selector:@selector(displayWindowControllerNotificationBlocked:)
evands@3499
   773
					   name:GrowlDisplayWindowControllerNotificationBlockedNotification
ofri@2657
   774
					 object:self];
boredzo@2405
   775
	}
boredzo@2405
   776
}
ingmarstein@2663
   777
- (void) removeNotificationObserver:(id)observer {
jkp@2741
   778
	[[NSNotificationCenter defaultCenter] removeObserver:observer];
boredzo@2405
   779
}
boredzo@2405
   780
boredzo@2405
   781
@end