/*******************************************************************************
 Copyright (c) 2005-2006 Jordy Rose
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
 
 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.
 
 Except as contained in this notice, the name(s) of the above copyright holders
 shall not be used in advertising or otherwise to promote the sale, use or other
 dealings in this Software without prior authorization.
*******************************************************************************/

//
//  DockyardPreferences.m
//  Dockyard
//
#import "DockyardPreferences.h"

NSString * const DockyardDefaultsDomain		= @"com.belkadan.Dockyard";
NSString * const DockyardOldDefaultsDomain	= @"com.ordinology.belkadan.Dockyard";

NSString * const DockyardDockDefaultsDomain			= @"com.apple.dock";
NSString * const DockyardDashboardDefaultsDomain	= @"com.apple.dashboard";

NSString * const DockyardDockChangedNotification	= @"com.belkadan.Dockyard.dockChanged";
NSString * const DockyardDockCreatedNotification	= @"com.belkadan.Dockyard.dockCreated";
NSString * const DockyardDockRenamedNotification	= @"com.belkadan.Dockyard.dockRenamed";
NSString * const DockyardDocksReorderedNotification	= @"com.belkadan.Dockyard.docksReordered";
NSString * const DockyardDockDeletedNotification	= @"com.belkadan.Dockyard.dockDeleted";
NSString * const DockyardDocksDeletedNotification	= @"com.belkadan.Dockyard.docksDeleted";
NSString * const DockyardDashboardSwitchChangedNotification	= @"com.belkadan.Dockyard.dashboardSwitchChanged";

NSString * const DockyardDefaultsChangedNotification	= @"com.belkadan.Dockyard.defaultsModified";
NSString * const DockyardDockItemsChangedNotification	= @"com.belkadan.Dockyard.dockItemsChanged";

static NSNumber *zero = nil;

/*!
 * This is the central managing class for the entire Dockyard back end, and should be included
 * in any executable that uses Dockyard. All Dockyard-wide defaults and settings,
 * including the Docks themselves, should be accessed through this class, which will
 * automatically synchronize itself with any other instance of DockyardPreferences running.
 * This is very useful when using both the Manager and the menu extra, for example.
 *
 * @version 1.0
 * @author Belkadan Software
 */

@implementation DockyardPreferences
+ (void)initialize {
	NSArray *dockNames = [[NSArray alloc] initWithObjects:@"dockNames", nil];
    [self setKeys:dockNames triggerChangeNotificationsForDependentKey:@"numberOfDocks"];
	[dockNames release];
}

- (id)init
{
	return [self initWithIdentifier:@"com.belkadan.Dockyard.anonymousPreferences"];
}

/*!
 * Creates a new instance of DockyardPreferences. The identifier is currently unused,
 * but may be in the future. It is recommended that each executable's instance of 
 * DockyardPreferences have its own identifier, but this is not possible when encoded
 * in a nib file.
 *
 * @functiongroup Initializing DockyardPreferences
 */
- (id)initWithIdentifier:(NSString *)givenID
{
	self = [super init];
	
	if (self != nil) {		
		noteCenter = [[NSDistributedNotificationCenter defaultCenter] retain];
		[noteCenter addObserver:self selector:@selector(dockyardDockChanged:) name:DockyardDockChangedNotification object:nil];
		[noteCenter addObserver:self selector:@selector(dockyardDockCreated:) name:DockyardDockCreatedNotification object:nil];
		[noteCenter addObserver:self selector:@selector(dockyardDockRenamed:) name:DockyardDockRenamedNotification object:nil];
		[noteCenter addObserver:self selector:@selector(dockyardDocksReordered:) name:DockyardDocksReorderedNotification object:nil];
		[noteCenter addObserver:self selector:@selector(dockyardDockDeleted:) name:DockyardDockDeletedNotification object:nil];
		[noteCenter addObserver:self selector:@selector(dockyardDocksDeleted:) name:DockyardDocksDeletedNotification object:nil];
		[noteCenter addObserver:self selector:@selector(dockyardDashboardSwitchChanged:) name:DockyardDashboardSwitchChangedNotification object:nil];
		[noteCenter addObserver:self selector:@selector(dockyardDefaultsChanged:) name:DockyardDefaultsChangedNotification object:nil];
		[noteCenter addObserver:self selector:@selector(dockyardDockItemsChanged:) name:DockyardDockItemsChangedNotification object:nil];
		
		[NSUserDefaults resetStandardUserDefaults];
		defaults = [NSUserDefaults standardUserDefaults];
		
		// Get rid of compiler warnings
		defaultsDict = (NSMutableDictionary *)[defaults persistentDomainForName:DockyardDefaultsDomain];
		
		if (defaultsDict == nil) {
			defaultsDict = (NSMutableDictionary *)[defaults persistentDomainForName:DockyardOldDefaultsDomain];
//			[defaults removePersistentDomainForName:DockyardOldDefaultsDomain];
		}
		
		defaultsDict = [defaultsDict mutableDeepPropertyListCopy];
		
		currentDock = [[defaultsDict objectForKey:@"currentDock"] retain];
		docks = [[defaultsDict objectForKey:@"docks"] retain];
		shouldSwitchDashboard = [[defaultsDict objectForKey:@"shouldSwitchDashboard"] boolValue];
		
		if (defaultsDict == nil || docks == nil || currentDock == nil || [docks count] == 0) {
			currentDock = [NSLocalizedStringFromTableInBundle(@"Default", nil, [NSBundle bundleForClass:[self class]], @"The name of the dock automatically created when the user first launches Dockyard") retain];
			
			docks = [[NSMutableDictionary alloc] initWithObjectsAndKeys:[[[defaults persistentDomainForName:DockyardDockDefaultsDomain] mutableDeepPropertyListCopy] autorelease], currentDock, nil];
			dashboards = [[NSMutableDictionary alloc] initWithObjectsAndKeys:[[[defaults persistentDomainForName:DockyardDashboardDefaultsDomain] mutableDeepPropertyListCopy] autorelease], currentDock, nil];
			names = [[NSMutableArray alloc] initWithObjects:[[currentDock mutableCopy] autorelease], nil];
	

			defaultsDict = [[NSMutableDictionary alloc] initWithObjectsAndKeys:docks, @"docks",
																		  dashboards, @"dashboards",
																			   names, @"dockNames",
																		 currentDock, @"currentDock",
														[NSNumber numberWithBool:NO], @"shouldSwitchDashboard",
																					  nil];
			[defaults setPersistentDomain:defaultsDict forName:DockyardDefaultsDomain];
						
		} else {
//			docks = [tempDocks mutableCopy];
//			names = (NSMutableArray *)[[(NSArray *)[defaultsDict objectForKey:@"dockNames"] resultsOfMakeObjectsPerformSelector:@selector(mutableCopy)] retain];
//			[names makeObjectsPerformSelector:@selector(release)];
			names = [[defaultsDict objectForKey:@"dockNames"] retain];
			
			dashboards = [[defaultsDict objectForKey:@"dashboards"] retain];
			if (dashboards == nil) {
				NSDictionary *dashboardData = [defaults persistentDomainForName:DockyardDashboardDefaultsDomain];
				dashboards = [[NSMutableDictionary alloc] initWithObjectsAndKeys:[[dashboardData mutableDeepPropertyListCopy] autorelease], currentDock, nil];
			}
		}
				
		identifier = [givenID copy];
		[self update]; // One last synchronization check
	}
	
	[[names objectAtIndex:0] string];
	
	return self;
}

/*!
 * Forces the preferences to synchronize with NSUserDefaults and the hard disk.
 * This does NOT synchronize with other executables currently running Dockyard.
 * All methods that change preferences data automatically call this method, so
 * you should never need to call it directly.
 *
 * @functiongroup Saving data
 */
