William Bowling is sharing code with you

Bitbucket is a code hosting site. Unlimited public and private repositories. Free for small teams.

Don't show this again

wbowling / adium (fork of adium / adium)

Fork of Adium for patches/improvements

Clone this repository (size: 338.7 MB): HTTPS / SSH
hg clone https://bitbucket.org/wbowling/adium
hg clone ssh://hg@bitbucket.org/wbowling/adium

adium / Source / AIDockController.m

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
/* 
 * Adium is the legal property of its developers, whose names are listed in the copyright file included
 * with this source distribution.
 * 
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
 * General Public License as published by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
 * Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program; if not,
 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

// $Id$

#import "AIDockController.h"
#import <Adium/AIInterfaceControllerProtocol.h>
#import <AIUtilities/AIDictionaryAdditions.h>
#import <AIUtilities/AIFileManagerAdditions.h>
#import <AIUtilities/AIApplicationAdditions.h>
#import <Adium/AIIconState.h>

#define DOCK_DEFAULT_PREFS                      @"DockPrefs"
#define ICON_DISPLAY_DELAY                      0.1

#define LAST_ICON_UPDATE_VERSION        @"Adium:Last Icon Update Version"

#define CONTINUOUS_BOUNCE_INTERVAL  0
#define SINGLE_BOUNCE_INTERVAL          999
#define NO_BOUNCE_INTERVAL                      1000

@interface AIDockController ()
- (void)_setNeedsDisplay;
- (void)_buildIcon;
- (void)animateIcon:(NSTimer *)timer;
- (void)_singleBounce;
- (BOOL)_continuousBounce;
- (void)_stopBouncing;
- (BOOL)_bounceWithInterval:(double)delay;
- (AIIconState *)iconStateFromStateDict:(NSDictionary *)stateDict folderPath:(NSString *)folderPath;
- (void)updateAppBundleIcon;
@end

@implementation AIDockController
 
//init and close
- (id)init
{
        if ((self = [super init])) {
                activeIconStateArray = [[NSMutableArray alloc] initWithObjects:@"Base",nil];
                availableDynamicIconStateDict = [[NSMutableDictionary alloc] init];
                currentIconState = nil;
                currentAttentionRequest = -1;
                currentBounceInterval = NO_BOUNCE_INTERVAL;
                animationTimer = nil;
                bounceTimer = nil;
                needsDisplay = NO;
        }
        
        return self;
}

- (void)controllerDidLoad
{
        NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
        
    //Register our default preferences
    [adium.preferenceController registerDefaults:[NSDictionary dictionaryNamed:DOCK_DEFAULT_PREFS
                                                                                                                                                forClass:[self class]] 
                                                                                  forGroup:PREF_GROUP_APPEARANCE];
    
    //Observe pref changes
        [adium.preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_APPEARANCE];
        
    //We always want to stop bouncing when Adium is made active
    [notificationCenter addObserver:self
                               selector:@selector(appWillChangeActive:) 
                                   name:NSApplicationWillBecomeActiveNotification 
                                 object:nil];
        
    //We also stop bouncing when Adium is no longer active
    [notificationCenter addObserver:self
                               selector:@selector(appWillChangeActive:) 
                                   name:NSApplicationWillResignActiveNotification 
                                 object:nil];
        
        //If Adium has been upgraded since the last time we ran, re-apply the user's custom icon
        NSString        *lastVersion = [[NSUserDefaults standardUserDefaults] objectForKey:LAST_ICON_UPDATE_VERSION];
        if (![[NSApp applicationVersion] isEqualToString:lastVersion]) {
                [self updateAppBundleIcon];
                [[NSUserDefaults standardUserDefaults] setObject:[NSApp applicationVersion] forKey:LAST_ICON_UPDATE_VERSION];
        }
}

- (void)controllerWillClose
{
        [adium.preferenceController unregisterPreferenceObserver:self];

        //Reset our icon by removing all icon states (except for the base state)
        NSArray *stateArrayCopy = [[activeIconStateArray copy] autorelease]; //Work with a copy, since this array will change as we remove states
        NSEnumerator *enumerator = [stateArrayCopy objectEnumerator];
        [enumerator nextObject]; //Skip the first icon
        for (NSString *iconState in enumerator) {
                [self removeIconStateNamed:iconState];
        }

        //Force the icon to update
        [self _buildIcon];
}


/*!
 * @brief Returns an array of available dock icon pack paths
 */
