Source/AIDockController.m
changeset 3670 add0c83648a5
parent 3092 ffb42621b742
child 3673 c3cc8fe8f018
equal deleted inserted replaced
3669:7905c08bc4d8 3670:add0c83648a5
    19 #import "AIDockController.h"
    19 #import "AIDockController.h"
    20 #import <Adium/AIInterfaceControllerProtocol.h>
    20 #import <Adium/AIInterfaceControllerProtocol.h>
    21 #import <AIUtilities/AIDictionaryAdditions.h>
    21 #import <AIUtilities/AIDictionaryAdditions.h>
    22 #import <AIUtilities/AIFileManagerAdditions.h>
    22 #import <AIUtilities/AIFileManagerAdditions.h>
    23 #import <AIUtilities/AIApplicationAdditions.h>
    23 #import <AIUtilities/AIApplicationAdditions.h>
       
    24 #import <Adium/AIChatControllerProtocol.h>
    24 #import <Adium/AIIconState.h>
    25 #import <Adium/AIIconState.h>
       
    26 #import <Adium/AIChat.h>
       
    27 #import <Adium/AIStatusControllerProtocol.h>
    25 
    28 
    26 #define DOCK_DEFAULT_PREFS			@"DockPrefs"
    29 #define DOCK_DEFAULT_PREFS			@"DockPrefs"
    27 #define ICON_DISPLAY_DELAY			0.1
    30 #define ICON_DISPLAY_DELAY			0.1
    28 
    31 
    29 #define LAST_ICON_UPDATE_VERSION	@"Adium:Last Icon Update Version"
    32 #define LAST_ICON_UPDATE_VERSION	@"Adium:Last Icon Update Version"
    30 
    33 
    31 #define CONTINUOUS_BOUNCE_INTERVAL  0
    34 #define CONTINUOUS_BOUNCE_INTERVAL  0
    32 #define SINGLE_BOUNCE_INTERVAL		999
    35 #define SINGLE_BOUNCE_INTERVAL		999
    33 #define NO_BOUNCE_INTERVAL			1000
    36 #define NO_BOUNCE_INTERVAL			1000
       
    37 
       
    38 #define DOCK_ICON_INTERNAL_PATH		@"../Shared Images/"
       
    39 #define DOCK_ICON_SHARED_IMAGES		@"Shared Dock Icon Images"
    34 
    40 
    35 @interface AIDockController ()
    41 @interface AIDockController ()
    36 - (void)_setNeedsDisplay;
    42 - (void)_setNeedsDisplay;
    37 - (void)_buildIcon;
    43 - (void)_buildIcon;
    38 - (void)animateIcon:(NSTimer *)timer;
    44 - (void)animateIcon:(NSTimer *)timer;
    40 - (BOOL)_continuousBounce;
    46 - (BOOL)_continuousBounce;
    41 - (void)_stopBouncing;
    47 - (void)_stopBouncing;
    42 - (BOOL)_bounceWithInterval:(double)delay;
    48 - (BOOL)_bounceWithInterval:(double)delay;
    43 - (AIIconState *)iconStateFromStateDict:(NSDictionary *)stateDict folderPath:(NSString *)folderPath;
    49 - (AIIconState *)iconStateFromStateDict:(NSDictionary *)stateDict folderPath:(NSString *)folderPath;
    44 - (void)updateAppBundleIcon;
    50 - (void)updateAppBundleIcon;
       
    51 - (void)updateDockView;
       
    52 - (void)updateDockBadge;
       
    53 - (void)animateDockIcon;
    45 
    54 
    46 - (void)appWillChangeActive:(NSNotification *)notification;
    55 - (void)appWillChangeActive:(NSNotification *)notification;
    47 - (void)bounceWithTimer:(NSTimer *)timer;
    56 - (void)bounceWithTimer:(NSTimer *)timer;
    48 @end
    57 @end
    49 
    58 
    59 		currentAttentionRequest = -1;
    68 		currentAttentionRequest = -1;
    60 		currentBounceInterval = NO_BOUNCE_INTERVAL;
    69 		currentBounceInterval = NO_BOUNCE_INTERVAL;
    61 		animationTimer = nil;
    70 		animationTimer = nil;
    62 		bounceTimer = nil;
    71 		bounceTimer = nil;
    63 		needsDisplay = NO;
    72 		needsDisplay = NO;
       
    73 		unviewedState = NO;
    64 	}
    74 	}
    65 	
    75 	
    66 	return self;
    76 	return self;
    67 }
    77 }
    68 
    78 
    69 - (void)controllerDidLoad
    79 - (void)controllerDidLoad
    70 {
    80 {
       
    81 	dockTile = [NSApp dockTile];
       
    82 	view = [[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, 128, 128)];
       
    83 	
       
    84 	[dockTile setContentView:view];
       
    85 
       
    86 	//Register our default preferences
       
    87 	[adium.preferenceController registerDefaults:[NSDictionary dictionaryNamed:DOCK_DEFAULT_PREFS
       
    88 																	  forClass:[self class]] 
       
    89 										forGroup:PREF_GROUP_APPEARANCE];
       
    90 	
       
    91 	//Observe pref changes
       
    92 	[adium.preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_APPEARANCE];
       
    93 	// Register as an observer of the status preferences for unread conversation count
       
    94 	[adium.preferenceController registerPreferenceObserver:self
       
    95 												  forGroup:PREF_GROUP_STATUS_PREFERENCES];
       
    96 	
       
    97 	[adium.chatController registerChatObserver:self];
       
    98 	
    71 	NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    99 	NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    72 	
   100 	
    73     //Register our default preferences
   101 	//We always want to stop bouncing when Adium is made active
    74     [adium.preferenceController registerDefaults:[NSDictionary dictionaryNamed:DOCK_DEFAULT_PREFS
   102 	[notificationCenter addObserver:self
    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:) 
   103 	                       selector:@selector(appWillChangeActive:) 
    84 	                           name:NSApplicationWillBecomeActiveNotification 
   104 	                           name:NSApplicationWillBecomeActiveNotification 
    85 	                         object:nil];
   105 	                         object:nil];
    86 	
   106 	
    87     //We also stop bouncing when Adium is no longer active
   107     //We also stop bouncing when Adium is no longer active
    88     [notificationCenter addObserver:self
   108     [notificationCenter addObserver:self
    89 	                       selector:@selector(appWillChangeActive:) 
   109 	                       selector:@selector(appWillChangeActive:) 
    90 	                           name:NSApplicationWillResignActiveNotification 
   110 	                           name:NSApplicationWillResignActiveNotification 
    91 	                         object:nil];
   111 	                         object:nil];
    92 	
   112 	
    93 	//If Adium has been upgraded since the last time we ran, re-apply the user's custom icon
   113 	//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];
   114 	NSString	*lastVersion = [[NSUserDefaults standardUserDefaults] objectForKey:LAST_ICON_UPDATE_VERSION];
    95 	if (![[NSApp applicationVersion] isEqualToString:lastVersion]) {
   115 	if (![[NSApp applicationVersion] isEqualToString:lastVersion]) {
    96 		[self updateAppBundleIcon];
   116 		[self updateAppBundleIcon];
    97 		[[NSUserDefaults standardUserDefaults] setObject:[NSApp applicationVersion] forKey:LAST_ICON_UPDATE_VERSION];
   117 		[[NSUserDefaults standardUserDefaults] setObject:[NSApp applicationVersion] forKey:LAST_ICON_UPDATE_VERSION];
    98 	}
   118 	}
    99 }
   119 }
   100 
   120 
   101 - (void)controllerWillClose
   121 - (void)controllerWillClose
   102 {
   122 {
   103 	[adium.preferenceController unregisterPreferenceObserver:self];
   123 	[adium.preferenceController unregisterPreferenceObserver:self];
       
   124 	[adium.chatController unregisterChatObserver:self];
   104 
   125 
   105 	//Reset our icon by removing all icon states (except for the base state)
   126 	//Reset our icon by removing all icon states (except for the base state)
   106 	NSArray *stateArrayCopy = [[activeIconStateArray copy] autorelease]; //Work with a copy, since this array will change as we remove states
   127 	NSArray *stateArrayCopy = [[activeIconStateArray copy] autorelease]; //Work with a copy, since this array will change as we remove states
   107 	NSEnumerator *enumerator = [stateArrayCopy objectEnumerator];
   128 	NSEnumerator *enumerator = [stateArrayCopy objectEnumerator];
   108 	[enumerator nextObject]; //Skip the first icon
   129 	[enumerator nextObject]; //Skip the first icon
   113 	//Force the icon to update
   134 	//Force the icon to update
   114 	[self _buildIcon];
   135 	[self _buildIcon];
   115 }
   136 }
   116 
   137 
   117 
   138 
       
   139 #pragma mark Dock Icon Packs
   118 /*!
   140 /*!
   119  * @brief Returns an array of available dock icon pack paths
   141  * @brief Returns an array of available dock icon pack paths
   120  */
   142  */
   121 - (NSArray *)availableDockIconPacks
   143 - (NSArray *)availableDockIconPacks
   122 {
   144 {
   128 		[iconPackPaths addObject:path];
   150 		[iconPackPaths addObject:path];
   129 	}
   151 	}
   130 	return iconPackPaths;
   152 	return iconPackPaths;
   131 }
   153 }
   132 
   154 
   133 
   155 //Load an icon pack
   134 
   156 - (NSMutableDictionary *)iconPackAtPath:(NSString *)folderPath
       
   157 {
       
   158 	//Load the icon pack
       
   159 	NSDictionary *iconPackDict = [NSDictionary dictionaryWithContentsOfFile:[folderPath stringByAppendingPathComponent:@"IconPack.plist"]];
       
   160 
       
   161 	NSMutableDictionary *iconStateDict = [NSMutableDictionary dictionary];
       
   162 
       
   163 	//Process each state in the icon pack, adding it to the iconStateDict
       
   164 	for (NSString *stateNameKey in [iconPackDict objectForKey:@"State"]) {
       
   165 		NSDictionary *stateDict = [[iconPackDict objectForKey:@"State"] objectForKey:stateNameKey];
       
   166 		AIIconState *iconState = [self iconStateFromStateDict:stateDict folderPath:folderPath];
       
   167 		if (iconState)
       
   168 			[iconStateDict setObject:iconState forKey:stateNameKey];
       
   169 	}
       
   170 
       
   171 	return [NSMutableDictionary dictionaryWithObjectsAndKeys:[iconPackDict objectForKey:@"Description"], @"Description", iconStateDict, @"State", nil];
       
   172 }
       
   173 
       
   174 - (AIIconState *)previewStateForIconPackAtPath:(NSString *)folderPath
       
   175 {
       
   176 	AIIconState	*previewState = nil;
       
   177 	
       
   178 	[self getName:NULL previewState:&previewState forIconPackAtPath:folderPath];
       
   179 	
       
   180 	return previewState;
       
   181 }
       
   182 
       
   183 /*!
       
   184  * @brief Get the name and preview state for a dock icon pack
       
   185  *
       
   186  * @param outName Reference to an NSString, or NULL if this information is not needed
       
   187  * @param outIconState Reference to an AIIconState, or NULL if this information is not needed
       
   188  * @param folderPath The path to the dock icon pack
       
   189  */
       
   190 - (void)getName:(NSString **)outName previewState:(AIIconState **)outIconState forIconPackAtPath:(NSString *)folderPath
       
   191 {
       
   192 	//Load the icon pack
       
   193 	NSDictionary *iconPackDict = [NSDictionary dictionaryWithContentsOfFile:[folderPath stringByAppendingPathComponent:@"IconPack.plist"]];
       
   194 	
       
   195 	//Load the preview state
       
   196 	NSDictionary *stateDict = [[iconPackDict objectForKey:@"State"] objectForKey:@"Preview"];
       
   197 	
       
   198 	if (outIconState) *outIconState = [self iconStateFromStateDict:stateDict folderPath:folderPath];
       
   199 	if (outName) *outName = [[iconPackDict objectForKey:@"Description"] objectForKey:@"Title"];
       
   200 }
       
   201 
       
   202 - (AIIconState *)iconStateFromStateDict:(NSDictionary *)stateDict folderPath:(NSString *)folderPath
       
   203 {
       
   204 	AIIconState		*iconState = nil;
       
   205 	//Get the state information
       
   206 	BOOL _overlay = [[stateDict objectForKey:@"Overlay"] boolValue];
       
   207 	BOOL looping = [[stateDict objectForKey:@"Looping"] boolValue];
       
   208 	
       
   209 	if ([[stateDict objectForKey:@"Animated"] integerValue]) { //Animated State
       
   210 		NSMutableDictionary	*tempIconCache = [NSMutableDictionary dictionary];
       
   211 		
       
   212 		CGFloat delay   = (CGFloat)[[stateDict objectForKey:@"Delay"] doubleValue];
       
   213 		NSArray *imageNameArray = [stateDict objectForKey:@"Images"];
       
   214 
       
   215 		//Load the images
       
   216 		NSMutableArray *imageArray = [NSMutableArray arrayWithCapacity:[imageNameArray count]];
       
   217 		for (NSString *imageName in imageNameArray) {
       
   218 			NSString	*imagePath;
       
   219 			
       
   220 			if ([imageName hasPrefix:DOCK_ICON_INTERNAL_PATH]) {
       
   221 				//Special hack for all the incorrectly made icon packs we have floating around out there :P
       
   222 				imageName = [imageName substringFromIndex:[DOCK_ICON_INTERNAL_PATH length]];
       
   223 				imagePath = [[NSBundle mainBundle] pathForResource:[[[imageName stringByDeletingPathExtension] stringByAppendingString:@"-localized"] stringByAppendingPathExtension:[imageName pathExtension]]
       
   224 				                                            ofType:@""
       
   225 				                                       inDirectory:DOCK_ICON_SHARED_IMAGES];
       
   226 				
       
   227 				if (!imagePath) {
       
   228 					imagePath = [[NSBundle mainBundle] pathForResource:imageName
       
   229 																ofType:@""
       
   230 														   inDirectory:DOCK_ICON_SHARED_IMAGES];
       
   231 				}
       
   232 			} else {
       
   233 				imagePath = [folderPath stringByAppendingPathComponent:imageName];
       
   234 			}
       
   235 			
       
   236 			NSImage *image = [tempIconCache objectForKey:imagePath]; //We re-use the same images for each state if possible to lower memory usage.
       
   237 			if (!image && imagePath) {
       
   238 				image = [[[NSImage alloc] initByReferencingFile:imagePath] autorelease];
       
   239 				if (image)
       
   240 					[tempIconCache setObject:image forKey:imagePath];
       
   241 			}
       
   242 			
       
   243 			if (image)
       
   244 				[imageArray addObject:image];
       
   245 		}
       
   246 		
       
   247 		//Create the state
       
   248 		if (delay != 0 && [imageArray count] != 0) {
       
   249 			iconState = [[AIIconState alloc] initWithImages:imageArray
       
   250 													  delay:delay
       
   251 													looping:looping
       
   252 													overlay:_overlay];
       
   253 		} else {
       
   254 			NSLog(@"Invalid animated icon state");
       
   255 		}
       
   256 	} else { //Static State
       
   257 		NSString	*imageName;
       
   258 		NSString	*imagePath;
       
   259 		NSImage		*image;
       
   260 		
       
   261 		imageName = [stateDict objectForKey:@"Image"];
       
   262 		
       
   263 		if ([imageName hasPrefix:DOCK_ICON_INTERNAL_PATH]) {
       
   264 			//Special hack for all the incorrectly made icon packs we have floating around out there :P
       
   265 			imageName = [imageName substringFromIndex:[DOCK_ICON_INTERNAL_PATH length]];
       
   266 			imagePath = [[NSBundle mainBundle] pathForResource:[[[imageName stringByDeletingPathExtension] stringByAppendingString:@"-localized"] stringByAppendingPathExtension:[imageName pathExtension]]
       
   267 														ofType:@""
       
   268 												   inDirectory:DOCK_ICON_SHARED_IMAGES];
       
   269 			if (!imagePath) {
       
   270 				imagePath = [[NSBundle mainBundle] pathForResource:imageName
       
   271 															ofType:@""
       
   272 													   inDirectory:DOCK_ICON_SHARED_IMAGES];
       
   273 			}
       
   274 		} else {
       
   275 			imagePath = [folderPath stringByAppendingPathComponent:imageName];
       
   276 		}
       
   277 
       
   278 		//Get the state information
       
   279 		image = [[NSImage alloc] initByReferencingFile:imagePath];
       
   280 		
       
   281 		//Create the state
       
   282 		iconState = [[AIIconState alloc] initWithImage:image overlay:_overlay];
       
   283 		[image release];
       
   284 	}
       
   285 
       
   286 	return [iconState autorelease];
       
   287 }
       
   288 
       
   289 /*!
       
   290  * @brief Does the current icon know how to display a given state?
       
   291  */
       
   292 - (BOOL)currentIconSupportsIconStateNamed:(NSString *)inName
       
   293 {
       
   294 	return ([[availableIconStateDict objectForKey:@"State"] objectForKey:inName] != nil);
       
   295 }
       
   296 
       
   297 //Set an icon state from our currently loaded icon pack
       
   298 - (void)setIconStateNamed:(NSString *)inName
       
   299 {
       
   300 	if (![activeIconStateArray containsObject:inName]) {
       
   301 		[activeIconStateArray addObject:inName];
       
   302 		[self _setNeedsDisplay];
       
   303 	}
       
   304 }
       
   305 
       
   306 //Remove an active icon state
       
   307 - (void)removeIconStateNamed:(NSString *)inName
       
   308 {
       
   309 	if ([activeIconStateArray containsObject:inName]) {
       
   310 		[activeIconStateArray removeObject:inName];
       
   311 		[self _setNeedsDisplay];
       
   312 	}
       
   313 }
       
   314 
       
   315 //Set a custom icon state
       
   316 - (void)setIconState:(AIIconState *)iconState named:(NSString *)inName
       
   317 {
       
   318 	[availableDynamicIconStateDict setObject:iconState forKey:inName]; //Add the new state to our available dict
       
   319 	[self setIconStateNamed:inName]; //Set it
       
   320 }
       
   321 
       
   322 #pragma mark Controller
   135 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
   323 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
   136 							object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
   324 							object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
   137 {
   325 {
   138 	if (!key || [key isEqualToString:KEY_ACTIVE_DOCK_ICON]) {		
   326 	if ([group isEqualToString:PREF_GROUP_APPEARANCE]) {
   139 		//Load the new icon pack
   327 		if (!key || [key isEqualToString:KEY_ACTIVE_DOCK_ICON]) {
   140 		NSString *iconPath = [adium pathOfPackWithName:[prefDict objectForKey:KEY_ACTIVE_DOCK_ICON]
   328 			//Load the new icon pack
   141 											 extension:@"AdiumIcon"
   329 			NSString *iconPath = [adium pathOfPackWithName:[prefDict objectForKey:KEY_ACTIVE_DOCK_ICON]
   142 									resourceFolderName:FOLDER_DOCK_ICONS];
   330 												 extension:@"AdiumIcon"
   143 
   331 										resourceFolderName:FOLDER_DOCK_ICONS];
   144 		if (iconPath) {
   332 
   145 			NSMutableDictionary	*newAvailableIconStateDict = [self iconPackAtPath:iconPath];
   333 			if (iconPath) {
   146 			if (newAvailableIconStateDict) {
   334 				NSMutableDictionary	*newAvailableIconStateDict = [self iconPackAtPath:iconPath];
   147 				[availableIconStateDict autorelease]; 
   335 				if (newAvailableIconStateDict) {
   148 				availableIconStateDict = [newAvailableIconStateDict retain];
   336 					[availableIconStateDict autorelease];
   149 			}
   337 					availableIconStateDict = [newAvailableIconStateDict retain];
   150 		}
   338 				}
   151 		
   339 			}
   152 		//Write the icon to the Adium application bundle so finder will see it
   340 			
   153 		//On launch we only need to update the icon file if this is a new version of Adium.  When preferences
   341 			//Write the icon to the Adium application bundle so that Finder will see it.
   154 		//change we always want to update it
   342 			//On launch we only need to update the icon file if this is a new version of Adium.
   155 		if (!firstTime) {
   343 			//When preferences change we always want to update it
   156 			[self updateAppBundleIcon];
   344 			if (!firstTime) {
   157 		}
   345 				[self updateAppBundleIcon];
   158 
   346 			}
   159 		//Recomposite the icon
   347 
   160 		[self _setNeedsDisplay];
   348 			//Recomposite the icon
   161 	}
   349 			[self _setNeedsDisplay];
   162 }
   350 		}
   163 
   351 		else if (!key || [key isEqualToString:KEY_BADGE_DOCK_ICON]) {
   164 - (void)updateAppBundleIcon
   352 			BOOL newShouldBadge = [[prefDict objectForKey:KEY_BADGE_DOCK_ICON] boolValue];
   165 {	
   353 			if (newShouldBadge != shouldBadge) {
   166 	NSImage *image = [[[availableIconStateDict objectForKey:@"State"] objectForKey:@"ApplicationIcon"] image];
   354 				shouldBadge = newShouldBadge;
   167 	if (!image) 
   355 				
   168 		image = [[[availableIconStateDict objectForKey:@"State"] objectForKey:@"Base"] image];
   356 				[self updateDockBadge];
   169 
   357 			}
   170 	if (image) {
   358 		}
   171 		[[NSWorkspace sharedWorkspace] setIcon:image 
   359 		else if (!key || [key isEqualToString:KEY_ANIMATE_DOCK_ICON]) {
   172 									   forFile:[[NSBundle mainBundle] bundlePath]
   360 			BOOL newAnimateDockIcon = [[prefDict objectForKey:KEY_ANIMATE_DOCK_ICON] boolValue];
   173 									   options:0];
   361 			if (newAnimateDockIcon != animateDockIcon) {
   174 
   362 				animateDockIcon = newAnimateDockIcon;
   175 		//Finder won't update Adium's icon to match the new one until it is restarted if we don't
   363 				
   176 		//tell NSWorkspace to note the change.
   364 				[self animateDockIcon];
   177 		[[NSWorkspace sharedWorkspace] noteFileSystemChanged:[[NSBundle mainBundle] bundlePath]];
   365 			}
       
   366 		}
       
   367 	}
       
   368 	
       
   369 	if ([group isEqualToString:PREF_GROUP_STATUS_PREFERENCES]) {
       
   370 		if (!key || [key isEqualToString:KEY_STATUS_CONVERSATION_COUNT]) {
       
   371 			BOOL newShowConversationCount = [[prefDict objectForKey:KEY_STATUS_CONVERSATION_COUNT] boolValue];
       
   372 			if (newShowConversationCount != showConversationCount) {
       
   373 				showConversationCount = newShowConversationCount;
       
   374 				
       
   375 				[self updateDockBadge];
       
   376 			}
       
   377 		}
       
   378 		else if ([key isEqualToString:KEY_STATUS_MENTION_COUNT]) {
       
   379 			//Just update as the counting is handled elsewhere
       
   380 			[self updateDockBadge];
       
   381 		}
   178 	}
   382 	}
   179 }
   383 }
   180 
   384 
   181 //Icons ------------------------------------------------------------------------------------
   385 //Icons ------------------------------------------------------------------------------------
   182 - (void)_setNeedsDisplay
   386 - (void)_setNeedsDisplay
   191 									   userInfo:nil
   395 									   userInfo:nil
   192 										repeats:NO];
   396 										repeats:NO];
   193 	}
   397 	}
   194 }
   398 }
   195 
   399 
   196 //Load an icon pack
   400 - (void)updateAppBundleIcon
   197 - (NSMutableDictionary *)iconPackAtPath:(NSString *)folderPath
   401 {	
   198 {
   402 	NSImage *image = [[[availableIconStateDict objectForKey:@"State"] objectForKey:@"ApplicationIcon"] image];
   199 	//Load the icon pack
   403 	if (!image)
   200 	NSDictionary *iconPackDict = [NSDictionary dictionaryWithContentsOfFile:[folderPath stringByAppendingPathComponent:@"IconPack.plist"]];
   404 		image = [[[availableIconStateDict objectForKey:@"State"] objectForKey:@"Base"] image];
   201 
   405 	
   202 	NSMutableDictionary *iconStateDict = [NSMutableDictionary dictionary];
   406 	if (image) {
   203 
   407 		[[NSWorkspace sharedWorkspace] setIcon:image
   204 	//Process each state in the icon pack, adding it to the iconStateDict
   408 									   forFile:[[NSBundle mainBundle] bundlePath]
   205 	for (NSString *stateNameKey in [iconPackDict objectForKey:@"State"]) {
   409 									   options:0];
   206 		NSDictionary *stateDict = [[iconPackDict objectForKey:@"State"] objectForKey:stateNameKey];
   410 		
   207 		AIIconState *iconState = [self iconStateFromStateDict:stateDict folderPath:folderPath];
   411 		//Finder won't update Adium's icon to match the new one until it is restarted if we don't
   208 		if (iconState)
   412 		//tell NSWorkspace to note the change.
   209 			[iconStateDict setObject:iconState forKey:stateNameKey];
   413 		[[NSWorkspace sharedWorkspace] noteFileSystemChanged:[[NSBundle mainBundle] bundlePath]];
   210 	}
   414 	}
   211 
       
   212 	return [NSMutableDictionary dictionaryWithObjectsAndKeys:[iconPackDict objectForKey:@"Description"], @"Description", iconStateDict, @"State", nil];
       
   213 }
   415 }
   214 
   416 
   215 /*!
   417 /*!
   216  * @brief Get the name and preview steate for a dock icon pack
   418  * @brief Return the dock icon image without any auxiliary states
   217  *
   419  */
   218  * @param outName Reference to an NSString, or NULL if this information is not needed
   420 - (NSImage *)baseApplicationIconImage
   219  * @param outIconState Reference to an AIIconState, or NULL if this information is not needed
   421 {
   220  * @param folderPath The path to the dock icon pack
   422 	NSDictionary	*availableIcons = [availableIconStateDict objectForKey:@"State"];
   221  */
   423 	AIIconState		*baseState = [availableIcons objectForKey:@"Base"];
   222 - (void)getName:(NSString **)outName previewState:(AIIconState **)outIconState forIconPackAtPath:(NSString *)folderPath
   424 	
   223 {	
   425 	if (baseState) {
   224 	//Load the icon pack
   426 		AIIconState		*iconState = [[[AIIconState alloc] initByCompositingStates:[NSArray arrayWithObject:baseState]] autorelease];
   225 	NSDictionary *iconPackDict = [NSDictionary dictionaryWithContentsOfFile:[folderPath stringByAppendingPathComponent:@"IconPack.plist"]];
   427 		return [iconState image];
   226 	
   428 	}
   227 	//Load the preview state
   429 	
   228 	NSDictionary *stateDict = [[iconPackDict objectForKey:@"State"] objectForKey:@"Preview"];
   430 	return nil;
   229 	
   431 }
   230 	if (outIconState) *outIconState = [self iconStateFromStateDict:stateDict folderPath:folderPath];
   432 
   231 	if (outName) *outName = [[iconPackDict objectForKey:@"Description"] objectForKey:@"Title"];
   433 - (void)setOverlay:(NSImage *)newImage
   232 }
   434 {
   233 
   435 	[overlay release];
   234 - (AIIconState *)previewStateForIconPackAtPath:(NSString *)folderPath
   436 	overlay = [newImage retain];
   235 {
   437 	[self updateDockView];
   236 	AIIconState	*previewState = nil;
       
   237 	
       
   238 	[self getName:NULL previewState:&previewState forIconPackAtPath:folderPath];
       
   239 	
       
   240 	return previewState;
       
   241 }
       
   242 
       
   243 - (AIIconState *)iconStateFromStateDict:(NSDictionary *)stateDict folderPath:(NSString *)folderPath
       
   244 {
       
   245 	AIIconState		*iconState = nil;
       
   246 	
       
   247 	if ([[stateDict objectForKey:@"Animated"] integerValue]) { //Animated State
       
   248 		NSMutableDictionary	*tempIconCache = [NSMutableDictionary dictionary];
       
   249 		
       
   250 		//Get the state information
       
   251 		BOOL overlay = [[stateDict objectForKey:@"Overlay"] boolValue];
       
   252 		BOOL looping = [[stateDict objectForKey:@"Looping"] boolValue];
       
   253 		CGFloat delay   = (CGFloat)[[stateDict objectForKey:@"Delay"] doubleValue];
       
   254 		NSArray *imageNameArray = [stateDict objectForKey:@"Images"];
       
   255 
       
   256 		//Load the images
       
   257 		NSMutableArray *imageArray = [NSMutableArray arrayWithCapacity:[imageNameArray count]];
       
   258 		for (NSString *imageName in imageNameArray) {
       
   259 			NSString	*imagePath;
       
   260 			
       
   261 #define DOCK_ICON_INTERNAL_PATH @"../Shared Images/"
       
   262 			if ([imageName hasPrefix:DOCK_ICON_INTERNAL_PATH]) {
       
   263 				//Special hack for all the incorrectly made icon packs we have floating around out there :P
       
   264 				imageName = [imageName substringFromIndex:[DOCK_ICON_INTERNAL_PATH length]];
       
   265 				imagePath = [[NSBundle mainBundle] pathForResource:[[[imageName stringByDeletingPathExtension] stringByAppendingString:@"-localized"] stringByAppendingPathExtension:[imageName pathExtension]]
       
   266 				                                            ofType:@""
       
   267 				                                       inDirectory:@"Shared Dock Icon Images"];
       
   268 				
       
   269 				if (!imagePath) {
       
   270 					imagePath = [[NSBundle mainBundle] pathForResource:imageName
       
   271 																ofType:@""
       
   272 														   inDirectory:@"Shared Dock Icon Images"];
       
   273 				}
       
   274 
       
   275 			} else {
       
   276 				imagePath = [folderPath stringByAppendingPathComponent:imageName];
       
   277 			}
       
   278 			
       
   279 			NSImage *image = [tempIconCache objectForKey:imagePath]; //We re-use the same images for each state if possible to lower memory usage.
       
   280 			if (!image && imagePath) {
       
   281 				image = [[[NSImage alloc] initByReferencingFile:imagePath] autorelease];
       
   282 				if (image) [tempIconCache setObject:image forKey:imagePath];
       
   283 			}
       
   284 			
       
   285 			if (image) [imageArray addObject:image];
       
   286 		}
       
   287 		
       
   288 		//Create the state
       
   289 		if (delay != 0 && [imageArray count] != 0) {
       
   290 			iconState = [[AIIconState alloc] initWithImages:imageArray
       
   291 													  delay:delay
       
   292 													looping:looping
       
   293 													overlay:overlay];
       
   294 		} else {
       
   295 			NSLog(@"Invalid animated icon state");
       
   296 		}
       
   297 		
       
   298 	} else { //Static State
       
   299 		NSString	*imageName;
       
   300 		NSString	*imagePath;
       
   301 		NSImage		*image;
       
   302 		BOOL		overlay;
       
   303 		
       
   304 		imageName = [stateDict objectForKey:@"Image"];
       
   305 		
       
   306 		if ([imageName hasPrefix:DOCK_ICON_INTERNAL_PATH]) {
       
   307 			//Special hack for all the incorrectly made icon packs we have floating around out there :P
       
   308 			imageName = [imageName substringFromIndex:[DOCK_ICON_INTERNAL_PATH length]];
       
   309 			imagePath = [[NSBundle mainBundle] pathForResource:[[[imageName stringByDeletingPathExtension] stringByAppendingString:@"-localized"] stringByAppendingPathExtension:[imageName pathExtension]]
       
   310 														ofType:@""
       
   311 												   inDirectory:@"Shared Dock Icon Images"];
       
   312 			if (!imagePath) {
       
   313 				imagePath = [[NSBundle mainBundle] pathForResource:imageName
       
   314 			                                            ofType:@""
       
   315 			                                       inDirectory:@"Shared Dock Icon Images"];
       
   316 			}
       
   317 		} else {
       
   318 			imagePath = [folderPath stringByAppendingPathComponent:imageName];
       
   319 		}
       
   320 
       
   321 		//Get the state information
       
   322 		image = [[NSImage alloc] initByReferencingFile:imagePath];
       
   323 		overlay = [[stateDict objectForKey:@"Overlay"] boolValue];
       
   324 		
       
   325 		//Create the state
       
   326 		iconState = [[AIIconState alloc] initWithImage:image overlay:overlay];		
       
   327 		[image release];
       
   328 	}
       
   329 
       
   330 	return [iconState autorelease];
       
   331 }
       
   332 
       
   333 //Set an icon state from our currently loaded icon pack
       
   334 - (void)setIconStateNamed:(NSString *)inName
       
   335 {
       
   336 	if (![activeIconStateArray containsObject:inName]) {
       
   337 		[activeIconStateArray addObject:inName]; 	//Add the name to our array
       
   338 		[self _setNeedsDisplay];			//Redisplay our icon
       
   339 	}
       
   340 }
       
   341 
       
   342 //Remove an active icon state
       
   343 - (void)removeIconStateNamed:(NSString *)inName
       
   344 {
       
   345 	if ([activeIconStateArray containsObject:inName]) {
       
   346 		[activeIconStateArray removeObject:inName]; 	//Remove the name from our array
       
   347 		
       
   348 		[self _setNeedsDisplay];			//Redisplay our icon
       
   349 	}
       
   350 }
       
   351 
       
   352 /*!
       
   353  * @brief Does the current icon know how to display a given state?
       
   354  */
       
   355 - (BOOL)currentIconSupportsIconStateNamed:(NSString *)inName
       
   356 {
       
   357 	return ([[availableIconStateDict objectForKey:@"State"] objectForKey:inName] != nil);
       
   358 }
       
   359 
       
   360 //Set a custom icon state
       
   361 - (void)setIconState:(AIIconState *)iconState named:(NSString *)inName
       
   362 {
       
   363     [availableDynamicIconStateDict setObject:iconState forKey:inName]; 	//Add the new state to our available dict
       
   364     [self setIconStateNamed:inName];					//Set it
       
   365 }
   438 }
   366 
   439 
   367 //Build/Pre-render the icon images, start/stop animation
   440 //Build/Pre-render the icon images, start/stop animation
   368 - (void)_buildIcon
   441 - (void)_buildIcon
   369 {
   442 {
   390 		//Generate the composited icon state
   463 		//Generate the composited icon state
   391 		[currentIconState release];
   464 		[currentIconState release];
   392 		currentIconState = [[AIIconState alloc] initByCompositingStates:iconStates];
   465 		currentIconState = [[AIIconState alloc] initByCompositingStates:iconStates];
   393 		
   466 		
   394 		if (![currentIconState animated]) { //Static icon
   467 		if (![currentIconState animated]) { //Static icon
   395 			NSImage *image = [currentIconState image];
   468 			[self updateDockView];
   396 			if (image) {
       
   397 				[[NSApplication sharedApplication] setApplicationIconImage:image];
       
   398 			}
       
   399 		} else { //Animated icon
   469 		} else { //Animated icon
   400 			//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.
   470 			//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.
   401 			[adium.interfaceController registerFlashObserver:self];
   471 			[adium.interfaceController registerFlashObserver:self];
   402 			observingFlash = YES;
   472 			observingFlash = YES;
   403 			
   473 			
   435 - (void)animateIcon:(NSTimer *)timer
   505 - (void)animateIcon:(NSTimer *)timer
   436 {
   506 {
   437 	//Move to the next image
   507 	//Move to the next image
   438 	if (timer) {
   508 	if (timer) {
   439 		[currentIconState nextFrame];
   509 		[currentIconState nextFrame];
   440 	}
   510 		[self updateDockView];
   441 
   511 	}
   442 	//Set the image
       
   443 	NSImage *image = [currentIconState image];
       
   444 	if (image) {
       
   445 		[[NSApplication sharedApplication] setApplicationIconImage:image];
       
   446 	}
       
   447 }
       
   448 
       
   449 //returns the % of the dock icon's full size that it currently is (0.0 - 1.0)
       
   450 - (CGFloat)dockIconScale
       
   451 {
       
   452 	NSScreen *screen = [NSScreen mainScreen];
       
   453 	NSSize trueSize = screen.visibleFrame.size;
       
   454 	NSSize availableSize = screen.frame.size;
       
   455 
       
   456 	CGFloat	dHeight = availableSize.height - trueSize.height;
       
   457 	CGFloat dWidth = availableSize.width - trueSize.width;
       
   458 	CGFloat dockScale = 0;
       
   459 
       
   460 	if (dHeight != 22) { //dock is on the bottom
       
   461 		if (dHeight != 26) { //dock is not hidden
       
   462 			dockScale = (dHeight-22)/128;
       
   463 		}
       
   464 	} else if (dWidth != 0) { //dock is on the side
       
   465 		if (dWidth != 4) { //dock is not hidden
       
   466 			dockScale = (dWidth)/128;
       
   467 		}
       
   468 	} else {
       
   469 		//multiple monitors?
       
   470 		//Add support for multiple monitors
       
   471 	}
       
   472 
       
   473 	if (dockScale <= 0 || dockScale > 1.0f) {
       
   474 		dockScale = 0.3f;
       
   475 	}
       
   476 
       
   477 	return dockScale;
       
   478 }
       
   479 
       
   480 /*!
       
   481  * @brief Return the dock icon image without any auxiliary states
       
   482  */
       
   483 - (NSImage *)baseApplicationIconImage
       
   484 {
       
   485 	NSDictionary	*availableIcons = [availableIconStateDict objectForKey:@"State"];
       
   486 	AIIconState		*baseState = [availableIcons objectForKey:@"Base"];
       
   487 
       
   488 	if (baseState) {
       
   489 		AIIconState		*iconState = [[[AIIconState alloc] initByCompositingStates:[NSArray arrayWithObject:baseState]] autorelease];
       
   490 		return [iconState image];
       
   491 	}
       
   492 	
       
   493 	return nil;
       
   494 }
   512 }
   495 
   513 
   496 //Bouncing -------------------------------------------------------------------------------------------------------------
   514 //Bouncing -------------------------------------------------------------------------------------------------------------
   497 #pragma mark Bouncing
   515 #pragma mark Bouncing
   498 
   516 
   603 //Stop bouncing
   621 //Stop bouncing
   604 - (void)_stopBouncing
   622 - (void)_stopBouncing
   605 {
   623 {
   606 	//Stop any timer
   624 	//Stop any timer
   607 	if (bounceTimer) {
   625 	if (bounceTimer) {
   608 		[bounceTimer invalidate]; 
   626 		[bounceTimer invalidate];
   609 		[bounceTimer release]; 
   627 		[bounceTimer release];
   610 		bounceTimer = nil;
   628 		bounceTimer = nil;
   611 	}
   629 	}
   612 
   630 
   613 	//Stop any continuous bouncing
   631 	//Stop any continuous bouncing
   614 	if (currentAttentionRequest != -1) {
   632 	if (currentAttentionRequest != -1) {
   622 - (void)appWillChangeActive:(NSNotification *)notification
   640 - (void)appWillChangeActive:(NSNotification *)notification
   623 {
   641 {
   624     [self _stopBouncing]; //Stop any bouncing
   642     [self _stopBouncing]; //Stop any bouncing
   625 }
   643 }
   626 
   644 
       
   645 
       
   646 #pragma mark Dock Drawing
       
   647 - (void)updateDockView
       
   648 {
       
   649 	NSImage *image = [[[currentIconState image] copy] autorelease];
       
   650 	if (overlay) {
       
   651 		[image lockFocus];
       
   652 		[overlay drawInRect:[view frame] fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0f];
       
   653 		[image unlockFocus];
       
   654 	}
       
   655 	
       
   656 	[view setImage:image];
       
   657 	[dockTile setContentView:view];
       
   658 	[dockTile display];
       
   659 }
       
   660 
       
   661 - (void)updateDockBadge
       
   662 {
       
   663 	NSInteger contentCount = (showConversationCount ?
       
   664 							 [adium.chatController unviewedConversationCount] : [adium.chatController unviewedContentCount]);
       
   665 	if (contentCount > 0 && shouldBadge)
       
   666 		[dockTile setBadgeLabel:[NSString stringWithFormat:@"%d", contentCount]];
       
   667 	else
       
   668 		[dockTile setBadgeLabel:nil];
       
   669 }
       
   670 
       
   671 - (void)animateDockIcon
       
   672 {
       
   673 	[self updateDockBadge];
       
   674 	
       
   675 	if (adium.chatController.unviewedContentCount && animateDockIcon) {
       
   676 		//If this is the first contact with unviewed content, animate the dock
       
   677 		if (!unviewedState) {
       
   678 			NSString *iconState;
       
   679 			if (([adium.statusController.activeStatusState statusType] == AIInvisibleStatusType) &&
       
   680 				[self currentIconSupportsIconStateNamed:@"InvisibleAlert"]) {
       
   681 				iconState = @"InvisibleAlert";
       
   682 			} else {
       
   683 				iconState = @"Alert";
       
   684 			}
       
   685 			
       
   686 			[self setIconStateNamed:iconState];
       
   687 			unviewedState = YES;
       
   688 		}
       
   689 	} else if (unviewedState) {
       
   690 		//If there are no more contacts with unviewed content, stop animating the dock
       
   691 		[self removeIconStateNamed:@"Alert"];
       
   692 		[self removeIconStateNamed:@"InvisibleAlert"];
       
   693 		unviewedState = NO;
       
   694 	}
       
   695 }
       
   696 
       
   697 /*!
       
   698  * @brief When a chat has unviewed content update the badge and maybe start/stop the animation
       
   699  */
       
   700 - (NSSet *)updateChat:(AIChat *)inChat keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
       
   701 {
       
   702 	if (inModifiedKeys == nil || [inModifiedKeys containsObject:KEY_UNVIEWED_CONTENT]) {
       
   703 		[self animateDockIcon];
       
   704 	}
       
   705 	
       
   706 	return nil;
       
   707 }
       
   708 
   627 @end
   709 @end