Reverted ESFileTransfer in adium-0.8 to [14253]; we simply won't draw the arrow if...
[adiumx.git] / Source / AIDockController.m
blob2419288b87d33883d673dcf4afa12d223e24bd49
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 "AIAppearancePreferencesPlugin.h"
20 #import "AIDockController.h"
21 #import "AIInterfaceController.h"
22 #import "AIPreferenceController.h"
23 #import "ESDebugController.h"
24 #import <AIUtilities/AIDictionaryAdditions.h>
25 #import <AIUtilities/AIFileManagerAdditions.h>
26 #import <AIUtilities/CBApplicationAdditions.h>
27 #import <Adium/AIIconState.h>
28 #import <Adium/IconFamily.h>
30 #define DOCK_DEFAULT_PREFS                      @"DockPrefs"
31 #define ICON_DISPLAY_DELAY                      0.1
33 #define LAST_ICON_UPDATE_VERSION        @"Adium:Last Icon Update Version"
35 #define CONTINUOUS_BOUNCE_INTERVAL  0
36 #define SINGLE_BOUNCE_INTERVAL          999
37 #define NO_BOUNCE_INTERVAL                      1000
39 @interface AIDockController (PRIVATE)
40 - (void)_setNeedsDisplay;
41 - (void)_buildIcon;
42 - (void)animateIcon:(NSTimer *)timer;
43 - (void)_singleBounce;
44 - (void)_continuousBounce;
45 - (void)_stopBouncing;
46 - (void)_bounceWithInterval:(double)delay;
47 - (void)preferencesChanged:(NSNotification *)notification;
48 - (AIIconState *)iconStateFromStateDict:(NSDictionary *)stateDict folderPath:(NSString *)folderPath;
49 - (void)updateAppBundleIcon;
50 @end
52 #ifndef MAC_OS_X_10_4
53 @interface NSWorkspace (NewTigerMethod)
54 - (void)setIcon:(NSImage *)icon forFile:(NSString *)file options:(int)options;
55 @end
56 #endif
58 @implementation AIDockController
60 //init and close
61 - (void)initController
63     //init
64     activeIconStateArray = [[NSMutableArray alloc] initWithObjects:@"Base",nil];
65     availableDynamicIconStateDict = [[NSMutableDictionary alloc] init];
66     currentIconState = nil;
67     currentAttentionRequest = -1;
68         currentBounceInterval = NO_BOUNCE_INTERVAL;
69     animationTimer = nil;
70     bounceTimer = nil;
71     needsDisplay = NO;
73         AIPreferenceController *preferenceController = [adium preferenceController];
74         NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
76     //Register our default preferences
77     [preferenceController registerDefaults:[NSDictionary dictionaryNamed:DOCK_DEFAULT_PREFS
78                                       forClass:[self class]] 
79                                       forGroup:PREF_GROUP_APPEARANCE];
80     
81     //Observe pref changes
82         [preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_APPEARANCE];
83         
84     //We always want to stop bouncing when Adium is made active
85     [notificationCenter addObserver:self
86                                selector:@selector(appWillChangeActive:) 
87                                    name:NSApplicationWillBecomeActiveNotification 
88                                  object:nil];
90     //We also stop bouncing when Adium is no longer active
91     [notificationCenter addObserver:self
92                                selector:@selector(appWillChangeActive:) 
93                                    name:NSApplicationWillResignActiveNotification 
94                                  object:nil];
96         //If Adium has been upgraded since the last time we ran, re-apply the user's custom icon
97         NSString        *lastVersion = [[NSUserDefaults standardUserDefaults] objectForKey:LAST_ICON_UPDATE_VERSION];
98         if(![[NSApp applicationVersion] isEqualToString:lastVersion]){
99                 [self updateAppBundleIcon];
100                 [[NSUserDefaults standardUserDefaults] setObject:[NSApp applicationVersion] forKey:LAST_ICON_UPDATE_VERSION];
101         }
104 - (void)closeController
106         [[adium preferenceController] unregisterPreferenceObserver:self];
108         NSArray                 *stateArrayCopy;
109         NSEnumerator    *enumerator;
110         NSString                *iconState;
112         //Reset our icon by removing all icon states (except for the base state)
113         stateArrayCopy = [activeIconStateArray copy]; //Work with a copy, since this array will change as we remove states
114         enumerator = [stateArrayCopy objectEnumerator];
115         [enumerator nextObject]; //Skip the first icon
116         while(iconState = [enumerator nextObject]){
117                 [self removeIconStateNamed:iconState];
118         }
120         //Force the icon to update
121         [self _buildIcon];
123         [stateArrayCopy release];
128  * @brief Returns an array of available dock icon pack paths
129  */
130 - (NSArray *)availableDockIconPacks
132         return ([adium allResourcesForName:FOLDER_DOCK_ICONS withExtensions:@"AdiumIcon"]);
137 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
138                                                         object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
140         if(!key || [key isEqualToString:KEY_ACTIVE_DOCK_ICON]){
141                 NSMutableDictionary     *newAvailableIconStateDict;
142                 NSString                        *iconPath;
143                 
144                 //Load the new icon pack
145                 iconPath = [adium pathOfPackWithName:[prefDict objectForKey:KEY_ACTIVE_DOCK_ICON]
146                                                                    extension:@"AdiumIcon"
147                                                   resourceFolderName:FOLDER_DOCK_ICONS];
149                 if(iconPath){
150                         if(newAvailableIconStateDict = [[self iconPackAtPath:iconPath] retain]){
151                                 [availableIconStateDict release]; availableIconStateDict = newAvailableIconStateDict;
152                         }
153                 }
154                 
155                 //Write the icon to the Adium application bundle so finder will see it
156                 //On launch we only need to update the icon file if this is a new version of Adium.  When preferences
157                 //change we always want to update it
158                 if(!firstTime){
159                         [self updateAppBundleIcon];
160                 }
162                 //Recomposite the icon
163                 [self _setNeedsDisplay];
164         }
167 - (void)updateAppBundleIcon
169         NSImage                 *image;
170         
171         image = [[[availableIconStateDict objectForKey:@"State"] objectForKey:@"Base"] image];
172         if(image){
173                 if([NSApp isOnTigerOrBetter]){
174                         [[NSWorkspace sharedWorkspace] setIcon:image 
175                                                                                    forFile:[[NSBundle mainBundle] bundlePath]
176                                                                                    options:0];
177                         
178                 }else{
179                         NSString                *icnsPath = [[NSBundle mainBundle] pathForResource:@"Adium" ofType:@"icns"];
180                         IconFamily              *iconFamily;
181         
182                         iconFamily = [IconFamily iconFamilyWithThumbnailsOfImage:image
183                                                                                          usingImageInterpolation:NSImageInterpolationLow];
184                         [iconFamily setAsCustomIconForFile:[[NSBundle mainBundle] bundlePath]];
185                         [iconFamily writeToFile:icnsPath];
187                 }
188                 
189                 //Finder won't update Adium's icon to match the new one until it is restarted if we don't
190                 //tell NSWorkspace to note the change.
191                 [[NSWorkspace sharedWorkspace] noteFileSystemChanged:[[NSBundle mainBundle] bundlePath]];               
192         }
195 //Icons ------------------------------------------------------------------------------------
196 - (void)_setNeedsDisplay
198     if(!needsDisplay){
199         needsDisplay = YES;
201         //Invoke a display after a short delay
202         [NSTimer scheduledTimerWithTimeInterval:ICON_DISPLAY_DELAY
203                                          target:self
204                                        selector:@selector(_buildIcon)
205                                        userInfo:nil
206                                         repeats:NO];
207     }
210 //Load an icon pack
211 - (NSMutableDictionary *)iconPackAtPath:(NSString *)folderPath
213     NSMutableDictionary *iconStateDict;
214     NSDictionary                *iconPackDict;
215     NSEnumerator                *stateNameKeyEnumerator;
216     NSString                    *stateNameKey;
218     //Load the icon pack
219     iconPackDict = [NSDictionary dictionaryWithContentsOfFile:[folderPath stringByAppendingPathComponent:@"IconPack.plist"]];
221     //Process each state in the icon pack, adding it to the iconStateDict
222         iconStateDict = [NSMutableDictionary dictionary];
223         
224     stateNameKeyEnumerator = [[[iconPackDict objectForKey:@"State"] allKeys] objectEnumerator];
225     while((stateNameKey = [stateNameKeyEnumerator nextObject])){
226                 NSDictionary    *stateDict;
227                 AIIconState             *iconState;
228                 
229                 stateDict = [[iconPackDict objectForKey:@"State"] objectForKey:stateNameKey];
230                 if(iconState = [self iconStateFromStateDict:stateDict folderPath:folderPath]){
231                         [iconStateDict setObject:iconState forKey:stateNameKey];
232                 }
233         }
234         
235         return([NSMutableDictionary dictionaryWithObjectsAndKeys:[iconPackDict objectForKey:@"Description"], @"Description", iconStateDict, @"State", nil]);
238 - (AIIconState *)previewStateForIconPackAtPath:(NSString *)folderPath
240         NSDictionary    *iconPackDict;
241         NSDictionary    *stateDict;
242         
243         //Load the icon pack
244     iconPackDict = [NSDictionary dictionaryWithContentsOfFile:[folderPath stringByAppendingPathComponent:@"IconPack.plist"]];
245         
246         //Load the preview state
247         stateDict = [[iconPackDict objectForKey:@"State"] objectForKey:@"Preview"];
248         
249         return ([self iconStateFromStateDict:stateDict folderPath:folderPath]);
252 - (AIIconState *)iconStateFromStateDict:(NSDictionary *)stateDict folderPath:(NSString *)folderPath
254         AIIconState             *iconState = nil;
255         
256         if([[stateDict objectForKey:@"Animated"] intValue]){ //Animated State
257                 NSMutableDictionary     *tempIconCache = [NSMutableDictionary dictionary];
258                 NSArray                         *imageNameArray;
259                 NSEnumerator            *imageNameEnumerator;
260                 NSString                        *imageName;
261                 NSMutableArray          *imageArray;
262                 BOOL                            overlay, looping;
263                 float                           delay;
264                 
265                 //Get the state information
266                 overlay = [[stateDict objectForKey:@"Overlay"] intValue];
267                 looping = [[stateDict objectForKey:@"Looping"] intValue];
268                 delay = [[stateDict objectForKey:@"Delay"] floatValue];
269                 imageNameArray = [stateDict objectForKey:@"Images"];
270                 imageNameEnumerator = [imageNameArray objectEnumerator];
271                 
272                 //Load the images
273                 imageArray = [NSMutableArray arrayWithCapacity:[imageNameArray count]];
274                 while((imageName = [imageNameEnumerator nextObject])){
275                         NSString        *imagePath;
276                         NSImage         *image;
277                         
278 #define DOCK_ICON_INTERNAL_PATH @"../Shared Images/"
279                         if([imageName hasPrefix:DOCK_ICON_INTERNAL_PATH]){
280                                 //Special hack for all the incorrectly made icon packs we have floating around out there :P
281                                 imageName = [imageName substringFromIndex:[DOCK_ICON_INTERNAL_PATH length]];
282                                 imagePath = [[NSBundle mainBundle] pathForResource:imageName
283                                                                                                                         ofType:@""
284                                                                                                            inDirectory:@"Shared Dock Icon Images"];
285                         }else{
286                                 imagePath = [folderPath stringByAppendingPathComponent:imageName];
287                         }
288                         
289                         image = [tempIconCache objectForKey:imagePath]; //We re-use the same images for each state if possible to lower memory usage.
290                         if(!image && imagePath){
291                                 image = [[[NSImage alloc] initByReferencingFile:imagePath] autorelease];
292                                 if(image) [tempIconCache setObject:image forKey:imagePath];
293                         }
294                         
295                         if(image) [imageArray addObject:image];
296                 }
297                 
298                 //Create the state
299                 if(delay != 0 && [imageArray count] != 0){
300                         iconState = [[AIIconState alloc] initWithImages:imageArray
301                                                                                                           delay:delay
302                                                                                                         looping:looping
303                                                                                                         overlay:overlay];
304                 }else{
305                         NSLog(@"Invalid animated icon state (%@)",imageName);
306                 }
307                 
308         }else{ //Static State
309                 NSString        *imageName;
310                 NSString        *imagePath;
311                 NSImage         *image;
312                 BOOL            overlay;
313                 
314                 imageName = [stateDict objectForKey:@"Image"];
315                 
316                 if([imageName hasPrefix:DOCK_ICON_INTERNAL_PATH]){
317                         //Special hack for all the incorrectly made icon packs we have floating around out there :P
318                         imageName = [imageName substringFromIndex:[DOCK_ICON_INTERNAL_PATH length]];
319                         imagePath = [[NSBundle mainBundle] pathForResource:imageName
320                                                                                                                 ofType:@"" 
321                                                                                                    inDirectory:@"Shared Dock Icon Images"];
322                 }else{
323                         imagePath = [folderPath stringByAppendingPathComponent:imageName];
324                 }
326                 //Get the state information
327                 image = [[NSImage alloc] initByReferencingFile:imagePath];
328                 overlay = [[stateDict objectForKey:@"Overlay"] intValue];
329                 
330                 //Create the state
331                 iconState = [[AIIconState alloc] initWithImage:image overlay:overlay];          
332                 [image release];
333         }
335         return [iconState autorelease];
338 //Set an icon state from our currently loaded icon pack
339 - (void)setIconStateNamed:(NSString *)inName
341     if(![activeIconStateArray containsObject:inName]){
342         [activeIconStateArray addObject:inName];        //Add the name to our array
343         [self _setNeedsDisplay];                        //Redisplay our icon
344     }
347 //Remove an active icon state
348 - (void)removeIconStateNamed:(NSString *)inName
350     if([activeIconStateArray containsObject:inName]){
351         [activeIconStateArray removeObject:inName];     //Remove the name from our array
352         
353         [self _setNeedsDisplay];                        //Redisplay our icon
354     }
358  * @brief Does the current icon know how to display a given state?
359  */
360 - (BOOL)currentIconSupportsIconStateNamed:(NSString *)inName
362         return ([[availableIconStateDict objectForKey:@"State"] objectForKey:inName] != nil);
365 //Set a custom icon state
366 - (void)setIconState:(AIIconState *)iconState named:(NSString *)inName
368     [availableDynamicIconStateDict setObject:iconState forKey:inName];  //Add the new state to our available dict
369     [self setIconStateNamed:inName];                                    //Set it
372 //Build/Pre-render the icon images, start/stop animation
373 - (void)_buildIcon
375     NSMutableArray      *iconStates = [NSMutableArray array];
376     NSDictionary        *availableIcons;
377     NSEnumerator        *enumerator;
378     AIIconState         *state;
379     NSString            *name;
381     //Stop any existing animation
382     [animationTimer invalidate]; [animationTimer release]; animationTimer = nil;
383     if(observingFlash){
384         [[adium interfaceController] unregisterFlashObserver:self];
385         observingFlash = NO;
386     }
388     //Build an array of the valid active icon states
389     availableIcons = [availableIconStateDict objectForKey:@"State"];
390     enumerator = [activeIconStateArray objectEnumerator];
391     while(name = [enumerator nextObject]){
392         if((state = [availableIcons objectForKey:name]) || (state = [availableDynamicIconStateDict objectForKey:name])){
393             [iconStates addObject:state];
394         }
395     }
397     //Generate the composited icon state
398     [currentIconState release];
399     currentIconState = [[AIIconState alloc] initByCompositingStates:iconStates];
401     //
402     if(![currentIconState animated]){ //Static icon
403                 NSImage *image = [currentIconState image];
404         if(image) {
405                          [[NSApplication sharedApplication] setApplicationIconImage:image];
406                 }
408     }else{ //Animated icon
409         //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.
410         [[adium interfaceController] registerFlashObserver:self];
411         observingFlash = YES;
413         //Set the first frame of our animation
414         [self animateIcon:nil]; //Set the icon and move to the next frame
415     }
417     needsDisplay = NO;
420 - (void)flash:(int)value
422     //Start the flash timer
423     animationTimer = [[NSTimer scheduledTimerWithTimeInterval:[currentIconState animationDelay]
424                                                        target:self
425                                                      selector:@selector(animateIcon:)
426                                                      userInfo:nil
427                                                       repeats:YES] retain];
429     //Animate the icon
430     [self animateIcon:animationTimer]; //Set the icon and move to the next frame
432     //Once our animations stops, we no longer need to observe flashing
433     [[adium interfaceController] unregisterFlashObserver:self];
434     observingFlash = NO;
437 //Move the dock to the next animation frame (Assumes the current state is animated)
438 - (void)animateIcon:(NSTimer *)timer
440     NSImage     *image;
442     //Move to the next image
443     if(timer){
444         [currentIconState nextFrame];
445     }
446         
447     //Set the image
448     image = [currentIconState image];
449         if(image) {
450                 [[NSApplication sharedApplication] setApplicationIconImage:image];
451         }
452     
455 //returns the % of the dock icon's full size that it currently is (0.0 - 1.0)
456 - (float)dockIconScale
458     NSSize trueSize = [[NSScreen mainScreen] visibleFrame].size;
459     NSSize availableSize = [[NSScreen mainScreen] frame].size;
461     int dHeight = availableSize.height - trueSize.height;
462     int dWidth = availableSize.width - trueSize.width;
463     float dockScale = 0;
465     if(dHeight != 22){ //dock is on the bottom
466         if(dHeight == 26){ //dock is hidden
467         }else{ //dock is not hidden
468             dockScale = (dHeight-22)/128.0;
469         }
470     }else if(dWidth != 0){ //dock is on the side
471         if(dWidth == 4){ //dock is hidden
472         }else{ //dock is not hidden
473             dockScale = (dWidth)/128.0;
474         }
475     }else{
476         //multiple monitors?
477         //Add support for multiple monitors
478     }
480     if(dockScale <= 0 || dockScale > 1.0){
481         dockScale = 0.3;
482     }
484     return(dockScale);
488 //Bouncing -------------------------------------------------------------------------------------------------------------
489 #pragma mark Bouncing
491 //Perform a bouncing behavior
492 - (void)performBehavior:(DOCK_BEHAVIOR)behavior
494     //Start up the new behavior
495     switch(behavior){
496         case BOUNCE_NONE: {
497                         [self _stopBouncing];
498                         break;
499                 }
500         case BOUNCE_ONCE: {
501                         if (currentBounceInterval >= SINGLE_BOUNCE_INTERVAL){
502                                 currentBounceInterval = SINGLE_BOUNCE_INTERVAL;
503                                 [self _singleBounce];
504                         }
505                         break;
506                 }
507         case BOUNCE_REPEAT: [self _continuousBounce]; break;
508         case BOUNCE_DELAY5: [self _bounceWithInterval:5.0]; break;
509         case BOUNCE_DELAY10: [self _bounceWithInterval:10.0]; break;
510         case BOUNCE_DELAY15: [self _bounceWithInterval:15.0]; break;
511         case BOUNCE_DELAY30: [self _bounceWithInterval:30.0]; break;
512         case BOUNCE_DELAY60: [self _bounceWithInterval:60.0]; break;
513         default: break;
514     }    
517 //Return a string description of the bouncing behavior
518 - (NSString *)descriptionForBehavior:(DOCK_BEHAVIOR)behavior
520         NSString        *desc;
521         
522     switch(behavior){
523         case BOUNCE_NONE: desc = AILocalizedString(@"None",nil); break;
524         case BOUNCE_ONCE: desc = AILocalizedString(@"Once",nil); break;
525         case BOUNCE_REPEAT: desc = AILocalizedString(@"Repeatedly",nil); break;
526         case BOUNCE_DELAY5: desc = AILocalizedString(@"Every 5 Seconds",nil); break;
527         case BOUNCE_DELAY10: desc = AILocalizedString(@"Every 10 Seconds",nil); break;
528         case BOUNCE_DELAY15: desc = AILocalizedString(@"Every 15 Seconds",nil); break;
529         case BOUNCE_DELAY30: desc = AILocalizedString(@"Every 30 Seconds",nil); break;
530         case BOUNCE_DELAY60: desc = AILocalizedString(@"Every 60 Seconds",nil); break;
531         default: desc=@""; break;
532     }    
534         return(desc);
537 //Start a delayed bounce
538 - (void)_bounceWithInterval:(NSTimeInterval)delay
540         //Bounce only if the new delay is a faster bounce than the current one
541         if (delay < currentBounceInterval){
542                 [self _singleBounce]; // do one right away
543                 
544                 currentBounceInterval = delay;
545                 
546                 bounceTimer = [[NSTimer scheduledTimerWithTimeInterval:delay
547                                                                                                                 target:self
548                                                                                                           selector:@selector(bounceWithTimer:)
549                                                                                                           userInfo:nil
550                                                                                                            repeats:YES] retain];
551         }
554 //Activated by the time after each delay
555 - (void)bounceWithTimer:(NSTimer *)timer
557     //Bounce
558     [self _singleBounce];
561 //Bounce once via NSApp's NSInformationalRequest (also used by the timer to perform a single bounce)
562 - (void)_singleBounce
564     if([NSApp respondsToSelector:@selector(requestUserAttention:)]){
565         currentAttentionRequest = [NSApp requestUserAttention:NSInformationalRequest];
566     }
569 //Bounce continuously via NSApp's NSCriticalRequest
570 - (void)_continuousBounce
572         if (CONTINUOUS_BOUNCE_INTERVAL < currentBounceInterval){
573                 currentBounceInterval = CONTINUOUS_BOUNCE_INTERVAL;
574                 if([NSApp respondsToSelector:@selector(requestUserAttention:)]){
575                         currentAttentionRequest = [NSApp requestUserAttention:NSCriticalRequest];
576                 }
577         }
580 //Stop bouncing
581 - (void)_stopBouncing
583     //Stop any timer
584     if(bounceTimer){
585         [bounceTimer invalidate]; [bounceTimer release]; bounceTimer = nil;
586     }
588     //Stop any continuous bouncing
589     if(currentAttentionRequest != -1){
590         if([NSApp respondsToSelector:@selector(cancelUserAttentionRequest:)]){
591             [NSApp cancelUserAttentionRequest:currentAttentionRequest];
592         }
593         currentAttentionRequest = -1;
594     }
595         
596         currentBounceInterval = NO_BOUNCE_INTERVAL;
600 - (void)appWillChangeActive:(NSNotification *)notification
602     [self _stopBouncing]; //Stop any bouncing
605 @end