- (void)update
{
	[defaultsDict setObject:docks forKey:@"docks"];
	[defaultsDict setObject:dashboards forKey:@"dashboards"];
	[defaultsDict setObject:names forKey:@"dockNames"];
	[defaultsDict setObject:currentDock forKey:@"currentDock"];
	[defaultsDict setObject:[NSNumber numberWithBool:shouldSwitchDashboard] forKey:@"shouldSwitchDashboard"];

	[defaults removePersistentDomainForName:DockyardDefaultsDomain];
	[defaults setPersistentDomain:defaultsDict forName:DockyardDefaultsDomain];
	[defaults synchronize];
}

/*!
 * Returns the preferences' delegate.
 *
 * @functiongroup Setting the delegate
 */
- (id)delegate
{
	return delegate;
}

/*!
 * Sets the preferences' delegate. The delegate will receive all of the notifications
 * sent out by DockyardPreferences. However, the preferences instance is guaranteed to have
 * updated itself before forwarding any notifications, making sure that its delegate will
 * have updated data to work with. Note that any object <em>other</em> than the delegate
 * will not have this guarantee, and should take caution when requesting information from
 * the preferences during a notification post.
 */
- (void)setDelegate:(id)newDelegate
{
	delegate = newDelegate;
}

/*!
 * Provides access to Dockyard defaults by returning the value associated with <code>key</code>.
 * This is useful for accessing defaults that affect other aspects of Dockyard besides the dock items themselves.
 *
 * @functiongroup Setting Dockyard-specific defaults
 */
- (id)defaultsValueForKey:(NSString *)key
{
	return [defaultsDict objectForKey:key];
}

/*!
 * Provides access to Dockyard defaults by setting <code>value</code> as the value of <code>key</code>.
 * This is useful for setting defaults that affect other aspects of Dockyard besides the dock items themselves.
 * Note that the keys <code>"docks"</code>, <code>"dashboards"</code>, <code>"dockNames"</code>,
 * <code>"currentDock"</code>, <code>"shouldSwitchDashboard"</code>, and <code>"dockData"</code> are reserved
 * for private use by Dockyard. It is a good idea to follow the Dockyard naming convention of "Application:specificKey"
 * for all custom defaults keys. This avoids potential collisions.
 */
- (void)setDefaultsValue:(id)value forKey:(NSString *)key
{
	static NSArray *forbiddenKeys = nil;
	if (forbiddenKeys == nil)
		forbiddenKeys = [[NSArray alloc] initWithObjects:@"docks", @"dashboards", @"dockNames", @"currentDock", @"shouldSwitchDashboard", @"dockData", nil];
	
	[defaultsDict setValue:value forKey:key];
	[self update];
	
	NSDictionary *temp;
	if (value == nil) {
		value = @"";
		temp = [[NSDictionary alloc] initWithObjectsAndKeys:value, @"value", key, @"key", [NSNumber numberWithBool:YES], @"isNil", nil];
	} else temp = [[NSDictionary alloc] initWithObjectsAndKeys:value, @"value", key, @"key", nil];
	[noteCenter postNotificationName:DockyardDefaultsChangedNotification object:key userInfo:temp deliverImmediately:NO];
	[temp release];
}

- (void)dealloc
{
	[self update];
	
	[noteCenter removeObserver:self];
	[noteCenter release];

	[dashboards release];
	[docks release];
	[currentDock release];
	[names release];
	[defaultsDict release];
	[identifier release];
	[super dealloc];
}

#pragma mark -

/*!
 * Returns the list of docks currently in use. This list is sorted by user preference.
 *
 * @functiongroup Getting dock names and information
 * @see #setDockNames:
 */
- (NSArray *)dockNames
{
	return [[names copy] autorelease];
}

/*!
 * Sets the list of names of docks currently in use to <code>newNames</code>. This method should
 * ALWAYS contain either the same number of docks as before, or have less because of a drag in
 * progress. Do not use this method in code if possible -- it is provided for Key-Value Coding
 * and Cocoa Bindings.
 *
 * @see #dockNames
 * @see #rearrange
 */
- (void)setDockNames:(NSArray *)newNames
{
	[names setArray:newNames];
	[self update];
}

/*!
 * Returns the name of the current dock.
 *
 * @see #dockNames
 */
- (NSString *)currentDock
{
	return [[currentDock copy] autorelease];
}

/*!
 * Returns the number of docks currently in use.
 */
- (unsigned)numberOfDocks
{
	return [names count];
}

/*!
 * Returns <code>YES</code> if Dashboard switching is enabled, <code>NO</code>otherwise.
 *
 * @functiongroup Enabling Dashboard switching
 * @see #setShouldSwitchDashboard:
 */
- (BOOL)shouldSwitchDashboard
{
	return shouldSwitchDashboard;
}

/*!
 * Sets whether or not Dashboard switching is enabled.
 *
 * @see #shouldSwitchDashboard
 */
- (void)setShouldSwitchDashboard:(BOOL)newShouldSwitch
{
	shouldSwitchDashboard = newShouldSwitch;
	[self update];
	
	NSDictionary *temp = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithBool:shouldSwitchDashboard], @"shouldSwitchDashboard", nil];
	[noteCenter postNotificationName:DockyardDashboardSwitchChangedNotification object:identifier userInfo:temp deliverImmediately:NO];
	[temp release];
}

#pragma mark -

/*!
 * Returns the dictionary containing all of the dock data, organized by dock name.
 * This dictionary is not copied because of memory issues, and may be modified by
 * DockyardPreferences, so do not save it anywhere. It is better to get the data
 * specifically using <code>dataForDockWithName:</code> or
 * <code>itemsInSet:ofDock:</code>.
 *
 * @functiongroup Getting dock information
 */
- (NSDictionary *)dataForDocks
{
	return docks;
}

/*!
 * Returns the dictionary containing the dock data for the dock named <code>name</code>.
 * This is in the same format as the defaults domain "com.apple.dock".
 * This dictionary is not copied because of memory issues, and may be modified by
 * DockyardPreferences, so do not save it anywhere without copying yourself.
 * Instead, call this method again when you need it.
 *
 * @see #itemsInSet:ofDock:
 */
- (NSDictionary *)dataForDockWithName:(NSString *)name
{
	return [docks objectForKey:name];
}

/*!
 * Returns the dictionary containing all of the dashboard data, organized by dock name.
 * This dictionary is not copied because of memory issues, and may be modified by
 * DockyardPreferences, so do not save it anywhere. It is better to get the data
 * specifically using <code>dashboardDataForDockWithName:</code> or
 * <code>itemsInSet:ofDock:</code>.
 */
- (NSDictionary *)dashboardDataForDocks
{
	return dashboards;
}

/*!
 * Returns the dictionary containing the dashboard data for the dock named <code>name</code>.
 * This is in the same format as the defaults domain "com.apple.dashboard".
 * This dictionary is not copied because of memory issues, and may be modified by
 * DockyardPreferences, so do not save it anywhere without copying yourself.
 * Instead, call this method again when you need it.
 *
 * @see #itemsInSet:ofDock:
 */
- (NSDictionary *)dashboardDataForDockWithName:(NSString *)name
{
	return [dashboards objectForKey:name];
}

#pragma mark -

/*!
 * Moves the item at <code>sourceIndex</code> to <code>destIndex</code>.
 * <code>whichItems</code> can be the dock applications, dock items, or dashboard
 * widgets of the dock named <code>name</code>. If you are moving more than one
 * item to the same index, <code>moveItemsAtIndexes:toIndex:inSet:ofDock:</code>
 * is more efficient. The returned value is the index of the item after it has
 * been moved, or -1 if it could not be moved.
 *
 * @functiongroup Modifying dock contents
 * @see #moveItemsAtIndexes:toIndex:inSet:ofDock:
 */
