Use initialize, not load, wherever possible.
[adiumx.git] / Source / AIDockController.m
blob6d3f68d84206ff254e1488738a675cfb86360f38
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
5  * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6  * General Public License as published by the Free Software Foundation; either version 2 of the License,
7  * or (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
11  * Public License for more details.
12  * 
13  * You should have received a copy of the GNU General Public License along with this program; if not,
14  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15  */
17 // $Id$
19 #import "AIDockController.h"
20 #import <Adium/AIInterfaceControllerProtocol.h>
21 #import <Adium/AIPreferenceControllerProtocol.h>
22 #import <AIUtilities/AIDictionaryAdditions.h>
23 #import <AIUtilities/AIFileManagerAdditions.h>
24 #import <AIUtilities/AIApplicationAdditions.h>
25 #import <Adium/AIIconState.h>
26 #import <Adium/IconFamily.h>
28 #define DOCK_DEFAULT_PREFS                      @"DockPrefs"
29 #define ICON_DISPLAY_DELAY                      0.1
31 #define LAST_ICON_UPDATE_VERSION        @"Adium:Last Icon Update Version"
33 #define CONTINUOUS_BOUNCE_INTERVAL  0
34 #define SINGLE_BOUNCE_INTERVAL          999
35 #define NO_BOUNCE_INTERVAL                      1000
37 @interface AIDockController (PRIVATE)
38 - (void)_setNeedsDisplay;
39 - (void)_buildIcon;
40 - (void)animateIcon:(NSTimer *)timer;
41 - (void)_singleBounce;
42 - (BOOL)_continuousBounce;
43 - (void)_stopBouncing;
44 - (BOOL)_bounceWithInterval:(double)delay;
45 - (void)preferencesChanged:(NSNotification *)notification;
46 - (AIIconState *)iconStateFromStateDict:(NSDictionary *)stateDict folderPath:(NSString *)folderPath;
47 - (void)updateAppBundleIcon;
48 @end
50 @implementation AIDockController
52 //init and close
53 - (id)init
55         if ((self = [super init])) {
56                 activeIconStateArray = [[NSMutableArray alloc] initWithObjects:@"Base",nil];
57                 availableDynamicIconStateDict = [[NSMutableDictionary alloc] init];
58                 currentIconState = nil;
59                 currentAttentionRequest = -1;
60                 currentBounceInterval = NO_BOUNCE_INTERVAL;
61                 animationTimer = nil;
62                 bounceTimer = nil;
63                 needsDisplay = NO;
64         }
65         
66         return self;
69 - (void)controllerDidLoad
71         NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
72         
73     //Register our default preferences
74     [[adium preferenceController] registerDefaults:[NSDictionary dictionaryNamed:DOCK_DEFAULT_PREFS
75                                                                                                                                                 forClass:[self class]] 
76                                                                                   forGroup:PREF_GROUP_APPEARANCE];
77     
78     //Observe pref changes
79         [[adium preferenceController] registerPreferenceObserver:self forGroup:PREF_GROUP_APPEARANCE];
80         
81     //We always want to stop bouncing when Adium is made active
82     [notificationCenter addObserver:self
83                                selector:@selector(appWillChangeActive:) 
84                                    name:NSApplicationWillBecomeActiveNotification 
85                                  object:nil];
86         
87     //We also stop bouncing when Adium is no longer active
88     [notificationCenter addObserver:self
89                                selector:@selector(appWillChangeActive:) 
90                                    name:NSApplicationWillResignActiveNotification 
91                                  object:nil];
92         
93         //If Adium has been upgraded since the last time we ran, re-apply the user's custom icon
94         NSString        *lastVersion = [[NSUserDefaults standardUserDefaults] objectForKey:LAST_ICON_UPDATE_VERSION];
95         if (![[NSApp applicationVersion] isEqualToString:lastVersion]) {
96                 [self updateAppBundleIcon];
97                 [[NSUserDefaults standardUserDefaults] setObject:[NSApp applicationVersion] forKey:LAST_ICON_UPDATE_VERSION];
98         }
101 - (void)controllerWillClose
103         [[adium preferenceController] unregisterPreferenceObserver:self];
105         NSArray                 *stateArrayCopy;
106         NSEnumerator    *enumerator;
107         NSString                *iconState;
109         //Reset our icon by removing all icon states (except for the base state)
110         stateArrayCopy = [activeIconStateArray copy]; //Work with a copy, since this array will change as we remove states
111         enumerator = [stateArrayCopy objectEnumerator];
112         [enumerator nextObject]; //Skip the first icon
113         while ((iconState = [enumerator nextObject])) {
114                 [self removeIconStateNamed:iconState];
115         }
117         //Force the icon to update
118         [self _buildIcon];
120         [stateArrayCopy release];
125  * @brief Returns an array of available dock icon pack paths
126  */
127 - (NSArray *)availableDockIconPacks
129         NSEnumerator * folderPathEnumerator = [[adium allResourcesForName:FOLDER_DOCK_ICONS withExtensions:@"AdiumIcon"] objectEnumerator];
130         NSMutableArray * iconPackPaths = [NSMutableArray array]; //this will be the folder path for old packs, and the bundle resource path for new
131         NSString * path;
132         NSBundle * xtraBundle;
133         while ((path = [folderPathEnumerator nextObject])) {
134                 xtraBundle = [NSBundle bundleWithPath:path];
135                 if (xtraBundle && ([[xtraBundle objectForInfoDictionaryKey:@"XtraBundleVersion"] intValue] == 1))//This checks for a new-style xtra
136                         path = [xtraBundle resourcePath];
137                 [iconPackPaths addObject:path];
138         }
139         return iconPackPaths;
144 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
145                                                         object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
147         if (!key || [key isEqualToString:KEY_ACTIVE_DOCK_ICON]) {
148                 NSMutableDictionary     *newAvailableIconStateDict;
149                 NSString                        *iconPath;
150                 
151                 //Load the new icon pack
152                 iconPath = [adium pathOfPackWithName:[prefDict objectForKey:KEY_ACTIVE_DOCK_ICON]
153                                                                    extension:@"AdiumIcon"
154                                                   resourceFolderName:FOLDER_DOCK_ICONS];
156                 if (iconPath) {
157                         if ((newAvailableIconStateDict = [[self iconPackAtPath:iconPath] retain])) {
158                                 [availableIconStateDict release]; availableIconStateDict = newAvailableIconStateDict;
159                         }
160                 }
161                 
162                 //Write the icon to the Adium application bundle so finder will see it
163                 //On launch we only need to update the icon file if this is a new version of Adium.  When preferences
164                 //change we always want to update it
165                 if (!firstTime) {
166                         [self updateAppBundleIcon];
167                 }
169                 //Recomposite the icon
170                 [self _setNeedsDisplay];
171         }
174 - (void)updateAppBundleIcon
176         NSImage                 *image;
177         
178         image = [[[availableIconStateDict objectForKey:@"State"] objectForKey:@"Base"] image];
179         if (image) {
180                 [[NSWorkspace sharedWorkspace] setIcon:image 
181                                                                            forFile:[[NSBundle mainBundle] bundlePath]
182                                                                            options:0];
183                 
184                 //Finder won't update Adium's icon to match the new one until it is restarted if we don't
185                 //tell NSWorkspace to note the change.
186                 [[NSWorkspace sharedWorkspace] noteFileSystemChanged:[[NSBundle mainBundle] bundlePath]];
187         }
190 //Icons ------------------------------------------------------------------------------------
191 - (void)_setNeedsDisplay
193     if (!needsDisplay) {
194         needsDisplay = YES;
196         //Invoke a display after a short delay
197         [NSTimer scheduledTimerWithTimeInterval:ICON_DISPLAY_DELAY
198                                          target:self
199                                        selector:@selector(_buildIcon)
200                                        userInfo:nil
201                                         repeats:NO];
202     }
205 //Load an icon pack
206 - (NSMutableDictionary *)iconPackAtPath:(NSString *)folderPath
208     NSMutableDictionary *iconStateDict;
209     NSDictionary                *iconPackDict;
210     NSEnumerator                *stateNameKeyEnumerator;
211     NSString                    *stateNameKey;
213     //Load the icon pack
214     iconPackDict = [NSDictionary dictionaryWithContentsOfFile:[folderPath stringByAppendingPathComponent:@"IconPack.plist"]];
216     //Process each state in the icon pack, adding it to the iconStateDict
217         iconStateDict = [NSMutableDictionary dictionary];
218         
219     stateNameKeyEnumerator = [[[iconPackDict objectForKey:@"State"] allKeys] objectEnumerator];
220     while ((stateNameKey = [stateNameKeyEnumerator nextObject])) {
221                 NSDictionary    *stateDict;
222                 AIIconState             *iconState;
223                 
224                 stateDict = [[iconPackDict objectForKey:@"State"] objectForKey:stateNameKey];
225                 if ((iconState = [self iconStateFromStateDict:stateDict folderPath:folderPath])) {
226                         [iconStateDict setObject:iconState forKey:stateNameKey];
227                 }
228         }
229         
230         return [NSMutableDictionary dictionaryWithObjectsAndKeys:[iconPackDict objectForKey:@"Description"], @"Description", iconStateDict, @"State", nil];
234  * @brief Get the name and preview steate for a dock icon pack
236  * @param outName Reference to an NSString, or NULL if this information is not needed
237  * @param outIconState Reference to an AIIconState, or NULL if this information is not needed
238  * @param folderPath The path to the dock icon pack
239  */
240 - (void)getName:(NSString **)outName previewState:(AIIconState **)outIconState forIconPackAtPath:(NSString *)folderPath
242         NSDictionary    *iconPackDict;
243         NSDictionary    *stateDict;
244         
245         //Load the icon pack
246     iconPackDict = [NSDictionary dictionaryWithContentsOfFile:[folderPath stringByAppendingPathComponent:@"IconPack.plist"]];
247         
248         //Load the preview state
249         stateDict = [[iconPackDict objectForKey:@"State"] objectForKey:@"Preview"];
250         
251         if (outIconState) *outIconState = [self iconStateFromStateDict:stateDict folderPath:folderPath];
252         if (outName) *outName = [[iconPackDict objectForKey:@"Description"] objectForKey:@"Title"];
255 - (AIIconState *)previewStateForIconPackAtPath:(NSString *)folderPath
257         AIIconState     *previewState = nil;
258         
259         [self getName:NULL previewState:&previewState forIconPackAtPath:folderPath];
260         
261         return previewState;
264 - (AIIconState *)iconStateFromStateDict:(NSDictionary *)stateDict folderPath:(NSString *)folderPath
266         AIIconState             *iconState = nil;
267         
268         if ([[stateDict objectForKey:@"Animated"] intValue]) { //Animated State
269                 NSMutableDictionary     *tempIconCache = [NSMutableDictionary dictionary];
270                 NSArray                         *imageNameArray;
271                 NSEnumerator            *imageNameEnumerator;
272                 NSString                        *imageName;
273                 NSMutableArray          *imageArray;
274                 BOOL                            overlay, looping;
275                 float                           delay;
276                 
277                 //Get the state information
278                 overlay = [[stateDict objectForKey:@"Overlay"] boolValue];
279                 looping = [[stateDict objectForKey:@"Looping"] boolValue];
280                 delay   = [[stateDict objectForKey:@"Delay"]  floatValue];
281                 imageNameArray = [stateDict objectForKey:@"Images"];
282                 imageNameEnumerator = [imageNameArray objectEnumerator];
284                 //Load the images
285                 imageArray = [NSMutableArray arrayWithCapacity:[imageNameArray count]];
286                 while ((imageName = [imageNameEnumerator nextObject])) {
287                         NSString        *imagePath;
288                         NSImage         *image;
289                         
290 #define DOCK_ICON_INTERNAL_PATH @"../Shared Images/"
291                         if ([imageName hasPrefix:DOCK_ICON_INTERNAL_PATH]) {
292                                 //Special hack for all the incorrectly made icon packs we have floating around out there :P
293                                 imageName = [imageName substringFromIndex:[DOCK_ICON_INTERNAL_PATH length]];
294                                 imagePath = [[NSBundle mainBundle] pathForResource:[[[imageName stringByDeletingPathExtension] stringByAppendingString:@"-localized"] stringByAppendingPathExtension:[imageName pathExtension]]
295                                                                             ofType:@""
296                                                                        inDirectory:@"Shared Dock Icon Images"];
297                                 
298                                 if (!imagePath) {
299                                         imagePath = [[NSBundle mainBundle] pathForResource:imageName
300                                                                                                                                 ofType:@""
301                                                                                                                    inDirectory:@"Shared Dock Icon Images"];
302                                 }
304                         } else {
305                                 imagePath = [folderPath stringByAppendingPathComponent:imageName];
306                         }
307                         
308                         image = [tempIconCache objectForKey:imagePath]; //We re-use the same images for each state if possible to lower memory usage.
309                         if (!image && imagePath) {
310                                 image = [[[NSImage alloc] initByReferencingFile:imagePath] autorelease];
311                                 if (image) [tempIconCache setObject:image forKey:imagePath];
312                         }
313                         
314                         if (image) [imageArray addObject:image];
315                 }
316                 
317                 //Create the state
318                 if (delay != 0 && [imageArray count] != 0) {
319                         iconState = [[AIIconState alloc] initWithImages:imageArray
320                                                                                                           delay:delay
321                                                                                                         looping:looping
322                                                                                                         overlay:overlay];
323                 } else {
324                         NSLog(@"Invalid animated icon state (%@)",imageName);
325                 }
326                 
327         } else { //Static State
328                 NSString        *imageName;
329                 NSString        *imagePath;
330                 NSImage         *image;
331                 BOOL            overlay;
332                 
333                 imageName = [stateDict objectForKey:@"Image"];
334                 
335                 if ([imageName hasPrefix:DOCK_ICON_INTERNAL_PATH]) {
336                         //Special hack for all the incorrectly made icon packs we have floating around out there :P
337                         imageName = [imageName substringFromIndex:[DOCK_ICON_INTERNAL_PATH length]];
338                         imagePath = [[NSBundle mainBundle] pathForResource:[[[imageName stringByDeletingPathExtension] stringByAppendingString:@"-localized"] stringByAppendingPathExtension:[imageName pathExtension]]
339                                                                                                                 ofType:@""
340                                                                                                    inDirectory:@"Shared Dock Icon Images"];
341                         if (!imagePath) {
342                                 imagePath = [[NSBundle mainBundle] pathForResource:imageName
343                                                                     ofType:@""
344                                                                inDirectory:@"Shared Dock Icon Images"];
345                         }
346                 } else {
347                         imagePath = [folderPath stringByAppendingPathComponent:imageName];
348                 }
350                 //Get the state information
351                 image = [[NSImage alloc] initByReferencingFile:imagePath];
352                 overlay = [[stateDict objectForKey:@"Overlay"] boolValue];
353                 
354                 //Create the state
355                 iconState = [[AIIconState alloc] initWithImage:image overlay:overlay];          
356                 [image release];
357         }
359         return [iconState autorelease];
362 //Set an icon state from our currently loaded icon pack
363 - (void)setIconStateNamed:(NSString *)inName
365     if (![activeIconStateArray containsObject:inName]) {
366         [activeIconStateArray addObject:inName];        //Add the name to our array
367         [self _setNeedsDisplay];                        //Redisplay our icon
368     }
371 //Remove an active icon state
372 - (void)removeIconStateNamed:(NSString *)inName
374     if ([activeIconStateArray containsObject:inName]) {
375         [activeIconStateArray removeObject:inName];     //Remove the name from our array
376         
377         [self _setNeedsDisplay];                        //Redisplay our icon
378     }
382  * @brief Does the current icon know how to display a given state?
383  */
384 - (BOOL)currentIconSupportsIconStateNamed:(NSString *)inName
386         return ([[availableIconStateDict objectForKey:@"State"] objectForKey:inName] != nil);
389 //Set a custom icon state
390 - (void)setIconState:(AIIconState *)iconState named:(NSString *)inName
392     [availableDynamicIconStateDict setObject:iconState forKey:inName];  //Add the new state to our available dict
393     [self setIconStateNamed:inName];                                    //Set it
396 //Build/Pre-render the icon images, start/stop animation
397 - (void)_buildIcon
399     NSMutableArray      *iconStates = [NSMutableArray array];
400     NSDictionary        *availableIcons;
401     NSEnumerator        *enumerator;
402     AIIconState         *state;
403     NSString            *name;
405     //Stop any existing animation
406     [animationTimer invalidate]; [animationTimer release]; animationTimer = nil;
407     if (observingFlash) {
408         [[adium interfaceController] unregisterFlashObserver:self];
409         observingFlash = NO;
410     }
412     //Build an array of the valid active icon states
413     availableIcons = [availableIconStateDict objectForKey:@"State"];
414     enumerator = [activeIconStateArray objectEnumerator];
415     while ((name = [enumerator nextObject])) {
416         if ((state = [availableIcons objectForKey:name]) || (state = [availableDynamicIconStateDict objectForKey:name])) {
417             [iconStates addObject:state];
418         }
419     }
421     //Generate the composited icon state
422     [currentIconState release];
423     currentIconState = [[AIIconState alloc] initByCompositingStates:iconStates];
425     //
426     if (![currentIconState animated]) { //Static icon
427                 NSImage *image = [currentIconState image];
428         if (image) {
429                          [[NSApplication sharedApplication] setApplicationIconImage:image];
430                 }
432     } else { //Animated icon
433         //Our dock icon can run its animation at any speed, but we want to try and sync it with the global Adium flashing.  To do this, we delay starting our timer until the next flash occurs.
434         [[adium interfaceController] registerFlashObserver:self];
435         observingFlash = YES;
437         //Set the first frame of our animation
438         [self animateIcon:nil]; //Set the icon and move to the next frame
439     }
441     needsDisplay = NO;
444 - (void)flash:(int)value
446     //Start the flash timer
447     animationTimer = [[NSTimer scheduledTimerWithTimeInterval:[currentIconState animationDelay]
448                                                        target:self
449                                                      selector:@selector(animateIcon:)
450                                                      userInfo:nil
451                                                       repeats:YES] retain];
453     //Animate the icon
454     [self animateIcon:animationTimer]; //Set the icon and move to the next frame
456     //Once our animations stops, we no longer need to observe flashing
457     [[adium interfaceController] unregisterFlashObserver:self];
458     observingFlash = NO;
461 //Move the dock to the next animation frame (Assumes the current state is animated)
462 - (void)animateIcon:(NSTimer *)timer
464     NSImage     *image;
466     //Move to the next image
467     if (timer) {
468         [currentIconState nextFrame];
469     }
470         
471     //Set the image
472     image = [currentIconState image];
473         if (image) {
474                 [[NSApplication sharedApplication] setApplicationIconImage:image];
475         }
476     
479 //returns the % of the dock icon's full size that it currently is (0.0 - 1.0)
480 - (float)dockIconScale
482     NSSize trueSize = [[NSScreen mainScreen] visibleFrame].size;
483     NSSize availableSize = [[NSScreen mainScreen] frame].size;
485     int dHeight = availableSize.height - trueSize.height;
486     int dWidth = availableSize.width - trueSize.width;
487     float dockScale = 0;
489     if (dHeight != 22) { //dock is on the bottom
490         if (dHeight == 26) { //dock is hidden
491         } else { //dock is not hidden
492             dockScale = (dHeight-22)/128.0;
493         }
494     } else if (dWidth != 0) { //dock is on the side
495         if (dWidth == 4) { //dock is hidden
496         } else { //dock is not hidden
497             dockScale = (dWidth)/128.0;
498         }
499     } else {
500         //multiple monitors?
501         //Add support for multiple monitors
502     }
504     if (dockScale <= 0 || dockScale > 1.0) {
505         dockScale = 0.3;
506     }
508     return dockScale;
512  * @brief Return the dock icon image without any auxiliary states
513  */
514 - (NSImage *)baseApplicationIconImage
516         NSDictionary    *availableIcons = [availableIconStateDict objectForKey:@"State"];
517         AIIconState             *baseState;
518         NSImage                 *baseApplicationIconImage;
520         if ((baseState = [availableIcons objectForKey:@"Base"])) {
521                 AIIconState             *iconState = [[[AIIconState alloc] initByCompositingStates:[NSArray arrayWithObject:baseState]] autorelease];
522                 baseApplicationIconImage = [iconState image];
523         } else {
524                 baseApplicationIconImage = nil;
525         }
526         
527         return baseApplicationIconImage;
530 //Bouncing -------------------------------------------------------------------------------------------------------------
531 #pragma mark Bouncing
534  * @brief Perform a bouncing behavior
536  * @result YES if the behavior is ongoing; NO if it isn't (because it is immediately complete or some other, faster continuous behavior is in progress)
537  */
538 - (BOOL)performBehavior:(AIDockBehavior)behavior
540         BOOL    ongoingBehavior = NO;
542     //Start up the new behavior
543     switch (behavior) {
544         case AIDockBehaviorStopBouncing: {
545                         [self _stopBouncing];
546                         break;
547                 }
548         case AIDockBehaviorBounceOnce: {
549                         if (currentBounceInterval >= SINGLE_BOUNCE_INTERVAL) {
550                                 currentBounceInterval = SINGLE_BOUNCE_INTERVAL;
551                                 [self _singleBounce];
552                         }
553                         break;
554                 }
555         case AIDockBehaviorBounceRepeatedly: ongoingBehavior = [self _continuousBounce]; break;
556         case AIDockBehaviorBounceDelay_FiveSeconds: ongoingBehavior = [self _bounceWithInterval:5.0]; break;
557         case AIDockBehaviorBounceDelay_TenSeconds: ongoingBehavior = [self _bounceWithInterval:10.0]; break;
558         case AIDockBehaviorBounceDelay_FifteenSeconds: ongoingBehavior = [self _bounceWithInterval:15.0]; break;
559         case AIDockBehaviorBounceDelay_ThirtySeconds: ongoingBehavior = [self _bounceWithInterval:30.0]; break;
560         case AIDockBehaviorBounceDelay_OneMinute: ongoingBehavior = [self _bounceWithInterval:60.0]; break;
561         default: break;
562     }
563         
564         return ongoingBehavior;
567 //Return a string description of the bouncing behavior
568 - (NSString *)descriptionForBehavior:(AIDockBehavior)behavior
570         NSString        *desc;
571         
572     switch (behavior) {
573         case AIDockBehaviorStopBouncing: desc = AILocalizedString(@"None",nil); break;
574         case AIDockBehaviorBounceOnce: desc = AILocalizedString(@"Once",nil); break;
575         case AIDockBehaviorBounceRepeatedly: desc = AILocalizedString(@"Repeatedly",nil); break;
576         case AIDockBehaviorBounceDelay_FiveSeconds: desc = AILocalizedString(@"Every 5 Seconds",nil); break;
577         case AIDockBehaviorBounceDelay_TenSeconds: desc = AILocalizedString(@"Every 10 Seconds",nil); break;
578         case AIDockBehaviorBounceDelay_FifteenSeconds: desc = AILocalizedString(@"Every 15 Seconds",nil); break;
579         case AIDockBehaviorBounceDelay_ThirtySeconds: desc = AILocalizedString(@"Every 30 Seconds",nil); break;
580         case AIDockBehaviorBounceDelay_OneMinute: desc = AILocalizedString(@"Every 60 Seconds",nil); break;
581         default: desc=@""; break;
582     }    
584         return desc;
588  * @brief Start a delayed, repeated bounce
590  * @result YES if we are now bouncing more frequently than before; NO if this call had no effect
591  */
592 - (BOOL)_bounceWithInterval:(NSTimeInterval)delay
594         BOOL    ongoingBehavior;
596         //Bounce only if the new delay is a faster bounce than the current one
597         if (delay < currentBounceInterval) {
598                 [self _singleBounce]; // do one right away
599                 
600                 currentBounceInterval = delay;
601                 
602                 bounceTimer = [[NSTimer scheduledTimerWithTimeInterval:delay
603                                                                                                                 target:self
604                                                                                                           selector:@selector(bounceWithTimer:)
605                                                                                                           userInfo:nil
606                                                                                                            repeats:YES] retain];
607                 
608                 ongoingBehavior = YES;
609         } else {
610                 ongoingBehavior = NO;
611         }
612         
613         return ongoingBehavior;
616 //Activated by the time after each delay
617 - (void)bounceWithTimer:(NSTimer *)timer
619     //Bounce
620     [self _singleBounce];
623 //Bounce once via NSApp's NSInformationalRequest (also used by the timer to perform a single bounce)
624 - (void)_singleBounce
626     if ([NSApp respondsToSelector:@selector(requestUserAttention:)]) {
627         currentAttentionRequest = [NSApp requestUserAttention:NSInformationalRequest];
628     }
632  * @brief Bounce continuously via NSApp's NSCriticalRequest
634  * We will bounce until we become the active application or our dock icon is clicked
636  * @result YES if we are now bouncing more frequently than before; NO if this call had no effect
637  */
638 - (BOOL)_continuousBounce
640         BOOL ongoingBehavior;
642         if (CONTINUOUS_BOUNCE_INTERVAL < currentBounceInterval) {
643                 currentBounceInterval = CONTINUOUS_BOUNCE_INTERVAL;
644                 if ([NSApp respondsToSelector:@selector(requestUserAttention:)]) {
645                         currentAttentionRequest = [NSApp requestUserAttention:NSCriticalRequest];
646                 }
648                 ongoingBehavior = YES;
649         } else {
650                 ongoingBehavior = NO;   
651         }
652         
653         return ongoingBehavior;
656 //Stop bouncing
657 - (void)_stopBouncing
659     //Stop any timer
660     if (bounceTimer) {
661         [bounceTimer invalidate]; [bounceTimer release]; bounceTimer = nil;
662     }
664     //Stop any continuous bouncing
665     if (currentAttentionRequest != -1) {
666         if ([NSApp respondsToSelector:@selector(cancelUserAttentionRequest:)]) {
667             [NSApp cancelUserAttentionRequest:currentAttentionRequest];
668         }
669         currentAttentionRequest = -1;
670     }
671         
672         currentBounceInterval = NO_BOUNCE_INTERVAL;
676 - (void)appWillChangeActive:(NSNotification *)notification
678     [self _stopBouncing]; //Stop any bouncing
681 @end