- (NSArray *)availableDockIconPacks
{
        NSMutableArray * iconPackPaths = [NSMutableArray array]; //this will be the folder path for old packs, and the bundle resource path for new
        for (NSString *path in [adium allResourcesForName:FOLDER_DOCK_ICONS withExtensions:@"AdiumIcon"]) {
                NSBundle *xtraBundle = [NSBundle bundleWithPath:path];
                if (xtraBundle && ([[xtraBundle objectForInfoDictionaryKey:@"XtraBundleVersion"] integerValue] == 1))//This checks for a new-style xtra
                        path = [xtraBundle resourcePath];
                [iconPackPaths addObject:path];
        }
        return iconPackPaths;
}



- (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
                                                        object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
{
        if (!key || [key isEqualToString:KEY_ACTIVE_DOCK_ICON]) {               
                //Load the new icon pack
                NSString *iconPath = [adium pathOfPackWithName:[prefDict objectForKey:KEY_ACTIVE_DOCK_ICON]
                                                                                         extension:@"AdiumIcon"
                                                                        resourceFolderName:FOLDER_DOCK_ICONS];

                if (iconPath) {
                        NSMutableDictionary     *newAvailableIconStateDict = [self iconPackAtPath:iconPath];
                        if (newAvailableIconStateDict) {
                                [availableIconStateDict autorelease]; 
                                availableIconStateDict = [newAvailableIconStateDict retain];
                        }
                }
                
                //Write the icon to the Adium application bundle so finder will see it
                //On launch we only need to update the icon file if this is a new version of Adium.  When preferences
                //change we always want to update it
                if (!firstTime) {
                        [self updateAppBundleIcon];
                }

                //Recomposite the icon
                [self _setNeedsDisplay];
        }
}

- (void)updateAppBundleIcon
{       
        NSImage *image = [[[availableIconStateDict objectForKey:@"State"] objectForKey:@"ApplicationIcon"] image];
        if (!image) 
                image = [[[availableIconStateDict objectForKey:@"State"] objectForKey:@"Base"] image];

        if (image) {
                [[NSWorkspace sharedWorkspace] setIcon:image 
                                                                           forFile:[[NSBundle mainBundle] bundlePath]
                                                                           options:0];

                //Finder won't update Adium's icon to match the new one until it is restarted if we don't
                //tell NSWorkspace to note the change.
                [[NSWorkspace sharedWorkspace] noteFileSystemChanged:[[NSBundle mainBundle] bundlePath]];
        }
}

//Icons ------------------------------------------------------------------------------------
- (void)_setNeedsDisplay
{
        if (!needsDisplay) {
                needsDisplay = YES;

                //Invoke a display after a short delay
                [NSTimer scheduledTimerWithTimeInterval:ICON_DISPLAY_DELAY
                                                                                 target:self
                                                                           selector:@selector(_buildIcon)
                                                                           userInfo:nil
                                                                                repeats:NO];
        }
}

//Load an icon pack
- (NSMutableDictionary *)iconPackAtPath:(NSString *)folderPath
{
        //Load the icon pack
        NSDictionary *iconPackDict = [NSDictionary dictionaryWithContentsOfFile:[folderPath stringByAppendingPathComponent:@"IconPack.plist"]];

        NSMutableDictionary *iconStateDict = [NSMutableDictionary dictionary];

        //Process each state in the icon pack, adding it to the iconStateDict
        for (NSString *stateNameKey in [iconPackDict objectForKey:@"State"]) {
                NSDictionary *stateDict = [[iconPackDict objectForKey:@"State"] objectForKey:stateNameKey];
                AIIconState *iconState = [self iconStateFromStateDict:stateDict folderPath:folderPath];
                if (iconState)
                        [iconStateDict setObject:iconState forKey:stateNameKey];
        }

        return [NSMutableDictionary dictionaryWithObjectsAndKeys:[iconPackDict objectForKey:@"Description"], @"Description", iconStateDict, @"State", nil];
}

/*!
 * @brief Get the name and preview steate for a dock icon pack
 *
 * @param outName Reference to an NSString, or NULL if this information is not needed
 * @param outIconState Reference to an AIIconState, or NULL if this information is not needed
 * @param folderPath The path to the dock icon pack
 */
- (void)getName:(NSString **)outName previewState:(AIIconState **)outIconState forIconPackAtPath:(NSString *)folderPath
{       
        //Load the icon pack
        NSDictionary *iconPackDict = [NSDictionary dictionaryWithContentsOfFile:[folderPath stringByAppendingPathComponent:@"IconPack.plist"]];
        
        //Load the preview state
        NSDictionary *stateDict = [[iconPackDict objectForKey:@"State"] objectForKey:@"Preview"];
        
        if (outIconState) *outIconState = [self iconStateFromStateDict:stateDict folderPath:folderPath];
        if (outName) *outName = [[iconPackDict objectForKey:@"Description"] objectForKey:@"Title"];
}

- (AIIconState *)previewStateForIconPackAtPath:(NSString *)folderPath
{
        AIIconState     *previewState = nil;
        
        [self getName:NULL previewState:&previewState forIconPackAtPath:folderPath];
        
        return previewState;
}

- (AIIconState *)iconStateFromStateDict:(NSDictionary *)stateDict folderPath:(NSString *)folderPath
{
        AIIconState             *iconState = nil;
        
        if ([[stateDict objectForKey:@"Animated"] integerValue]) { //Animated State
                NSMutableDictionary     *tempIconCache = [NSMutableDictionary dictionary];
                
                //Get the state information
                BOOL overlay = [[stateDict objectForKey:@"Overlay"] boolValue];
                BOOL looping = [[stateDict objectForKey:@"Looping"] boolValue];
                CGFloat delay   = [[stateDict objectForKey:@"Delay"] doubleValue];
                NSArray *imageNameArray = [stateDict objectForKey:@"Images"];

                //Load the images
                NSMutableArray *imageArray = [NSMutableArray arrayWithCapacity:[imageNameArray count]];
                for (NSString *imageName in imageNameArray) {
                        NSString        *imagePath;
                        
#define DOCK_ICON_INTERNAL_PATH @"../Shared Images/"
                        if ([imageName hasPrefix:DOCK_ICON_INTERNAL_PATH]) {
                                //Special hack for all the incorrectly made icon packs we have floating around out there :P
                                imageName = [imageName substringFromIndex:[DOCK_ICON_INTERNAL_PATH length]];
                                imagePath = [[NSBundle mainBundle] pathForResource:[[[imageName stringByDeletingPathExtension] stringByAppendingString:@"-localized"] stringByAppendingPathExtension:[imageName pathExtension]]
                                                                            ofType:@""
                                                                       inDirectory:@"Shared Dock Icon Images"];
                                
                                if (!imagePath) {
                                        imagePath = [[NSBundle mainBundle] pathForResource:imageName
                                                                                                                                ofType:@""
                                                                                                                   inDirectory:@"Shared Dock Icon Images"];
                                }

                        } else {
                                imagePath = [folderPath stringByAppendingPathComponent:imageName];
                        }
                        
                        NSImage *image = [tempIconCache objectForKey:imagePath]; //We re-use the same images for each state if possible to lower memory usage.
                        if (!image && imagePath) {
                                image = [[[NSImage alloc] initByReferencingFile:imagePath] autorelease];
                                if (image) [tempIconCache setObject:image forKey:imagePath];
                        }
                        
                        if (image) [imageArray addObject:image];
                }
                
                //Create the state
                if (delay != 0 && [imageArray count] != 0) {
                        iconState = [[AIIconState alloc] initWithImages:imageArray
                                                                                                          delay:delay
                                                                                                        looping:looping
                                                                                                        overlay:overlay];
                } else {
                        NSLog(@"Invalid animated icon state");
                }
                
        } else { //Static State
                NSString        *imageName;
                NSString        *imagePath;
                NSImage         *image;
                BOOL            overlay;
                
                imageName = [stateDict objectForKey:@"Image"];
                
                if ([imageName hasPrefix:DOCK_ICON_INTERNAL_PATH]) {
                        //Special hack for all the incorrectly made icon packs we have floating around out there :P
                        imageName = [imageName substringFromIndex:[DOCK_ICON_INTERNAL_PATH length]];
                        imagePath = [[NSBundle mainBundle] pathForResource:[[[imageName stringByDeletingPathExtension] stringByAppendingString:@"-localized"] stringByAppendingPathExtension:[imageName pathExtension]]
                                                                                                                ofType:@""
                                                                                                   inDirectory:@"Shared Dock Icon Images"];
                        if (!imagePath) {
                                imagePath = [[NSBundle mainBundle] pathForResource:imageName
                                                                    ofType:@""
                                                               inDirectory:@"Shared Dock Icon Images"];
                        }
                } else {
                        imagePath = [folderPath stringByAppendingPathComponent:imageName];
                }

                //Get the state information
                image = [[NSImage alloc] initByReferencingFile:imagePath];
                overlay = [[stateDict objectForKey:@"Overlay"] boolValue];
                
                //Create the state
                iconState = [[AIIconState alloc] initWithImage:image overlay:overlay];          
                [image release];
        }

        return [iconState autorelease];
}

//Set an icon state from our currently loaded icon pack
- (void)setIconStateNamed:(NSString *)inName
{
        if (![activeIconStateArray containsObject:inName]) {
                [activeIconStateArray addObject:inName];        //Add the name to our array
                [self _setNeedsDisplay];                        //Redisplay our icon
        }
}

//Remove an active icon state
- (void)removeIconStateNamed:(NSString *)inName
{
        if ([activeIconStateArray containsObject:inName]) {
                [activeIconStateArray removeObject:inName];     //Remove the name from our array
                
                [self _setNeedsDisplay];                        //Redisplay our icon
        }
}

/*!
 * @brief Does the current icon know how to display a given state?
 */
- (BOOL)currentIconSupportsIconStateNamed:(NSString *)inName
{
        return ([[availableIconStateDict objectForKey:@"State"] objectForKey:inName] != nil);
}

//Set a custom icon state
- (void)setIconState:(AIIconState *)iconState named:(NSString *)inName
{
    [availableDynamicIconStateDict setObject:iconState forKey:inName];  //Add the new state to our available dict
    [self setIconStateNamed:inName];                                    //Set it
}

//Build/Pre-render the icon images, start/stop animation
- (void)_buildIcon
{
        NSMutableArray  *iconStates = [NSMutableArray array];

        //Stop any existing animation
        [animationTimer invalidate]; [animationTimer release]; animationTimer = nil;
        if (observingFlash) {
                [adium.interfaceController unregisterFlashObserver:self];
                observingFlash = NO;
        }

        //Build an array of the valid active icon states
        NSDictionary *availableIcons = [availableIconStateDict objectForKey:@"State"];
        for (NSString *name in activeIconStateArray) {
                AIIconState *state = [availableIcons objectForKey:name];
                if (!state)
                        state = [availableDynamicIconStateDict objectForKey:name];
                if (state)
                        [iconStates addObject:state];
        }

        //Generate the composited icon state
        [currentIconState release];
        currentIconState = [[AIIconState alloc] initByCompositingStates:iconStates];

        if (![currentIconState animated]) { //Static icon
                NSImage *image = [currentIconState image];
                if (image) {
                         [[NSApplication sharedApplication] setApplicationIconImage:image];
                }
        } else { //Animated icon
                //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.
                [adium.interfaceController registerFlashObserver:self];
                observingFlash = YES;

                //Set the first frame of our animation
                [self animateIcon:nil]; //Set the icon and move to the next frame
        }

        needsDisplay = NO;
}

- (void)flash:(int)value
{
    //Start the flash timer
    animationTimer = [[NSTimer scheduledTimerWithTimeInterval:[currentIconState animationDelay]
                                                       target:self
                                                     selector:@selector(animateIcon:)
                                                     userInfo:nil
                                                      repeats:YES] retain];

    //Animate the icon
    [self animateIcon:animationTimer]; //Set the icon and move to the next frame

    //Once our animations stops, we no longer need to observe flashing
    [adium.interfaceController unregisterFlashObserver:self];
    observingFlash = NO;
}

//Move the dock to the next animation frame (Assumes the current state is animated)
- (void)animateIcon:(NSTimer *)timer
{
        //Move to the next image
        if (timer) {
                [currentIconState nextFrame];
        }

        //Set the image
        NSImage *image = [currentIconState image];
        if (image) {
                [[NSApplication sharedApplication] setApplicationIconImage:image];
        }
}

//returns the % of the dock icon's full size that it currently is (0.0 - 1.0)
- (CGFloat)dockIconScale
{
        NSScreen *mainScreen = [NSScreen mainScreen];
        NSSize trueSize = mainScreen.visibleFrame.size;
        NSSize availableSize = mainScreen.frame.size;

        NSInteger       dHeight = availableSize.height - trueSize.height;
        NSInteger dWidth = availableSize.width - trueSize.width;
        CGFloat dockScale = 0;

        if (dHeight != 22) { //dock is on the bottom
                if (dHeight != 26) { //dock is not hidden
                        dockScale = (dHeight-22)/128.0;
                }
        } else if (dWidth != 0) { //dock is on the side
                if (dWidth != 4) { //dock is not hidden
                        dockScale = (dWidth)/128.0;
                }
        } else {
                //multiple monitors?
                //Add support for multiple monitors
        }

        if (dockScale <= 0 || dockScale > 1.0) {
                dockScale = 0.3;
        }

        return dockScale;
}

/*!
 * @brief Return the dock icon image without any auxiliary states
 */
- (NSImage *)baseApplicationIconImage
{
        NSDictionary    *availableIcons = [availableIconStateDict objectForKey:@"State"];
        AIIconState             *baseState = [availableIcons objectForKey:@"Base"];

        if (baseState) {
                AIIconState             *iconState = [[[AIIconState alloc] initByCompositingStates:[NSArray arrayWithObject:baseState]] autorelease];
                return [iconState image];
        }
        
        return nil;
}

//Bouncing -------------------------------------------------------------------------------------------------------------
#pragma mark Bouncing

/*!
 * @brief Perform a bouncing behavior
 *
 * @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)
 */
- (BOOL)performBehavior:(AIDockBehavior)behavior
{
        BOOL    ongoingBehavior = NO;

        //Start up the new behavior
        switch (behavior) {
                case AIDockBehaviorStopBouncing: {
                        [self _stopBouncing];
                        break;
                }
                case AIDockBehaviorBounceOnce: {
                        if (currentBounceInterval >= SINGLE_BOUNCE_INTERVAL) {
                                currentBounceInterval = SINGLE_BOUNCE_INTERVAL;
                                [self _singleBounce];
                        }
                        break;
                }
                case AIDockBehaviorBounceRepeatedly: ongoingBehavior = [self _continuousBounce]; break;
                case AIDockBehaviorBounceDelay_FiveSeconds: ongoingBehavior = [self _bounceWithInterval:5.0]; break;
                case AIDockBehaviorBounceDelay_TenSeconds: ongoingBehavior = [self _bounceWithInterval:10.0]; break;
                case AIDockBehaviorBounceDelay_FifteenSeconds: ongoingBehavior = [self _bounceWithInterval:15.0]; break;
                case AIDockBehaviorBounceDelay_ThirtySeconds: ongoingBehavior = [self _bounceWithInterval:30.0]; break;
                case AIDockBehaviorBounceDelay_OneMinute: ongoingBehavior = [self _bounceWithInterval:60.0]; break;
        }
        
        return ongoingBehavior;
}

//Return a string description of the bouncing behavior
- (NSString *)descriptionForBehavior:(AIDockBehavior)behavior
{
        switch (behavior) {
                case AIDockBehaviorStopBouncing: return AILocalizedString(@"None",nil);
                case AIDockBehaviorBounceOnce: return AILocalizedString(@"Once",nil);
                case AIDockBehaviorBounceRepeatedly: return AILocalizedString(@"Repeatedly",nil);
                case AIDockBehaviorBounceDelay_FiveSeconds: return AILocalizedString(@"Every 5 Seconds",nil);
                case AIDockBehaviorBounceDelay_TenSeconds: return AILocalizedString(@"Every 10 Seconds",nil);
                case AIDockBehaviorBounceDelay_FifteenSeconds: return AILocalizedString(@"Every 15 Seconds",nil);
                case AIDockBehaviorBounceDelay_ThirtySeconds: return AILocalizedString(@"Every 30 Seconds",nil);
                case AIDockBehaviorBounceDelay_OneMinute: return AILocalizedString(@"Every 60 Seconds",nil);
                default: return @"";
        }
}

/*!
 * @brief Start a delayed, repeated bounce
 *
 * @result YES if we are now bouncing more frequently than before; NO if this call had no effect
 */
- (BOOL)_bounceWithInterval:(NSTimeInterval)delay
{
        //Bounce only if the new delay is a faster bounce than the current one
        if (delay < currentBounceInterval) {
                [self _singleBounce]; // do one right away
                
                currentBounceInterval = delay;
                
                bounceTimer = [[NSTimer scheduledTimerWithTimeInterval:delay
                                                                                                                target:self
                                                                                                          selector:@selector(bounceWithTimer:)
                                                                                                          userInfo:nil
                                                                                                           repeats:YES] retain];
                
                return YES;
        }
        return NO;
}

//Activated by the time after each delay
- (void)bounceWithTimer:(NSTimer *)timer
{
        //Bounce
        [self _singleBounce];
}

//Bounce once via NSApp's NSInformationalRequest (also used by the timer to perform a single bounce)
- (void)_singleBounce
{
        currentAttentionRequest = [NSApp requestUserAttention:NSInformationalRequest];
}

/*!
 * @brief Bounce continuously via NSApp's NSCriticalRequest
 *
 * We will bounce until we become the active application or our dock icon is clicked
 *
 * @result YES if we are now bouncing more frequently than before; NO if this call had no effect
 */
- (BOOL)_continuousBounce
{
        if (CONTINUOUS_BOUNCE_INTERVAL < currentBounceInterval) {
                currentBounceInterval = CONTINUOUS_BOUNCE_INTERVAL;
                currentAttentionRequest = [NSApp requestUserAttention:NSCriticalRequest];

                return YES;
        }
        return NO;
}

//Stop bouncing
- (void)_stopBouncing
{
        //Stop any timer
        if (bounceTimer) {
                [bounceTimer invalidate]; 
                [bounceTimer release]; 
                bounceTimer = nil;
        }

        //Stop any continuous bouncing
        if (currentAttentionRequest != -1) {
                [NSApp cancelUserAttentionRequest:currentAttentionRequest];
                currentAttentionRequest = -1;
        }
        
        currentBounceInterval = NO_BOUNCE_INTERVAL;
}

- (void)appWillChangeActive:(NSNotification *)notification
{
    [self _stopBouncing]; //Stop any bouncing
}

@end