- (int)moveItemAtIndex:(unsigned)sourceIndex toIndex:(unsigned)destIndex inSet:(enum DockyardItemSet)whichItems ofDock:(NSString *)name
{
	NSMutableArray *dockItems;
	
	switch (whichItems) {
	case DockyardDockAppSet:
		[self willChangeValueForKey:@"dataForDocks"];
		dockItems = [[docks objectForKey:name] objectForKey:@"persistent-apps"];
		break;
	case DockyardDockItemSet:
		[self willChangeValueForKey:@"dataForDocks"];
		dockItems = [[docks objectForKey:name] objectForKey:@"persistent-others"];
		break;
	case DockyardWidgetSet:
		[self willChangeValueForKey:@"dashboardDataForDocks"];
		dockItems = [[dashboards objectForKey:name] objectForKey:@"layer-gadgets"];
		if (dockItems == nil)
			return -1;
		break;
	default:
		return -1;
	}
	
	id item = [[dockItems objectAtIndex:sourceIndex] retain];
	if (destIndex > sourceIndex)
		destIndex--;

	[dockItems removeObjectAtIndex:sourceIndex];
	[dockItems insertObject:item atIndex:destIndex];
	
	if (whichItems == DockyardWidgetSet)
		[self didChangeValueForKey:@"dashboardDataForDocks"];
	else
		[self didChangeValueForKey:@"dataForDocks"];
	[item release];
	
	[self update];

	NSDictionary *temp = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithInt:whichItems], @"whichItems", name, @"dockName", dockItems, @"newItems", nil];
	[noteCenter postNotificationName:DockyardDockItemsChangedNotification object:identifier userInfo:temp deliverImmediately:NO];
	[temp release];
	
	return destIndex;
}

/*!
 * Moves the items at <code>sourceIndex</code> to <code>destIndex</code>.
 * <code>whichItems</code> can be the dock applications, dock items, or dashboard
 * widgets of the dock named <code>name</code>. If you are moving only one
 * item to the destination index, <code>moveItemAtIndex:toIndex:inSet:ofDock:</code>
 * is more efficient. The returned value is the index of the first item after it has
 * been moved, or -1 if it could not be moved.
 *
 * @see #moveItemAtIndex:toIndex:inSet:ofDock:
 */
- (int)moveItemsAtIndexes:(NSIndexSet *)indexSet toIndex:(unsigned)destIndex inSet:(enum DockyardItemSet)whichItems ofDock:(NSString *)name
{
	NSMutableArray *dockItems;
	
	switch (whichItems) {
	case DockyardDockAppSet:
		[self willChangeValueForKey:@"dataForDocks"];
		dockItems = [[docks objectForKey:name] objectForKey:@"persistent-apps"];
		break;
	case DockyardDockItemSet:
		[self willChangeValueForKey:@"dataForDocks"];
		dockItems = [[docks objectForKey:name] objectForKey:@"persistent-others"];
		break;
	case DockyardWidgetSet:
		[self willChangeValueForKey:@"dashboardDataForDocks"];
		dockItems = [[dashboards objectForKey:name] objectForKey:@"layer-gadgets"];
		if (dockItems == nil)
			return -1;
		break;
	default:
		return -1;
	}
		
	NSArray *itemsToMove = [[dockItems pantherObjectsAtIndexes:indexSet] retain];
	if ([indexSet firstIndex] > destIndex) {
		[dockItems pantherRemoveObjectsAtIndexes:indexSet];
		
	} else if ([indexSet lastIndex] < destIndex) {
		[dockItems pantherRemoveObjectsAtIndexes:indexSet];
		destIndex -= [itemsToMove count];
		
	} else {
		[dockItems pantherRemoveObjectsAtIndexes:indexSet];
		int index;
		for (index = [indexSet indexLessThanIndex:destIndex]; index != NSNotFound; index = [indexSet indexLessThanIndex:index]) {
			destIndex--;
		}
	}
	[dockItems pantherInsertObjects:itemsToMove atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(destIndex, [itemsToMove count])]];
	
	if (whichItems == DockyardWidgetSet)
		[self didChangeValueForKey:@"dashboardDataForDocks"];
	else
		[self didChangeValueForKey:@"dataForDocks"];
	[itemsToMove release];
	
	[self update];

	NSDictionary *temp = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithInt:whichItems], @"whichItems", name, @"dockName", dockItems, @"newItems", nil];
	[noteCenter postNotificationName:DockyardDockItemsChangedNotification object:identifier userInfo:temp deliverImmediately:NO];
	[temp release];
	
	return destIndex;
}

/*!
 * Adds the item at <code>path</code> to the specified item set (applications, items, or widgets) of
 * the specified dock. Returns <code>YES</code> if the item was added successfully, <code>NO</code>
 * if not.
 * WARNING: adding items to the widget set (DockyardWidgetSet) is not yet supported!
 *
 * @see #addURL:withLabel:toDock:
 * @see #addItemWithData:toSet:ofDock:
 * @see #addItemsWithData:toSet:ofDock:
 */
