Fixed line width in the messages advanced prefs
[adiumx.git] / Source / AIDockController.m
blob20a2f114243986db156c2cef57e4fc8b40ff71fe
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:@"ApplicationIcon"] image];
179         if (!image) image = [[[availableIconStateDict objectForKey:@"State"] objectForKey:@"Base"] image];
181         if (image) {
182                 [[NSWorkspace sharedWorkspace] setIcon:image 
183                                                                            forFile:[[NSBundle mainBundle] bundlePath]
184                                                                            options:0];
186                 //Finder won't update Adium's icon to match the new one until it is restarted if we don't
187                 //tell NSWorkspace to note the change.
188                 [[NSWorkspace sharedWorkspace] noteFileSystemChanged:[[NSBundle mainBundle] bundlePath]];
189         }
192 //Icons ------------------------------------------------------------------------------------
193 - (void)_setNeedsDisplay
195     if (!needsDisplay) {
196         needsDisplay = YES;
198         //Invoke a display after a short delay
199         [NSTimer scheduledTimerWithTimeInterval:ICON_DISPLAY_DELAY
200                                          target:self
201                                        selector:@selector(_buildIcon)
202                                        userInfo:nil
203                                         repeats:NO];
204     }
207 //Load an icon pack
208 - (NSMutableDictionary *)iconPackAtPath:(NSString *)folderPath
210     NSMutableDictionary *iconStateDict;
211     NSDictionary                *iconPackDict;
212     NSEnumerator                *stateNameKeyEnumerator;
213     NSString                    *stateNameKey;
215     //Load the icon pack
216     iconPackDict = [NSDictionary dictionaryWithContentsOfFile:[folderPath stringByAppendingPathComponent:@"IconPack.plist"]];
218     //Process each state in the icon pack, adding it to the iconStateDict
219         iconStateDict = [NSMutableDictionary dictionary];
220         
221     stateNameKeyEnumerator = [[[iconPackDict objectForKey:@"State"] allKeys] objectEnumerator];
222     while ((stateNameKey = [stateNameKeyEnumerator nextObject])) {
223                 NSDictionary    *stateDict;
224                 AIIconState             *iconState;
225                 
226                 stateDict = [[iconPackDict objectForKey:@"State"] objectForKey:stateNameKey];
227                 if ((iconState = [self iconStateFromStateDict:stateDict folderPath:folderPath])) {
228                         [iconStateDict setObject:iconState forKey:stateNameKey];
229                 }
230         }
231         
232         return [NSMutableDictionary dictionaryWithObjectsAndKeys:[iconPackDict objectForKey:@"Description"], @"Description", iconStateDict, @"State", nil];
236  * @brief Get the name and preview steate for a dock icon pack
238  * @param outName Reference to an NSString, or NULL if this information is not needed
239  * @param outIconState Reference to an AIIconState, or NULL if this information is not needed
240  * @param folderPath The path to the dock icon pack
241  */
242 - (void)getName:(NSString **)outName previewState:(AIIconState **)outIconState forIconPackAtPath:(NSString *)folderPath
244         NSDictionary    *iconPackDict;
245         NSDictionary    *stateDict;
246         
247         //Load the icon pack
248     iconPackDict = [NSDictionary dictionaryWithContentsOfFile:[folderPath stringByAppendingPathComponent:@"IconPack.plist"]];
249         
250         //Load the preview state
251         stateDict = [[iconPackDict objectForKey:@"State"] objectForKey:@"Preview"];
252         
253         if (outIconState) *outIconState = [self iconStateFromStateDict:stateDict folderPath:folderPath];
254         if (outName) *outName = [[iconPackDict objectForKey:@"Description"] objectForKey:@"Title"];
257 - (AIIconState *)previewStateForIconPackAtPath:(NSString *)folderPath
259         AIIconState     *previewState = nil;
260         
261         [self getName:NULL previewState:&previewState forIconPackAtPath:folderPath];
262         
263         return previewState;
266 - (AIIconState *)iconStateFromStateDict:(NSDictionary *)stateDict folderPath:(NSString *)folderPath
268         AIIconState             *iconState = nil;
269         
270         if ([[stateDict objectForKey:@"Animated"] intValue]) { //Animated State
271                 NSMutableDictionary     *tempIconCache = [NSMutableDictionary dictionary];
272                 NSArray                         *imageNameArray;
273                 NSEnumerator            *imageNameEnumerator;
274                 NSString                        *imageName;
275                 NSMutableArray          *imageArray;
276                 BOOL                            overlay, looping;
277                 float                           delay;
278                 
279                 //Get the state information
280                 overlay = [[stateDict objectForKey:@"Overlay"] boolValue];
281                 looping = [[stateDict objectForKey:@"Looping"] boolValue];
282                 delay   = [[stateDict objectForKey:@"Delay"]  floatValue];
283                 imageNameArray = [stateDict objectForKey:@"Images"];
284                 imageNameEnumerator = [imageNameArray objectEnumerator];
286                 //Load the images
287                 imageArray = [NSMutableArray arrayWithCapacity:[imageNameArray count]];
288                 while ((imageName = [imageNameEnumerator nextObject])) {
289                         NSString        *imagePath;
290                         NSImage         *image;
291                         
292 #define DOCK_ICON_INTERNAL_PATH @"../Shared Images/"
293                         if ([imageName hasPrefix:DOCK_ICON_INTERNAL_PATH]) {
294                                 //Special hack for all the incorrectly made icon packs we have floating around out there :P
295                                 imageName = [imageName substringFromIndex:[DOCK_ICON_INTERNAL_PATH length]];
296                                 imagePath = [[NSBundle mainBundle] pathForResource:[[[imageName stringByDeletingPathExtension] stringByAppendingString:@"-localized"] stringByAppendingPathExtension:[imageName pathExtension]]
297                                                                             ofType:@""
298                                                                        inDirectory:@"Shared Dock Icon Images"];
299                                 
300                                 if (!imagePath) {
301                                         imagePath = [[NSBundle mainBundle] pathForResource:imageName
302                                                                                                                                 ofType:@""
303                                                                                                                    inDirectory:@"Shared Dock Icon Images"];
304                                 }
306                         } else {
307                                 imagePath = [folderPath stringByAppendingPathComponent:imageName];
308                         }
309                         
310                         image = [tempIconCache objectForKey:imagePath]; //We re-use the same images for each state if possible to lower memory usage.
311                         if (!image && imagePath) {
312                                 image = [[[NSImage alloc] initByReferencingFile:imagePath] autorelease];
313                                 if (image) [tempIconCache setObject:image forKey:imagePath];
314                         }
315                         
316                         if (image) [imageArray addObject:image];
317                 }
318                 
319                 //Create the state
320                 if (delay != 0 && [imageArray count] != 0) {
321                         iconState = [[AIIconState alloc] initWithImages:imageArray
322                                                                                                           delay:delay
323                                                                                                         looping:looping
324                                                                                                         overlay:overlay];
325                 } else {
326                         NSLog(@"Invalid animated icon state (%@)",imageName);
327                 }
328                 
329         } else { //Static State
330                 NSString        *imageName;
331                 NSString        *imagePath;
332                 NSImage         *image;
333                 BOOL            overlay;
334                 
335                 imageName = [stateDict objectForKey:@"Image"];
336                 
337                 if ([imageName hasPrefix:DOCK_ICON_INTERNAL_PATH]) {
338                         //Special hack for all the incorrectly made icon packs we have floating around out there :P
339                         imageName = [imageName substringFromIndex:[DOCK_ICON_INTERNAL_PATH length]];
340                         imagePath = [[NSBundle mainBundle] pathForResource:[[[imageName stringByDeletingPathExtension] stringByAppendingString:@"-localized"] stringByAppendingPathExtension:[imageName pathExtension]]
341                                                                                                                 ofType:@""
342                                                                                                    inDirectory:@"Shared Dock Icon Images"];
343                         if (!imagePath) {
344                                 imagePath = [[NSBundle mainBundle] pathForResource:imageName
345                                                                     ofType:@""
346                                                                inDirectory:@"Shared Dock Icon Images"];
347                         }
348                 } else {
349                         imagePath = [folderPath stringByAppendingPathComponent:imageName];
350                 }
352                 //Get the state information
353                 image = [[NSImage alloc] initByReferencingFile:imagePath];
354                 overlay = [[stateDict objectForKey:@"Overlay"] boolValue];
355                 
356                 //Create the state
357                 iconState = [[AIIconState alloc] initWithImage:image overlay:overlay];          
358                 [image release];
359         }
361         return [iconState autorelease];
364 //Set an icon state from our currently loaded icon pack
365 - (void)setIconStateNamed:(NSString *)inName
367     if (![activeIconStateArray containsObject:inName]) {
368         [activeIconStateArray addObject:inName];        //Add the name to our array
369         [self _setNeedsDisplay];                        //Redisplay our icon
370     }
373 //Remove an active icon state
374 - (void)removeIconStateNamed:(NSString *)inName
376     if ([activeIconStateArray containsObject:inName]) {
377         [activeIconStateArray removeObject:inName];     //Remove the name from our array
378         
379         [self _setNeedsDisplay];                        //Redisplay our icon
380     }
384  * @brief Does the current icon know how to display a given state?
385  */
386 - (BOOL)currentIconSupportsIconStateNamed:(NSString *)inName
388         return ([[availableIconStateDict objectForKey:@"State"] objectForKey:inName] != nil);
391 //Set a custom icon state
392 - (void)setIconState:(AIIconState *)iconState named:(NSString *)inName
394     [availableDynamicIconStateDict setObject:iconState forKey:inName];  //Add the new state to our available dict
395     [self setIconStateNamed:inName];                                    //Set it
398 //Build/Pre-render the icon images, start/stop animation
399 - (void)_buildIcon
401     NSMutableArray      *iconStates = [NSMutableArray array];
402     NSDictionary        *availableIcons;
403     NSEnumerator        *enumerator;
404     AIIconState         *state;
405     NSString            *name;
407     //Stop any existing animation
408     [animationTimer invalidate]; [animationTimer release]; animationTimer = nil;
409     if (observingFlash) {
410         [[adium interfaceController] unregisterFlashObserver:self];
411         observingFlash = NO;
412     }
414     //Build an array of the valid active icon states
415     availableIcons = [availableIconStateDict objectForKey:@"State"];
416     enumerator = [activeIconStateArray objectEnumerator];
417     while ((name = [enumerator nextObject])) {
418         if ((state = [availableIcons objectForKey:name]) || (state = [availableDynamicIconStateDict objectForKey:name])) {
419             [iconStates addObject:state];
420         }
421     }
423     //Generate the composited icon state
424     [currentIconState release];
425     currentIconState = [[AIIconState alloc] initByCompositingStates:iconStates];
427     //
428     if (![currentIconState animated]) { //Static icon
429                 NSImage *image = [currentIconState image];
430         if (image) {
431                          [[NSApplication sharedApplication] setApplicationIconImage:image];
432                 }
434     } else { //Animated icon
435         //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.
436         [[adium interfaceController] registerFlashObserver:self];
437         observingFlash = YES;
439         //Set the first frame of our animation
440         [self animateIcon:nil]; //Set the icon and move to the next frame
441     }
443     needsDisplay = NO;
446 - (void)flash:(int)value
448     //Start the flash timer
449     animationTimer = [[NSTimer scheduledTimerWithTimeInterval:[currentIconState animationDelay]
450                                                        target:self
451                                                      selector:@selector(animateIcon:)
452                                                      userInfo:nil
453                                                       repeats:YES] retain];
455     //Animate the icon
456     [self animateIcon:animationTimer]; //Set the icon and move to the next frame
458     //Once our animations stops, we no longer need to observe flashing
459     [[adium interfaceController] unregisterFlashObserver:self];
460     observingFlash = NO;
463 //Move the dock to the next animation frame (Assumes the current state is animated)
464 - (void)animateIcon:(NSTimer *)timer
466     NSImage     *image;
468     //Move to the next image
469     if (timer) {
470         [currentIconState nextFrame];
471     }
472         
473     //Set the image
474     image = [currentIconState image];
475         if (image) {
476                 [[NSApplication sharedApplication] setApplicationIconImage:image];
477         }
478     
481 //returns the % of the dock icon's full size that it currently is (0.0 - 1.0)
482 - (float)dockIconScale
484     NSSize trueSize = [[NSScreen mainScreen] visibleFrame].size;
485     NSSize availableSize = [[NSScreen mainScreen] frame].size;
487     int dHeight = availableSize.height - trueSize.height;
488     int dWidth = availableSize.width - trueSize.width;
489     float dockScale = 0;
491     if (dHeight != 22) { //dock is on the bottom
492         if (dHeight == 26) { //dock is hidden
493         } else { //dock is not hidden
494             dockScale = (dHeight-22)/128.0;
495         }
496     } else if (dWidth != 0) { //dock is on the side
497         if (dWidth == 4) { //dock is hidden
498         } else { //dock is not hidden
499             dockScale = (dWidth)/128.0;
500         }
501     } else {
502         //multiple monitors?
503         //Add support for multiple monitors
504     }
506     if (dockScale <= 0 || dockScale > 1.0) {
507         dockScale = 0.3;
508     }
510     return dockScale;
514  * @brief Return the dock icon image without any auxiliary states
515  */
516 - (NSImage *)baseApplicationIconImage
518         NSDictionary    *availableIcons = [availableIconStateDict objectForKey:@"State"];
519         AIIconState             *baseState;
520         NSImage                 *baseApplicationIconImage;
522         if ((baseState = [availableIcons objectForKey:@"Base"])) {
523                 AIIconState             *iconState = [[[AIIconState alloc] initByCompositingStates:[NSArray arrayWithObject:baseState]] autorelease];
524                 baseApplicationIconImage = [iconState image];
525         } else {
526                 baseApplicationIconImage = nil;
527         }
528         
529         return baseApplicationIconImage;
532 //Bouncing -------------------------------------------------------------------------------------------------------------
533 #pragma mark Bouncing
536  * @brief Perform a bouncing behavior
538  * @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)
539  */
540 - (BOOL)performBehavior:(AIDockBehavior)behavior
542         BOOL    ongoingBehavior = NO;
544     //Start up the new behavior
545     switch (behavior) {
546         case AIDockBehaviorStopBouncing: {
547                         [self _stopBouncing];
548                         break;
549                 }
550         case AIDockBehaviorBounceOnce: {
551                         if (currentBounceInterval >= SINGLE_BOUNCE_INTERVAL) {
552                                 currentBounceInterval = SINGLE_BOUNCE_INTERVAL;
553                                 [self _singleBounce];
554                         }
555                         break;
556                 }
557         case AIDockBehaviorBounceRepeatedly: ongoingBehavior = [self _continuousBounce]; break;
558         case AIDockBehaviorBounceDelay_FiveSeconds: ongoingBehavior = [self _bounceWithInterval:5.0]; break;
559         case AIDockBehaviorBounceDelay_TenSeconds: ongoingBehavior = [self _bounceWithInterval:10.0]; break;
560         case AIDockBehaviorBounceDelay_FifteenSeconds: ongoingBehavior = [self _bounceWithInterval:15.0]; break;
561         case AIDockBehaviorBounceDelay_ThirtySeconds: ongoingBehavior = [self _bounceWithInterval:30.0]; break;
562         case AIDockBehaviorBounceDelay_OneMinute: ongoingBehavior = [self _bounceWithInterval:60.0]; break;
563         default: break;
564     }
565         
566         return ongoingBehavior;
569 //Return a string description of the bouncing behavior
570 - (NSString *)descriptionForBehavior:(AIDockBehavior)behavior
572         NSString        *desc;
573         
574     switch (behavior) {
575         case AIDockBehaviorStopBouncing: desc = AILocalizedString(@"None",nil); break;
576         case AIDockBehaviorBounceOnce: desc = AILocalizedString(@"Once",nil); break;
577         case AIDockBehaviorBounceRepeatedly: desc = AILocalizedString(@"Repeatedly",nil); break;
578         case AIDockBehaviorBounceDelay_FiveSeconds: desc = AILocalizedString(@"Every 5 Seconds",nil); break;
579         case AIDockBehaviorBounceDelay_TenSeconds: desc = AILocalizedString(@"Every 10 Seconds",nil); break;
580         case AIDockBehaviorBounceDelay_FifteenSeconds: desc = AILocalizedString(@"Every 15 Seconds",nil); break;
581         case AIDockBehaviorBounceDelay_ThirtySeconds: desc = AILocalizedString(@"Every 30 Seconds",nil); break;
582         case AIDockBehaviorBounceDelay_OneMinute: desc = AILocalizedString(@"Every 60 Seconds",nil); break;
583         default: desc=@""; break;
584     }    
586         return desc;
590  * @brief Start a delayed, repeated bounce
592  * @result YES if we are now bouncing more frequently than before; NO if this call had no effect
593  */
594 - (BOOL)_bounceWithInterval:(NSTimeInterval)delay
596         BOOL    ongoingBehavior;
598         //Bounce only if the new delay is a faster bounce than the current one
599         if (delay < currentBounceInterval) {
600                 [self _singleBounce]; // do one right away
601                 
602                 currentBounceInterval = delay;
603                 
604                 bounceTimer = [[NSTimer scheduledTimerWithTimeInterval:delay
605                                                                                                                 target:self
606                                                                                                           selector:@selector(bounceWithTimer:)
607                                                                                                           userInfo:nil
608                                                                                                            repeats:YES] retain];
609                 
610                 ongoingBehavior = YES;
611         } else {
612                 ongoingBehavior = NO;
613         }
614         
615         return ongoingBehavior;
618 //Activated by the time after each delay
619 - (void)bounceWithTimer:(NSTimer *)timer
621     //Bounce
622     [self _singleBounce];
625 //Bounce once via NSApp's NSInformationalRequest (also used by the timer to perform a single bounce)
626 - (void)_singleBounce
628     if ([NSApp respondsToSelector:@selector(requestUserAttention:)]) {
629         currentAttentionRequest = [NSApp requestUserAttention:NSInformationalRequest];
630     }
634  * @brief Bounce continuously via NSApp's NSCriticalRequest
636  * We will bounce until we become the active application or our dock icon is clicked
638  * @result YES if we are now bouncing more frequently than before; NO if this call had no effect
639  */
640 - (BOOL)_continuousBounce
642         BOOL ongoingBehavior;
644         if (CONTINUOUS_BOUNCE_INTERVAL < currentBounceInterval) {
645                 currentBounceInterval = CONTINUOUS_BOUNCE_INTERVAL;
646                 if ([NSApp respondsToSelector:@selector(requestUserAttention:)]) {
647                         currentAttentionRequest = [NSApp requestUserAttention:NSCriticalRequest];
648                 }
650                 ongoingBehavior = YES;
651         } else {
652                 ongoingBehavior = NO;   
653         }
654         
655         return ongoingBehavior;
658 //Stop bouncing
659 - (void)_stopBouncing
661     //Stop any timer
662     if (bounceTimer) {
663         [bounceTimer invalidate]; [bounceTimer release]; bounceTimer = nil;
664     }
666     //Stop any continuous bouncing
667     if (currentAttentionRequest != -1) {
668         if ([NSApp respondsToSelector:@selector(cancelUserAttentionRequest:)]) {
669             [NSApp cancelUserAttentionRequest:currentAttentionRequest];
670         }
671         currentAttentionRequest = -1;
672     }
673         
674         currentBounceInterval = NO_BOUNCE_INTERVAL;
678 - (void)appWillChangeActive:(NSNotification *)notification
680     [self _stopBouncing]; //Stop any bouncing
683 @end