- (BOOL)addItem:(NSString *)path toSet:(enum DockyardItemSet)itemSet ofDock:(NSString *)dockName
{
	if (itemSet == DockyardDockItemSet || itemSet == DockyardDockAppSet) {
		if (zero == nil)
			zero = [[NSNumber alloc] initWithInt:0];

		NSMutableDictionary *topLevel = [NSMutableDictionary new];
		NSMutableDictionary *tileData = [NSMutableDictionary new];
		NSMutableDictionary *fileData = [NSMutableDictionary new];
		
		[fileData setObject:path forKey:@"_CFURLString"];
		[fileData setObject:zero forKey:@"_CFURLStringType"];
		
		[tileData setObject:fileData forKey:@"file-data"];
		[fileData release];
		
		[topLevel setObject:tileData forKey:@"tile-data"];
		[tileData release];
		
		return [self addItemWithData:[topLevel autorelease] toSet:itemSet ofDock:dockName];
/*		
		NSMutableArray *dockItems; 
		[self willChangeValueForKey:@"dataForDocks"];
		if (itemSet == DockyardDockAppSet) {
			dockItems = [[docks objectForKey:dockName] objectForKey:@"persistent-apps"];
		} else {
			dockItems = [[docks objectForKey:dockName] objectForKey:@"persistent-others"];
		}
		[dockItems addObject:topLevel];
		[self didChangeValueForKey:@"dataForDocks"];
		[topLevel release];
		
		[self update];
		
		NSDictionary *temp = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithInt:itemSet], @"whichItems", dockName, @"dockName", dockItems, @"newItems", nil];
		[noteCenter postNotificationName:DockyardDockItemsChangedNotification object:identifier userInfo:temp deliverImmediately:NO];
		[temp release]; */
		
	} else if (itemSet == DockyardWidgetSet) {
		return NO; // Until I can get this to work!
	
/*		static NSFileManager *manager = nil;
		if (manager == nil) manager = [NSFileManager defaultManager];
		
		NSArray *libraryPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask, YES);
		NSString *widgetSubPath = [@"Widgets" stringByAppendingPathComponent:[path lastPathComponent]];
		if ([libraryPaths count] > 0) {
			NSEnumerator *pathEnum = [libraryPaths objectEnumerator];
			NSString *nextPath;
			BOOL widgetIsInstalled;
			while (nextPath = [pathEnum nextObject]) {
				if ([manager fileExistsAtPath:[nextPath stringByAppendingPathComponent:widgetSubPath]]) {
					widgetIsInstalled = YES;
					break;
				}						
			}
			
			if (!widgetIsInstalled) {
				NSString *destination = [[@"~/Library" stringByAppendingPathComponent:widgetSubPath] stringByStandardizingPath];
				if ([manager movePath:path toPath:destination handler:nil])
					path = destination;
			}
		}
		
		NSBundle *widgetBundle = [[NSBundle alloc] initWithPath:path];
		NSMutableDictionary *widgetData = [[NSMutableDictionary alloc] initWithCapacity:8];
		[widgetData setObject:[widgetBundle bundleIdentifier] forKey:@"bundle_id"];
		[widgetData setObject:[widgetBundle objectForInfoDictionaryKey:@"Width"] forKey:@"width"];
		[widgetData setObject:[widgetBundle objectForInfoDictionaryKey:@"Height"] forKey:@"height"];
		
		NSString *label = [widgetBundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];
		[widgetData setObject:label forKey:@"label"];
		
		NSString *bundleVersionString = [widgetBundle objectForInfoDictionaryKey:@"CFBundleVersion"];
		NSScanner *versionScanner = [[NSScanner alloc] initWithString:bundleVersionString];
		int bundleVersion, nextNum;
		if ([versionScanner scanInt:&nextNum]) {
			bundleVersion = nextNum * 10000;
			if ([versionScanner scanString:@"." intoString:nil] && [versionScanner scanInt:&nextNum]) {
				bundleVersion += nextNum * 100;
				if ([versionScanner scanString:@"." intoString:nil] && [versionScanner scanInt:&nextNum]) {
					bundleVersion += nextNum;
				}
			}
		} else bundleVersion = 10000;
		[versionScanner release];
		
		[widgetData setObject:[NSNumber numberWithInt:bundleVersion] forKey:@"version"];
		[widgetData setObject:path forKey:@"path"];
		[widgetData setObject:[NSNumber numberWithBool:YES] forKey:@"enabled"];
		[widgetData setObject:[NSNumber numberWithDouble:[NSDate timeIntervalSinceReferenceDate]] forKey:@"date"];
		
		[self willChangeValueForKey:@"dashboardDataForDocks"];
		NSMutableArray *widgetList = [[dashboards objectForKey:dockName] objectForKey:@"widget-list"];
		int count = [widgetList count];
		CFIndex index = CFArrayBSearchValues((CFArrayRef) [widgetList valueForKey:@"label"],
																	  CFRangeMake(0, count),
																					  label,
													 (CFComparatorFunction) CFStringCompare,
			  (void *) (kCFCompareLocalized | kCFCompareNumerically | kCFCompareNonliteral));
		if (index >= count) {
			[widgetList addObject:widgetData];
		} else {
			[widgetList insertObject:widgetData atIndex:index];
		}
		[self didChangeValueForKey:@"dashboardDataForDocks"];
		[widgetData release]
		
		NSMutableDictionary *widgetData = [[NSMutableDictionary alloc] initWithCapacity:2];
		[widgetData setObject:[NSNumber numberWithBool:YES] forKey:@"in-layer"];
		[widgetData setObject:path forKey:@"path"];
		
//		[widgetData setObject:«64-bit Hex String» forKey:@"id"]; // Need to fix this, but also other problems persist
		
		if (zero == nil)
			zero = [[NSNumber alloc] initWithInt:0];
		
		[widgetData setObject:zero forKey:@"percent-x"];
		[widgetData setObject:zero forKey:@"percent-y"];
		[widgetData setObject:zero forKey:@"percent-offset-x"];
		[widgetData setObject:zero forKey:@"percent-offset-y"];
		[widgetData setObject:zero forKey:@"percent-type"];
		
		NSScreen *screen = [[NSScreen screens] objectAtIndex:0];
		float posX, posY;
		if (screen != nil) {
			NSSize screenSize = [screen frame].size;
			
			NSBundle *widgetBundle = [[NSBundle alloc] initWithPath:path];
			NSNumber *widgetDimension;
			
			widgetDimension = [widgetBundle objectForInfoDictionaryKey:@"Width"];
			if (widgetDimension != nil)
				posX = (screenSize.width - [widgetDimension floatValue]) / 2.0;
			else
				posX = screenSize.width / 2.0;
			
			widgetDimension = [widgetBundle objectForInfoDictionaryKey:@"Height"];
			if (widgetDimension != nil)
				posY = (screenSize.height + [widgetDimension floatValue]) / 2.0;
			else
				posY = screenSize.height / 2.0;
			
			[widgetBundle release];
			
		} else {
			posX = posY = 0.0; // Maybe something more helpful...?
		}
		
		[widgetData setObject:[NSNumber numberWithFloat:posX] forKey:@"pos-x"];
		[widgetData setObject:[NSNumber numberWithFloat:posY] forKey:@"pos-y"];
		
		[self addItemWithData:[widgetData autorelease] toSet:itemSet ofDock:dockName];
		
		[self willChangeValueForKey:@"dashboardDataForDocks"];
		NSMutableArray *layerGadgets = [[dashboards objectForKey:dockName] objectForKey:@"layer-gadgets"];
		if (layerGadgets == nil)
			return NO; // Uninitialized
			
		if ([layerGadgets count] > 0)
			[layerGadgets insertObject:widgetData atIndex:0];
		else
			[layerGadgets addObject:widgetData];
		[self didChangeValueForKey:@"dashboardDataForDocks"];
		[widgetData release];

		[self update];
		
		NSDictionary *temp = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithInt:itemSet], @"whichItems", dockName, @"dockName", layerGadgets, @"newItems", nil];
		[noteCenter postNotificationName:DockyardDockItemsChangedNotification object:identifier userInfo:temp deliverImmediately:NO];
		[temp release]; */
	}
	return NO;
}

/*!
 * Adds the specified URL to the item set of the specified dock, using <code>label</code> as
 * the item title. Returns <code>YES</code> if the item was added successfully, <code>NO</code>
 * if not.
 *
 * @see #addItem:toSet:ofDock:
 * @see #addItemWithData:toSet:ofDock:
 * @see #addItemsWithData:toSet:ofDock:
 */
- (BOOL)addURL:(NSURL *)url withLabel:(NSString *)label toDock:(NSString *)dockName
{
	static NSNumber *fifteen = nil;
	if (fifteen == nil)
		fifteen = [[NSNumber alloc] initWithInt:15];
	
	NSString *urlString = [url absoluteString];
	if (label == nil)
		label = urlString;

	NSMutableDictionary *topLevel	= [NSMutableDictionary new];
	NSMutableDictionary *tileData	= [NSMutableDictionary new];
	NSMutableDictionary *urlData	= [NSMutableDictionary new];
	
	[urlData setObject:urlString forKey:@"_CFURLString"];
	[urlData setObject:fifteen forKey:@"_CFURLStringType"];
	
	[tileData setObject:urlData forKey:@"url"];
	[tileData setObject:label forKey:@"label"];
	[urlData release];
	
	[topLevel setObject:tileData forKey:@"tile-data"];
	[topLevel setObject:@"url-tile" forKey:@"tile-type"];
	[tileData release];
	
	return [self addItemWithData:[topLevel autorelease] toSet:DockyardDockItemSet ofDock:dockName];
}

/*!
 * Adds the item specified in <code>data</code> to the specified item set (applications, items, or widgets) of
 * the specified dock. <code>data</code> must be in the proper format for its item set. This means it must work
 * as a member of the "persistent-apps," "persistent-others," or "layer-gadgets" arrays in Apple's dock and
 * dashboard defaults. Returns <code>YES</code> if the item was added successfully, <code>NO</code> if not.
 *
 * @see #addItem:toSet:ofDock:
 * @see #addURL:withLabel:toDock:
 * @see #addItemsWithData:toSet:ofDock:
 */
- (BOOL)addItemWithData:(NSDictionary *)data toSet:(enum DockyardItemSet)itemSet ofDock:(NSString *)dockName
{
	NSMutableArray *items; 
	if (itemSet == DockyardDockAppSet || itemSet == DockyardDockItemSet) {
		[self willChangeValueForKey:@"dataForDocks"];
		if (itemSet == DockyardDockAppSet) {
			items = [[docks objectForKey:dockName] objectForKey:@"persistent-apps"];
		} else {
			items = [[docks objectForKey:dockName] objectForKey:@"persistent-others"];
		}
		[items addObject:data];
		[self didChangeValueForKey:@"dataForDocks"];
		
	} else if (itemSet == DockyardWidgetSet) {
		[self willChangeValueForKey:@"dashboardDataForDocks"];
		items = [[dashboards objectForKey:dockName] objectForKey:@"layer-gadgets"];
		if (items == nil)
			return NO; // Uninitialized
			
		if ([items count] > 0)
			[items insertObject:data atIndex:0];
		else
			[items addObject:data];
		[self didChangeValueForKey:@"dashboardDataForDocks"];
	}
	
	[self update];
	
	NSDictionary *temp = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithInt:itemSet], @"whichItems", dockName, @"dockName", items, @"newItems", nil];
	[noteCenter postNotificationName:DockyardDockItemsChangedNotification object:identifier userInfo:temp deliverImmediately:NO];
	[temp release];
	
	return YES;
}

/*!
 * Adds the items specified in <code>data</code> to the specified item set (applications, items, or widgets) of
 * the specified dock. The members of <code>data</code> must be NSDictionaries in the proper format for the
 * given item set. This means they must work as a member of the "persistent-apps," "persistent-others," or
 * "layer-gadgets" arrays in Apple's dock and dashboard defaults. Returns <code>YES</code> if the items were
 * added successfully, <code>NO</code> if not.
 *
 * @see #addItem:toSet:ofDock:
 * @see #addURL:withLabel:toDock:
 * @see #addItemWithData:toSet:ofDock:
 */
- (BOOL)addItemsWithData:(NSArray *)data toSet:(enum DockyardItemSet)itemSet ofDock:(NSString *)dockName
{
	NSMutableArray *items; 
	if (itemSet == DockyardDockAppSet || itemSet == DockyardDockItemSet) {
		[self willChangeValueForKey:@"dataForDocks"];
		if (itemSet == DockyardDockAppSet) {
			items = [[docks objectForKey:dockName] objectForKey:@"persistent-apps"];
		} else {
			items = [[docks objectForKey:dockName] objectForKey:@"persistent-others"];
		}
		[items addObjectsFromArray:data];
		[self didChangeValueForKey:@"dataForDocks"];
		
	} else if (itemSet == DockyardWidgetSet) {
		[self willChangeValueForKey:@"dashboardDataForDocks"];
		items = [[dashboards objectForKey:dockName] objectForKey:@"layer-gadgets"];
		if (items == nil)
			return NO; // Uninitialized
			
		if ([items count] > 0)
			[items pantherInsertObjects:data atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [data count])]];
		else
			[items addObjectsFromArray:data];
		[self didChangeValueForKey:@"dashboardDataForDocks"];
	}
	
	[self update];
	NSDictionary *temp = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithInt:itemSet], @"whichItems", dockName, @"dockName", items, @"newItems", nil];
	[noteCenter postNotificationName:DockyardDockItemsChangedNotification object:identifier userInfo:temp deliverImmediately:NO];
	[temp release];
	
	return YES;
}

/*!
 * Removes the item at <code>index</code> from the specified item set of the specified dock.
 * If you are removing more than one item, <code>removeItemsAtIndexes:fromSet:ofDock:</code>
 * is more efficient.
 *
 * @see #removeItemsAtIndexes:fromSet:ofDock:
 */
- (void)removeItemAtIndex:(unsigned)index fromSet:(enum DockyardItemSet)itemSet ofDock:(NSString *)dockName
{
	NSMutableArray *dockItems;
	if (itemSet == DockyardDockItemSet || itemSet == DockyardDockAppSet) {
		[self willChangeValueForKey:@"dataForDocks"];
		if (itemSet == DockyardDockAppSet) {
			dockItems = [[docks objectForKey:dockName] objectForKey:@"persistent-apps"];
		} else {
			dockItems = [[docks objectForKey:dockName] objectForKey:@"persistent-others"];
		}
		[dockItems removeObjectAtIndex:index];
		[self didChangeValueForKey:@"dataForDocks"];
				
	} else if (itemSet == DockyardWidgetSet) {
		[self willChangeValueForKey:@"dashboardDataForDocks"];
		dockItems = [[dashboards objectForKey:dockName] objectForKey:@"layer-gadgets"];
		[dockItems removeObjectAtIndex:index];
		[self didChangeValueForKey:@"dashboardDataForDocks"];
	}
	
	[self update];
	
	NSDictionary *temp = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithInt:itemSet], @"whichItems", dockName, @"dockName", dockItems, @"newItems", nil];
	[noteCenter postNotificationName:DockyardDockItemsChangedNotification object:identifier userInfo:temp deliverImmediately:NO];
	[temp release];
}

/*!
 * Removes the items at <code>indexSet</code> from the specified item set of the specified dock.
 * If you are only removing one item, <code>removeItemAtIndex:fromSet:ofDock:</code> is more efficient.
 *
 * @see #removeItemAtIndex:fromSet:ofDock:
 */
- (void)removeItemsAtIndexes:(NSIndexSet *)indexSet fromSet:(enum DockyardItemSet)itemSet ofDock:(NSString *)dockName
{
	NSMutableArray *dockItems;
	if (itemSet == DockyardDockItemSet || itemSet == DockyardDockAppSet) {
		[self willChangeValueForKey:@"dataForDocks"];
		if (itemSet == DockyardDockAppSet) {
			dockItems = [[docks objectForKey:dockName] objectForKey:@"persistent-apps"];
		} else {
			dockItems = [[docks objectForKey:dockName] objectForKey:@"persistent-others"];
		}
		[dockItems pantherRemoveObjectsAtIndexes:indexSet];
		[self didChangeValueForKey:@"dataForDocks"];
				
	} else if (itemSet == DockyardWidgetSet) {
		[self willChangeValueForKey:@"dashboardDataForDocks"];
		dockItems = [[dashboards objectForKey:dockName] objectForKey:@"layer-gadgets"];
		[dockItems pantherRemoveObjectsAtIndexes:indexSet];
		[self didChangeValueForKey:@"dashboardDataForDocks"];
	}
	
	[self update];
	
	NSDictionary *temp = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithInt:itemSet], @"whichItems", dockName, @"dockName", dockItems, @"newItems", nil];
	[noteCenter postNotificationName:DockyardDockItemsChangedNotification object:identifier userInfo:temp deliverImmediately:NO];
	[temp release];
}

/*!
 * Returns the items in the specified item set of the specified dock. The returned array is in
 * the form of "persistent-apps", "persistent-others", or "layer-gadgets" from Apple's defaults,
 * depending on which item set is asked for. This is the preferred way to retrieve dock items,
 * but the result is not a copy due to memory issues. Because it may be modified later by
 * DockyardPreferences, it is better to call this method again whenever needed, rather than
 * retaining the result.
 *
 * @see #setItems:inSet:ofDock:
 */
- (NSArray *)itemsInSet:(enum DockyardItemSet)itemSet ofDock:(NSString *)dockName
{
	if (itemSet == DockyardDockAppSet) {
		return [[docks objectForKey:dockName] objectForKey:@"persistent-apps"];
		
	} else if (itemSet == DockyardDockItemSet) {
		return [[docks objectForKey:dockName] objectForKey:@"persistent-others"];
		
	} else if (itemSet == DockyardWidgetSet) {
		return [[dashboards objectForKey:dockName] objectForKey:@"layer-gadgets"];
	}
	return nil;
}

// WARNING! Do not use arbitrarily...<items> must have the proper format for persistent-apps, persistent-others, or layer-gadgets,
// depending on which item set is used.
/*!
 * Sets the items in the specified item set of the specified dock to <code>dockItems</code>. This MUST
 * be in the form of "persistent-apps", "persistent-others", or "layer-gadgets" from Apple's defaults
 * (depending on the item set being modified), or previously copied from <code>itemsInSet:ofDock</code>.
 *
 * @see #itemsInSet:ofDock:
 */
- (void)setItems:(NSArray *)dockItems inSet:(enum DockyardItemSet)itemSet ofDock:(NSString *)dockName
{
	if (itemSet == DockyardDockAppSet) {
		[self willChangeValueForKey:@"dataForDocks"];
		[[docks objectForKey:dockName] setObject:[[dockItems mutableDeepPropertyListCopy] autorelease] forKey:@"persistent-apps"];
		[self didChangeValueForKey:@"dataForDocks"];
		
	} else if (itemSet == DockyardDockItemSet) {
		[self willChangeValueForKey:@"dataForDocks"];
		[[docks objectForKey:dockName] setObject:[[dockItems mutableDeepPropertyListCopy] autorelease] forKey:@"persistent-others"];
		[self didChangeValueForKey:@"dataForDocks"];
		
	} else if (itemSet == DockyardWidgetSet) {
		[self willChangeValueForKey:@"dashboardDataForDocks"];
		[[dashboards objectForKey:dockName] setObject:[[dockItems mutableDeepPropertyListCopy] autorelease] forKey:@"layer-gadgets"];
		[self didChangeValueForKey:@"dashboardDataForDocks"];
	}
	
	[self update];
	
	NSDictionary *temp = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithInt:itemSet], @"whichItems", dockName, @"dockName", dockItems, @"newItems", nil];
	[noteCenter postNotificationName:DockyardDockItemsChangedNotification object:identifier userInfo:temp deliverImmediately:NO];
	[temp release];
}

#pragma mark -
/*!
 * Creates a dock with the specified name, using the data from the file at <code>dockPath</code>, and, if provided,
 * the dashboard data from the file at <code>dashboardPath</code>. <code>dashboardPath</code> is optional...pass
 * <code>nil</code> to avoid setting any dashboard data for this dock. Make sure that the files at each path
 * are in the same format as the Apple defaults domains "com.apple.dock" and "com.apple.dashboard". This is the
 * preferred way to create docks if all you have is a file path. Returns <code>YES</code> if the dock was created,
 * <code>NO</code> if not.
 *
 * @functiongroup Creating and deleting docks
 * @see #createDockWithName:fromData:dashboardData:
 * @see #createDocksWithNames:fromData:dashboardData:
 */
- (BOOL)createDockWithName:(NSString *)name fromFile:(NSString *)dockPath dashboardFile:(NSString *)dashboardPath
{
	if (name == nil || dockPath == nil || [names containsObject:name]) {
		return NO;
	}
	
	NSDictionary *dockData = [[NSDictionary dictionaryWithContentsOfFile:dockPath] mutableDeepPropertyListCopy];
//	[docks setObject:dockData forKey:name];
//	[dockData release];
	
	NSDictionary *dashboardData = nil;
	if (dashboardPath != nil) {
		dashboardData = [[NSDictionary dictionaryWithContentsOfFile:dashboardPath] mutableDeepPropertyListCopy];
//		if (dashboardData != nil) {
//			[dashboards setObject:dashboardData forKey:name];
//			[dashboardData release];
//		}
	}
	
	if (dockData != nil)
		return [self createDockWithName:name data:[dockData autorelease] dashboardData:[dashboardData autorelease]];
	else
		return NO;
	
/*	int index = [names count]; // Last index = old last + 1 = old count
	NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndex:index];
	
	[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:@"dockNames"];
	name = [name mutableCopy];
	[names addObject:name];
	[self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:@"dockNames"];
	
	[indexSet release];

	[self update];

	NSDictionary *userInfo;
	if (dashboardPath != nil)
		userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:name, @"name", dockData, @"dockData", dashboardData, @"dashboardData", nil];
	else
		userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:name, @"name", dockData, @"dockData", nil];
		
	[noteCenter postNotificationName:DockyardDockCreatedNotification object:identifier userInfo:userInfo deliverImmediately:NO];
	[userInfo release];
	[name release];

	return YES; */
}

/*!
 * Creates a dock with the specified name, using the data from <code>dockData</code>, and, if provided, the
 * dashboard data from <code>dashboardData</code>. <code>dashboardData</code> is optional...pass <code>nil</code>
 * to avoid setting any dashboard data for this dock. Make sure that the data dictionaries are in the same format
 * as the Apple defaults domains "com.apple.dock" and "com.apple.dashboard". This is the preferred way to create
 * docks if you have the data. Returns <code>YES</code> if the dock was created, <code>NO</code> if not.
 *
 * @see #createDockWithName:fromFile:dashboardFile:
 * @see #createDocksWithNames:fromData:dashboardData:
 */
- (BOOL)createDockWithName:(NSString *)name data:(NSDictionary *)dockData dashboardData:(NSDictionary *)dashboardData
{
	if (name == nil || [names containsObject:name])
		return NO;
	
	NSDictionary *temp;
	
	temp = [dockData mutableDeepPropertyListCopy];
	[docks setObject:temp forKey:name];
	[temp release];
	
	if (dashboardData != nil) {
		temp = [dashboardData mutableDeepPropertyListCopy];
		[dashboards setObject:temp forKey:name];
		[temp release];
	}
	
	int index = [names count]; // Last index = old last + 1 = old count
	NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndex:index];
	
	[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:@"dockNames"];
	name = [name mutableCopy];
	[names addObject:name];
	[self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:@"dockNames"];
	
	[indexSet release];

	[self update];

	NSDictionary *userInfo;
	if (dashboardData != nil)
		userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:name, @"name", dockData, @"dockData", dashboardData, @"dashboardData", nil];
	else
		userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:name, @"name", dockData, @"dockData", nil];
		
	[noteCenter postNotificationName:DockyardDockCreatedNotification object:identifier userInfo:userInfo deliverImmediately:NO];
	[userInfo release];
	[name release];

	return YES;
}

/*!
 * Creates docks with the specified names, using the data from <code>dockData</code>, and, if provided, the
 * dashboard data from <code>dashboardData</code>. There must be the same number of items in all three arrays.
 * To avoid setting dashboard data for a particular dock, use an instance of NSNull as a placeholder.
 * Make sure that each data dictionary element is in the same format as the Apple defaults domains
 * "com.apple.dock" or "com.apple.dashboard". This is the preferred way to create multiple docks.
 * Returns <code>YES</code> if <em>every</em> dock was created, <code>NO</code> if not. Note that if only some
 * docks are created, this method will return <code>NO</code>.
 *
 * @see #createDockWithName:fromFile:dashboardFile:
 * @see #createDockWithName:fromData:dashboardData:
 */
- (BOOL)createDocksWithNames:(NSArray *)dockNames data:(NSArray *)dockData dashboardData:(NSArray *)dashboardData
{
	int count = [dockNames count];
	int i;
	BOOL result = YES;
		
	NSString *nextName;
	NSDictionary *nextDock;
	NSDictionary *nextDashboard;
	
	for (i = 0; i < count; i++) {
		nextName = [dockNames objectAtIndex:i];
		nextDock = [dockData objectAtIndex:i];
		nextDashboard = [dashboardData objectAtIndex:i];
		if (![nextDashboard isKindOfClass:[NSDictionary class]])
			nextDashboard = nil;
		
		result = result && [self createDockWithName:nextName data:nextDock dashboardData:nextDashboard];
	}
	
	return result;
}

/*!
 * Removes the dock called <code>name</code>. Returns <code>YES</code> if the deletion was successful,
 * <code>NO</code> if not. This is the preferred way to delete a dock if all you know is its name.
 *
 * @see #deleteDockAtIndex:
 * @see #deleteDocksAtIndexes:
 */
- (BOOL)deleteDockWithName:(NSString *)name
{
	return [self deleteDockAtIndex:[names indexOfObject:name]];
}

/*!
 * Removes the dock at the specified index. Returns <code>YES</code> if the deletion was successful,
 * <code>NO</code> if not. This is the preferred way to delete a dock if you its position in the
 * list of docks.
 *
 * @see #deleteDockWithName:
 * @see #deleteDocksAtIndexes:
 */
- (BOOL)deleteDockAtIndex:(unsigned)index
{
	NSString *name = [[names objectAtIndex:index] retain];
	NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndex:index];
	
	[self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexSet forKey:@"dockNames"];
	[names removeObjectAtIndex:index];
	[self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexSet forKey:@"dockNames"];
	
	[indexSet release];
	
	[dashboards removeObjectForKey:name];
	[docks removeObjectForKey:name];
	[self update];
		
	NSDictionary *temp = [[NSDictionary alloc] initWithObjectsAndKeys:name, @"name", nil];
	[noteCenter postNotificationName:DockyardDockDeletedNotification object:identifier userInfo:temp deliverImmediately:NO];
	[temp release];
	
	[name release];
	
	return YES;
}

/*!
 * Removes the dock at the specified indexes. Returns <code>YES</code> if the deletion was successful,
 * <code>NO</code> if not. Note that if <em>any</em> one of the docks cannot be deleted, this method
 * will return <code>NO</code>. This is the preferred way to delete multiple docks.
 *
 * @see #deleteDockWithName:
 * @see #deleteDockAtIndex:
 */
- (BOOL)deleteDocksAtIndexes:(NSIndexSet *)indexSet
{
//	int index = [names indexOfObject:namesToDelete];
//	NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndex:index];
	if ([indexSet count] == 1)
		return [self deleteDockAtIndex:[indexSet firstIndex]];

	NSArray *namesToDelete = [[names pantherObjectsAtIndexes:indexSet] retain];
	
	[self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexSet forKey:@"dockNames"];
	[names pantherRemoveObjectsAtIndexes:indexSet];
	[self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexSet forKey:@"dockNames"];
		
	[dashboards removeObjectsForKeys:namesToDelete];
	[docks removeObjectsForKeys:namesToDelete];
	[self update];
		
	NSDictionary *temp = [[NSDictionary alloc] initWithObjectsAndKeys:[NSKeyedArchiver archivedDataWithRootObject:indexSet], @"indexSet", [namesToDelete objectAtIndex:0], @"firstName", nil];
	[noteCenter postNotificationName:DockyardDocksDeletedNotification object:identifier userInfo:temp deliverImmediately:NO];
	[temp release];
	
	[namesToDelete release];
	
	return YES;
}

/*!
 * Renames the dock named <code>oldName</code> to <code>newName</code>. Returns <code>YES</code>
 * if the renaming was successful, <code>NO</code> if not. No two docks can have the same name.
 *
 * @functiongroup Renaming and reordering docks
 */
- (BOOL)renameDock:(NSString *)oldName to:(NSString *)newName
{
	if ([docks objectForKey:newName] != nil || [docks objectForKey:oldName] == nil) {
		return NO;
	}
	
	[oldName retain];
	[docks setObject:[docks objectForKey:oldName] forKey:newName];
	[docks removeObjectForKey:oldName];
	
	NSDictionary *dashboardData = [dashboards objectForKey:oldName];
	if (dashboardData != nil) {
		[dashboards setObject:dashboardData forKey:newName];
		[dashboards removeObjectForKey:oldName];
	}
	
	int index = [names indexOfObject:oldName];
	if (index == NSNotFound)
		index = [names indexOfObject:newName]; // Already changed;
	NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndex:index];
	
	[self willChange:NSKeyValueChangeReplacement valuesAtIndexes:indexSet forKey:@"dockNames"];
	newName = [newName mutableCopy];
	[names replaceObjectAtIndex:index withObject:newName];
	[self didChange:NSKeyValueChangeReplacement valuesAtIndexes:indexSet forKey:@"dockNames"];
	
	[indexSet release];
	
	if ([currentDock isEqualToString:oldName]) {
		[self willChangeValueForKey:@"currentDock"];
		[currentDock release];
		currentDock = [newName retain];
		[self didChangeValueForKey:@"currentDock"];
	}
	
	[self update];

	NSDictionary *temp = [[NSDictionary alloc] initWithObjectsAndKeys:oldName, @"oldName", newName, @"newName", nil];
	[noteCenter postNotificationName:DockyardDockRenamedNotification object:identifier userInfo:temp deliverImmediately:NO];
	[temp release];
	[oldName release];
	[newName release];

	return YES;
}

/*!
 * Informs the receiver that a rearrangement has taken place. Unlike the other dock-modifying commands,
 * this method actually takes no action besides synchronizing the new arrangement with other executables.
 * This method should be called after calling <code>setDockNames:</code> in order to make the changes
 * affect all instances of DockyardPreferencs. These methods are not combined into one because of the
 * cost involved -- synchronizing during every drag and drop is too memory-intensive. Returns
 * <code>YES</code> if the rearrangment was successful, <code>NO</code> if not.
 *
 * @see #setDockNames:
 */
- (BOOL)rearrange
{
	[self update];
	
	NSDictionary *temp = [[NSDictionary alloc] initWithObjectsAndKeys:names, @"dockNames", nil];
	[noteCenter postNotificationName:DockyardDocksReorderedNotification object:identifier userInfo:temp deliverImmediately:NO];
	[temp release];
	
	return YES;
}

/*!
 * Changes to the dock named <code>newDock</code>. This is the most important method in the
 * entire Dockyard library, and perhaps one of the simplest to invoke. Returns <code>YES</code>
 * if the dock was changed, <code>NO</code> if not.
 *
 * @functiongroup Switching docks
 */
- (BOOL)changeDock:(NSString *)newDock
{
	static NSArray *killArguments = nil;
	if (killArguments == nil) killArguments = [[NSArray alloc] initWithObjects:@"Dock", nil];

	NSDictionary *oldDockData, *oldDashboardData, *newDockData, *newDashboardData, *noteInfo;
	NSString *oldDock;
	
	if ([currentDock isEqualToString:newDock]) return NO;
		
//	[defaults synchronize]; // Just in case the dock or dashboard has changed
	[NSUserDefaults resetStandardUserDefaults]; // This is a better way to do it.
	defaults = [NSUserDefaults standardUserDefaults];
	
	oldDockData = [[defaults persistentDomainForName:DockyardDockDefaultsDomain] mutableDeepPropertyListCopy];
	[docks setObject:oldDockData forKey:currentDock];
		
	if (shouldSwitchDashboard) {
		oldDashboardData = [[defaults persistentDomainForName:DockyardDashboardDefaultsDomain] mutableDeepPropertyListCopy];
		if (oldDashboardData != nil)
			[dashboards setObject:oldDashboardData forKey:currentDock];
	} else oldDashboardData = nil;
	
	[self willChangeValueForKey:@"currentDock"];
	oldDock = currentDock; // released at end
	currentDock = [newDock retain];
	[self didChangeValueForKey:@"currentDock"];
	
	newDockData = [docks objectForKey:currentDock];
	[defaults setPersistentDomain:newDockData forName:DockyardDockDefaultsDomain];
	
	if (shouldSwitchDashboard) {
		newDashboardData = [dashboards objectForKey:currentDock];
		if (newDashboardData != nil)
			[defaults setPersistentDomain:newDashboardData forName:DockyardDashboardDefaultsDomain];
	}
	
	[NSTask launchedTaskWithLaunchPath:@"/usr/bin/killall" arguments:killArguments];
	
	[self update];

	if (shouldSwitchDashboard && oldDashboardData != nil)
		noteInfo = [[NSDictionary alloc] initWithObjectsAndKeys:oldDock, @"oldDock", newDock, @"newDock", oldDockData, @"oldDockData", oldDashboardData, @"oldDashboardData", nil];
	else
		noteInfo = [[NSDictionary alloc] initWithObjectsAndKeys:oldDock, @"oldDock", newDock, @"newDock", oldDockData, @"oldDockData", nil];
		
	[noteCenter postNotificationName:DockyardDockChangedNotification object:identifier userInfo:noteInfo deliverImmediately:NO];
	[noteInfo release];
	[oldDock release];
	[oldDockData release];
	[oldDashboardData release];
	
	return YES;
}

#pragma mark -

- (void)dockyardDefaultsChanged:(NSNotification *)note
{
	NSDictionary *changeInfo = [note userInfo];
	id value = [changeInfo objectForKey:@"value"];
	if ([value isEqual:@""] && [changeInfo objectForKey:@"isNil"] != nil)
		value = nil;
	[defaultsDict setValue:value forKey:[changeInfo objectForKey:@"key"]];
	
	if ([delegate respondsToSelector:_cmd]) {
		[delegate performSelector:_cmd withObject:note];
	}
}

- (void)dockyardDashboardSwitchChanged:(NSNotification *)note
{
	[self willChangeValueForKey:@"shouldSwitchDashboard"];
	shouldSwitchDashboard = [[[note userInfo] objectForKey:@"shouldSwitchDashboard"] boolValue];
	[self didChangeValueForKey:@"shouldSwitchDashboard"];
	
	if ([delegate respondsToSelector:_cmd]) {
		[delegate performSelector:_cmd withObject:note];
	}
}

- (void)dockyardDockItemsChanged:(NSNotification *)note
{
	NSDictionary *info = [note userInfo];
	enum DockyardItemSet whichItems = [[info objectForKey:@"whichItems"] intValue];
	NSArray *newItems = [[info objectForKey:@"newItems"] mutableCopy];
	NSString *whichDock = [info objectForKey:@"dockName"];
	NSMutableDictionary *currentDockData;
	
	if (whichItems == DockyardWidgetSet) {
		[self willChangeValueForKey:@"dashboardDataForDocks"];
		currentDockData = [dashboards objectForKey:whichDock];
		[currentDockData setObject:newItems forKey:@"layer-gadgets"];
		[self didChangeValueForKey:@"dashboardDataForDocks"];
		
	} else if (whichItems != DockyardUnknownSet) {
		[self willChangeValueForKey:@"dataForDocks"];
		currentDockData = [docks objectForKey:whichDock];
		[currentDockData setObject:newItems forKey:(whichItems == DockyardDockAppSet) ? @"persistent-apps" : @"persistent-others"];
		[self didChangeValueForKey:@"dataForDocks"];
	}
	
	[newItems release];
	
	if ([delegate respondsToSelector:_cmd]) {
		[delegate performSelector:_cmd withObject:note];
	}
}

#pragma mark -

- (void)dockyardDockChanged:(NSNotification *)note
{
	NSDictionary *info = [note userInfo];
	NSString *newDock = [info objectForKey:@"newDock"];
	
	if (![currentDock isEqual:newDock]) {
		NSString *oldDock = [info objectForKey:@"oldDock"];
		NSDictionary *oldDockData = [[info objectForKey:@"oldDockData"] mutableCopy];
		NSDictionary *oldDashboardData = [[info objectForKey:@"oldDashboardData"] mutableCopy];
		
		[docks setObject:oldDockData forKey:oldDock];
		if (oldDashboardData != nil)
			[dashboards setObject:oldDashboardData forKey:oldDock];
		
		[oldDockData release];
		[oldDashboardData release];
		
		[self willChangeValueForKey:@"currentDock"];
		[currentDock release];
		currentDock = [newDock retain];
		[self didChangeValueForKey:@"currentDock"];
	}
	
	if ([delegate respondsToSelector:_cmd]) {
		[delegate performSelector:_cmd withObject:note];
	}
}

- (void)dockyardDockCreated:(NSNotification *)note
{
	NSDictionary *info = [note userInfo];
	NSString *name = [info objectForKey:@"name"];
	
	if ([names indexOfObject:name] == NSNotFound) {		
		NSDictionary *temp = [[info objectForKey:@"dockData"] mutableDeepPropertyListCopy];
		[docks setObject:temp forKey:name];
		[temp release];
		
		temp = [info objectForKey:@"dashboardData"];
		if (temp != nil) {
			temp = [temp mutableDeepPropertyListCopy];
			[dashboards setObject:temp forKey:name];
			[temp release];
		}
		
		int index = [names count]; // Last index = old last + 1 = old count
		NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndex:index];
		
		[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:@"dockNames"];
		name = [name mutableCopy];
		[names addObject:name];
		[self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:@"dockNames"];
		
		[indexSet release];
		[name release];
	}
	
	if ([delegate respondsToSelector:_cmd]) {
		[delegate performSelector:_cmd withObject:note];
	}
}

- (void)dockyardDockRenamed:(NSNotification *)note
{
	NSDictionary *info = [note userInfo];
	NSString *oldName = [info objectForKey:@"oldName"];
	
	if ([docks objectForKey:oldName] != nil) {
		NSString *newName = [info objectForKey:@"newName"];
		
		[docks setObject:[docks objectForKey:oldName] forKey:newName];
		[docks removeObjectForKey:oldName];
		
		NSDictionary *dashboardData = [dashboards objectForKey:oldName];
		if (dashboardData != nil) {
			[dashboards setObject:dashboardData forKey:newName];
			[dashboards removeObjectForKey:oldName];
		}
		
		int index = [names indexOfObject:oldName];
		if (index == NSNotFound)
			index = [names indexOfObject:newName]; // Already replaced
		NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndex:index];
		
		[self willChange:NSKeyValueChangeReplacement valuesAtIndexes:indexSet forKey:@"dockNames"];
		newName = [newName mutableCopy];
		[names replaceObjectAtIndex:index withObject:newName];
		[self didChange:NSKeyValueChangeReplacement valuesAtIndexes:indexSet forKey:@"dockNames"];
		
		[indexSet release];
		
		if ([currentDock isEqualToString:oldName]) {
			[self willChangeValueForKey:@"currentDock"];
			[currentDock release];
			currentDock = [newName retain];
			[self didChangeValueForKey:@"currentDock"];
		}
		[newName release];
	}
	
	if ([delegate respondsToSelector:_cmd]) {
		[delegate performSelector:_cmd withObject:note];
	}
}

- (void)dockyardDocksReordered:(NSNotification *)note
{
	NSDictionary *info = [note userInfo];
	NSArray *newNames = [info objectForKey:@"dockNames"];
	newNames = [newNames resultsOfMakeObjectsPerformSelector:@selector(mutableCopy)];
	[self setDockNames:newNames];
	[newNames makeObjectsPerformSelector:@selector(release)];
		
	if ([delegate respondsToSelector:_cmd]) {
		[delegate performSelector:_cmd withObject:note];
	}
}

- (void)dockyardDockDeleted:(NSNotification *)note
{
	NSDictionary *info = [note userInfo];
	NSString *name = [info objectForKey:@"name"];
	
	if ([names indexOfObject:name] != NSNotFound) {
		int index = [names indexOfObject:name];
		NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndex:index];
		
		[self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexSet forKey:@"dockNames"];
		[names removeObjectAtIndex:index];
		[self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexSet forKey:@"dockNames"];
		
		[indexSet release];
		
		[docks removeObjectForKey:name];
		if ([dashboards objectForKey:name] != nil)
			[dashboards removeObjectForKey:name];
	}
	
	if ([delegate respondsToSelector:_cmd]) {
		[delegate performSelector:_cmd withObject:note];
	}
}

- (void)dockyardDocksDeleted:(NSNotification *)note
{
	NSDictionary *info = [note userInfo];
	NSString *name = [info objectForKey:@"firstName"];
	
	if ([names indexOfObject:name] != NSNotFound) {
		NSIndexSet *indexSet = [NSKeyedUnarchiver unarchiveObjectWithData:[info objectForKey:@"indexSet"]];
		NSArray *namesToDelete = [[names pantherObjectsAtIndexes:indexSet] retain];

		[self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexSet forKey:@"dockNames"];
		[names pantherRemoveObjectsAtIndexes:indexSet];
		[self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexSet forKey:@"dockNames"];
				
		[docks removeObjectsForKeys:namesToDelete];
		[dashboards removeObjectsForKeys:namesToDelete];
		[namesToDelete release];
	}
	
	if ([delegate respondsToSelector:_cmd]) {
		[delegate performSelector:_cmd withObject:note];
	}
}
@end
