/*******************************************************************************
 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.
*******************************************************************************/

#import "DockManager.h"

// Taken from NSApplication.h -- needed for pre-Tiger systems
#ifndef NSAppKitVersionNumber10_3
	#define NSAppKitVersionNumber10_3 743
#endif

static BOOL userHasTiger;

/*!
 * Provides data for the dock names table in Dockyard Manager. Also supports a number
 * of Dock-related commands, such as duplication and deletion, most of which allow
 * Undo operations.
 */
@implementation DockManager
+ (void)initialize
{
	userHasTiger = floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_3;
}

- (void)awakeFromNib
{
	if (!hasAwoken) {
		[super awakeFromNib];

		[tableView setDoubleAction:@selector(changeDock:)];
		[tableView setTarget:self];
		
		[tableView sizeToFit];
		
		NSDistributedNotificationCenter *noteCenter = [NSDistributedNotificationCenter defaultCenter];
		[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];
		
		currentDock = [[preferences currentDock] retain];
		
		NSArray *names = [preferences dockNames];
		NSString *nextName;
		NSMenuItem *nextItem;
		int index;
		for (index = [names count] - 1; index >= 0; index--) {
			nextName = [names objectAtIndex:index];
			nextItem = (NSMenuItem *) [dockIconSwitchMenu insertItemWithTitle:nextName action:@selector(changeDockMenuCommand:) keyEquivalent:@"" atIndex:0];
			[nextItem setTarget:self];
			[nextItem setRepresentedObject:nextName];
			if ([nextName isEqualToString:currentDock])
				[nextItem setState:NSOnState];
		}
		
		[self willChangeValueForKey:@"selectedDock"];
		[self didChangeValueForKey:@"selectedDock"];
		
		hasAwoken = YES;
	}
}

- (void)dealloc
{
	[[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
	[currentDock release];
	[editedDockName release];
	[editedDockOriginalName release];
	[super dealloc];
}

/*!
 * Displays the dock creation panel in a model window, loading the proper nib
 * if necessary.
 *
 * @see #confirmCreate:
 * @see #cancelCreate:
 */
- (IBAction)createNewDock:(id)sender
{
	if (createWindow == nil) {
		[NSBundle loadNibNamed:@"NewDockPanel" owner:self];
	}
	
	[NSApp runModalForWindow:createWindow];
}

/*!
 * Switches to the selected dock in the table view, signaling the switch with a
 * beep.
 *
 * @see #changeDockMenuCommand:
 * @see DockyardPreferences#changeDock:
 */
- (IBAction)changeDock:(id)sender
{
	unsigned whichDock = [tableView selectedRow];
	if (whichDock == -1)
		whichDock = [tableView clickedRow];
	if (whichDock == -1)
		return;
	[preferences changeDock:[[preferences dockNames] objectAtIndex:whichDock]];
	NSBeep();
	[tableView reloadData];
}

/*!
 * Switches to the dock represented by <code>sender</code>, which is not
 * necessarily the one selected in the table view. The switch is signified
 * with a beep.
 *
 * @see #changeDock:
 * @see DockyardPreferences#changeDock:
 */
- (IBAction)changeDockMenuCommand:(NSMenuItem *)sender
{
	[preferences changeDock:[sender representedObject]];
	NSBeep();
	[tableView reloadData];
}

/*!
 * Called when the user tries to delete the docks selected in the table.
 * If all of the docks are selected, just beeps. If the current dock is
 * selected, runs a sheet asking which dock to switch to before deletion. 
 * Otherwise, deletes the selected docks without running the sheet.
 *
 * @see #confirmDelete:
 * @see #cancelDelete:
 * @see DockyardPreferences#deleteDocksAtIndexes:
 */
- (IBAction)delete:(id)sender
{
	NSArray *whichDocks = [self selectedObjects];
	if ([whichDocks count] >= [preferences numberOfDocks]) {
		NSBeep();
	
	} else if ([whichDocks containsObject:currentDock]) {
		if (switchSheet == nil) {
			[NSBundle loadNibNamed:@"SwitchAfterDelete" owner:self];
		}
		
		NSMutableArray *remainingDocks = [[preferences dockNames] mutableCopy];
		[remainingDocks removeObjectsInArray:whichDocks];
		
		[dockListButton removeAllItems];
		[dockListButton addItemsWithTitles:remainingDocks];
		[NSApp beginSheet:switchSheet modalForWindow:[NSApp mainWindow] modalDelegate:self didEndSelector:NULL contextInfo:nil];
		
		[remainingDocks release];
		
	} else {
		NSIndexSet *selectedIndexes = [self selectionIndexes];
		NSMutableArray *namesAfterDeletion = [[preferences dockNames] mutableCopy];
		[namesAfterDeletion pantherRemoveObjectsAtIndexes:selectedIndexes];

		NSUndoManager *undoManager = [tableView undoManager];
		[undoManager beginUndoGrouping];
		[[undoManager prepareWithInvocationTarget:self]
			setDockNames:[preferences dockNames] oldNames:namesAfterDeletion newSelection:selectedIndexes oldSelection:[NSIndexSet indexSet]];
		[[undoManager prepareWithInvocationTarget:self]
			addDocksWithNames:whichDocks dockData:[[preferences dataForDocks] objectsForKeys:whichDocks notFoundMarker:[NSNull null]]
				dashboardData:[[preferences dashboardDataForDocks] objectsForKeys:whichDocks notFoundMarker:[NSNull null]]];
		
		[undoManager endUndoGrouping];
		if ([whichDocks count] == 1)
			[undoManager setActionName:NSLocalizedString(@"Delete Dock", @"Undo action title for deleting a Dock")];
		else
			[undoManager setActionName:NSLocalizedString(@"Delete Docks", @"Undo action title for deleting multiple Docks")];
		[namesAfterDeletion release];
		
		[tableView deselectAll:self];
		[preferences deleteDocksAtIndexes:selectedIndexes];
		[tableView reloadData];
	}
}

/*!
 * Duplicates the selected docks. The new dock names have the form @"<i>Name</i> Copy"
 * in English. Other languages may substitute another pattern.
 *
 * @see DockyardPreferences#createDocksWithNames:data:dashboardData:
 */
- (IBAction)duplicateDock:(id)sender
{
	NSArray *whichDocks = [self selectedObjects];
	int count = [whichDocks count];
	if (count == 0)
		return;
	
	NSIndexSet *selectedIndexes = [self selectionIndexes];
	NSIndexSet *resultIndexes = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange([tableView numberOfRows], count)];
	
	NSUndoManager *undoManager = [tableView undoManager];
	[[undoManager prepareWithInvocationTarget:self]
		removeDocksAtIndexes:resultIndexes select:selectedIndexes];
	
	if (count == 1)
		[undoManager setActionName:NSLocalizedString(@"Duplicate Dock", @"Undo action title for duplicating a Dock")];
	else
		[undoManager setActionName:NSLocalizedString(@"Duplicate Docks", @"Undo action title for duplicating multiple Docks")];
		
	NSString *template = NSLocalizedString(@"%@ Copy", @"Format of the name given to duplicated Docks.");
	NSMutableArray *newNames = [NSMutableArray new];
	NSEnumerator *nameEnum = [whichDocks objectEnumerator];
	NSString *nextName, *copyName;
	while (nextName = [nameEnum nextObject]) {
		copyName = [[NSString alloc] initWithFormat:template, nextName];
		[newNames addObject:copyName];
		[copyName release];
	}
	
	[preferences createDocksWithNames:newNames
		data:[[preferences dataForDocks] objectsForKeys:whichDocks notFoundMarker:[NSNull null]]
		dashboardData:[[preferences dashboardDataForDocks] objectsForKeys:whichDocks notFoundMarker:[NSNull null]]];
	
	[newNames release];
	
	[tableView selectRowIndexes:resultIndexes byExtendingSelection:NO];
	[resultIndexes release];
}

/*!
 * Tells the table view to edit the selected row. This method merely begins editing
 * a dock name. Any method that programmatically begins editing a dock name should
 * call this method, instead of going through the NSTableView containing the docks.
 *
 * @see #tableView:shouldEditTableColumn:row:
 */
- (IBAction)renameDock:(id)sender
{
	int row = [tableView selectedRow];
	editedDockName = [[[preferences dockNames] objectAtIndex:row] retain];
	editedDockOriginalName = [editedDockName copy];
	[tableView editColumn:0 row:row withEvent:nil select:YES];
}

#pragma mark -
/*!
 * Ends the sheet displayed when the user tries to delete the current dock.
 * Cancels the deletion.
 *
 * @see #delete:
 * @see #confirmDelete:
 */
- (IBAction)cancelDelete:(id)sender
{
	[NSApp endSheet:switchSheet];
	[switchSheet orderOut:sender];
}

/*!
 * Ends the sheet displayed when the user tries to delete the current dock.
 * Switches to the user's choice of dock, then deletes all of the docks
 * selected in the table view.
 *
 * @see #delete:
 * @see #cancelDelete:
 */
- (IBAction)confirmDelete:(id)sender
{
	[NSApp endSheet:switchSheet];
	NSString *dockToSwitchTo = [[dockListButton titleOfSelectedItem] retain];
	[switchSheet orderOut:sender];
	
	NSBeep();
	[preferences changeDock:dockToSwitchTo];
	[dockToSwitchTo release];
	
	[self delete:self];
}

/*!
 * Closes the panel used to create new docks. Does not create a dock.
 *
 * @see #createNewDock:
 * @see #confirmCreate:
 */
- (IBAction)cancelCreate:(id)sender
{
	[NSApp stopModal];
	[createWindow orderOut:sender];
}

/*!
 * Closes the panel used to create new docks. This function is mostly a call to
 * DockyardPreferences, but includes a switch statement to decide where to find
 * the dock's plist file. The choices are Apple's default dock, an empty dock,
 * the selected dock, and the current dock. The same choices are given for the
 * system. Also, if the name chosen is invalid, the window will not close, but will
 * dock's Dashboard contents on a 10.4 or later instead erase the name and wait for
 * a different one.
 *
 * @see #createNewDock:
 * @see #cancelCreate:
 * @see DockyardPreferences#createDockWithName:fromFile:dashboardFile:
 */
- (IBAction)confirmCreate:(id)sender
{
	static NSString *appleDockPath = nil;
	static NSString *emptyDockPath = nil;
	static NSString *currentDockPath = nil;
	static NSString *selectedDockPath = nil;

	static NSString *appleDashboardPath = nil;
	static NSString *emptyDashboardPath = nil;
	static NSString *currentDashboardPath = nil;
	static NSString *selectedDashboardPath = nil;

	NSString *name, *dockPath, *dashboardPath;
	BOOL loadNewDock = NO;
	
	name = [dockNameField stringValue];
	[name retain];
	loadNewDock = ([shouldSwitchButton state] == NSOnState);
		
	switch ([dockItemsMatrix selectedRow]) {
	case 1: // Apple default dock
		if (appleDockPath == nil)
			appleDockPath = [[[NSBundle bundleWithPath:@"/System/Library/CoreServices/Dock.app"] pathForResource:@"default" ofType:@"plist"] retain];
		dockPath = appleDockPath;
		break;
	case 2: // Empty dock
		if (emptyDockPath == nil)
			emptyDockPath = [[[NSBundle mainBundle] pathForResource:@"emptyDock" ofType:@"plist"] retain];
		dockPath = emptyDockPath;
		break;
	case 3: // Same as selected
		if (selectedDockPath == nil) {
			NSString *tempDir = NSTemporaryDirectory();
			if (tempDir == nil)
				tempDir = @"/tmp";
			selectedDockPath = [[tempDir stringByAppendingPathComponent:@"com.belkadan.Dockyard.Dock.temp.plist"] retain];
		}
		[[preferences dataForDockWithName:[[self selectedObjects] objectAtIndex:0]] writeToFile:selectedDockPath atomically:YES];
		dockPath = selectedDockPath;
		break;
	case 0: // Same as current
	default: // Default to same as current - should never happen
		if (currentDockPath == nil)
			currentDockPath = [[@"~/Library/Preferences/com.apple.dock.plist" stringByStandardizingPath] retain];
		dockPath = currentDockPath;
	}
	
	if (userHasTiger) {
		switch ([dashboardItemsMatrix selectedRow]) {
		case 1: // Apple default dashboard -- location unknown. For now use the in-bundle copy.
			if (appleDashboardPath == nil)
//				appleDashboardPath = [[NSBundle bundleWithPath:@"/System/Library/CoreServices/Dock.app"] pathForResource:@"default" ofType:@"plist"];
				appleDashboardPath = [[[NSBundle mainBundle] pathForResource:@"defaultDashboard" ofType:@"plist"] retain];
			dashboardPath = appleDashboardPath;
			break;
		case 2: // Empty dashboard
			if (emptyDashboardPath == nil)
				emptyDashboardPath = [[[NSBundle mainBundle] pathForResource:@"emptyDashboard" ofType:@"plist"] retain];
			dashboardPath = emptyDashboardPath;
			break;
		case 3: // Same as selected
			if (selectedDashboardPath == nil) {
				NSString *tempDir = NSTemporaryDirectory();
				if (tempDir == nil)
					tempDir = @"/tmp";
				selectedDashboardPath = [[tempDir stringByAppendingPathComponent:@"com.belkadan.Dockyard.Dashboard.temp.plist"] retain];
			}
			[[preferences dashboardDataForDockWithName:[[self selectedObjects] objectAtIndex:0]] writeToFile:selectedDashboardPath atomically:YES];
			dashboardPath = selectedDashboardPath;
			break;
		case 0: // Same as current
		default: // Default to same as current - should never happen
			if (currentDashboardPath == nil)
				currentDashboardPath = [[@"~/Library/Preferences/com.apple.dashboard.plist" stringByStandardizingPath] retain];
			dashboardPath = currentDashboardPath;
		}
		
	} else {
		dashboardPath = nil;
	}
	
	if ([preferences createDockWithName:name fromFile:dockPath dashboardFile:dashboardPath]) {
		if (loadNewDock)
			[preferences changeDock:name];
			
		[NSApp stopModal];
		[createWindow orderOut:sender];
		
		NSIndexSet *oldSelectionIndexes = [[tableView selectedRowIndexes] retain];
		NSIndexSet *newSelectionIndexes = [[NSIndexSet alloc] initWithIndex:([tableView numberOfRows] - 1)];
		
		[tableView selectRowIndexes:newSelectionIndexes byExtendingSelection:NO];
		
		NSUndoManager *undoManager = [tableView undoManager];
		[[undoManager prepareWithInvocationTarget:self]
			removeDocksAtIndexes:newSelectionIndexes select:oldSelectionIndexes];
		[undoManager setActionName:NSLocalizedString(@"Create Dock", @"Undo action title for creating a Dock")];
		
		[newSelectionIndexes release];
		[oldSelectionIndexes release];
		
	} else {
		NSBeep();
		[dockNameField setStringValue:@""];
		[((NSTextFieldCell *)[dockNameField cell]) setPlaceholderString:NSLocalizedString(@"Invalid Name!", @"Error message given when a name is taken")];
	}
	
	[name release];
}

/*!
 * If a single dock is selected, forwards the action to the next view in the key view
 * loop. This is assumed to be the table with the contents of the selected dock, but
 * really could be anything.
 */
- (IBAction)paste:(id)sender
{
	if ([self numberOfSelectedDocks] == 1)
		[[tableView nextKeyView] tryToPerform:_cmd with:sender];
}

/*!
 * Returns the number of docks currently selected.
 *
 * @see #selectedDock:
 */
- (unsigned)numberOfSelectedDocks
{
	return [tableView numberOfSelectedRows];
}

/*!
 * Returns the name of the currently selected dock. If there are no docks selected, or
 * if there are multiple docks selected, returns <code>nil</code>.
 *
 * @see #numberOfSelectedDocks
 */
- (NSString *)selectedDock
{
	NSArray *selectedDocks = [self selectedObjects];
	if ([selectedDocks count] == 1)
		return [selectedDocks objectAtIndex:0];
	else
		return nil;
}

#pragma mark -

/*!
 * 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.
 * Docks without dashboard data should 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".
 * Selects the newly created docks. This method should only be used to undo a deletion.
 *
 * @see DockyardPreferences#createDocksWithNames:data:dashboardData:
 */
- (void)addDocksWithNames:(NSArray *)names dockData:(NSArray *)dockData dashboardData:(NSArray *)dashboardData
{
	NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange([tableView numberOfRows], [names count])];

	NSUndoManager *undoManager = [tableView undoManager];
	[[undoManager prepareWithInvocationTarget:self]
		removeDocksAtIndexes:indexSet select:nil];
			
	[preferences createDocksWithNames:names data:dockData dashboardData:dashboardData];
	[self setSelectionIndexes:indexSet];
	
	[indexSet release];
}

/*!
 * Removes the dock at <code>indexes</code>, then selects the docks at <code>selectionIndexes</code>.
 * This method should only be used to undo a creation.
 *
 * @see DockyardPreferences#deleteDocksAtIndexes:
 */
- (void)removeDocksAtIndexes:(NSIndexSet *)indexes select:(NSIndexSet *)selectionIndexes
{
	NSArray *names = [[preferences dockNames] pantherObjectsAtIndexes:indexes];

	NSUndoManager *undoManager = [tableView undoManager];
	[[undoManager prepareWithInvocationTarget:self]
		addDocksWithNames:names dockData:[[preferences dataForDocks] objectsForKeys:names notFoundMarker:[NSNull null]]
			dashboardData:[[preferences dashboardDataForDocks] objectsForKeys:names notFoundMarker:[NSNull null]]];
	
	if (selectionIndexes == nil)
		[tableView deselectAll:self];
	else
		[tableView selectRowIndexes:selectionIndexes byExtendingSelection:NO];
	[preferences deleteDocksAtIndexes:indexes];
}

/*!
 * Renames the dock named <code>oldName</code> to <code>newName</code>. No two docks can have
 * the same name. Also selects the docks at <code>rows</code>. This method should only be
 * used to undo a renaming.
 *
 * @see DockyardPreferences#renameDock:to:
 */
- (void)rename:(NSString *)oldName to:(NSString *)newName selectedRows:(NSIndexSet *)rows
{
	NSUndoManager *undoManager = [tableView undoManager];
	[[undoManager prepareWithInvocationTarget:self] rename:newName to:oldName selectedRows:rows];
	
	[preferences renameDock:oldName to:newName];

	[tableView selectRowIndexes:rows byExtendingSelection:NO];
}

/*!
 * Sets the list of dock names to <code>newNames</code> and selects <code>newSelection</code>.
 * <code>oldSelection</code> and <code>oldNames</code> are passed in for undo purposes. This
 * method should only be used to undo a reorder or a deletion.
 *
 * @see DockyardPreferences#setDockNames:
 */	
- (void)setDockNames:(NSArray *)newNames oldNames:(NSArray *)oldNames newSelection:(NSIndexSet *)newSelection oldSelection:(NSIndexSet *)oldSelection
{
	NSUndoManager *undoManager = [tableView undoManager];
	[[undoManager prepareWithInvocationTarget:self] setDockNames:oldNames oldNames:newNames newSelection:oldSelection oldSelection:newSelection];
	
	[preferences setDockNames:newNames];
	[preferences rearrange];
	
	[tableView selectRowIndexes:newSelection byExtendingSelection:NO];
}

#pragma mark -

- (void)dockyardDockChanged:(NSNotification *)note
{
	NSDictionary *info = [note userInfo];
	currentDock = [[info objectForKey:@"newDock"] retain];
	NSString *oldDock = [info objectForKey:@"oldDock"];
	NSMenuItem *oldDockItem, *newDockItem;
				
	oldDockItem = (NSMenuItem *) [dockIconSwitchMenu itemWithTitle:oldDock];
	if (oldDockItem != nil) [oldDockItem setState:NSOffState];
		
	newDockItem = (NSMenuItem *) [dockIconSwitchMenu itemWithTitle:currentDock];
	[newDockItem setState:NSOnState];
	
	[tableView reloadData];
}

- (void)dockyardDockCreated:(NSNotification *)note
{
	NSString *name = [[note userInfo] objectForKey:@"name"];

	NSMenuItem *newItem = (NSMenuItem *) [dockIconSwitchMenu addItemWithTitle:name action:@selector(changeDockMenuCommand:) keyEquivalent:@""];
	[newItem setTarget:self];
	[newItem setRepresentedObject:name];
}

- (void)dockyardDockRenamed:(NSNotification *)note
{
	NSDictionary *info = [note userInfo];
	NSString *oldName = [info objectForKey:@"oldName"];
	NSString *newName = [info objectForKey:@"newName"];
	
	NSMenuItem *dockItem = [dockIconSwitchMenu itemWithTitle:oldName];
	
	[dockItem setTitle:newName];
	[dockItem setRepresentedObject:newName];
	
	if ([currentDock isEqualToString:oldName]) {
		[currentDock release];
		currentDock = [newName retain];
	}
}

- (void)dockyardDocksReordered:(NSNotification *)note
{
	NSDictionary *info = [note userInfo];
	NSArray *names = [info objectForKey:@"dockNames"];
	
	int i, count;
	NSMenuItem *item;
	
	count = [names count];
	
	for (i = 0; i < count; i++) {
		item = [[dockIconSwitchMenu itemWithTitle:[names objectAtIndex:i]] retain];
		
		[dockIconSwitchMenu removeItem:item];
		
		if (i == count - 1) [dockIconSwitchMenu addItem:item];
		else [dockIconSwitchMenu insertItem:item atIndex:i];
		
		[item release];
	}
}

- (void)dockyardDockDeleted:(NSNotification *)note
{
	NSDictionary *info = [note userInfo];
	NSString *name = [info objectForKey:@"name"];
	
	[dockIconSwitchMenu removeItem:[dockIconSwitchMenu itemWithTitle:name]];
}

- (void)dockyardDocksDeleted:(NSNotification *)note
{
	NSDictionary *info = [note userInfo];
	NSIndexSet *indexSet = [NSKeyedUnarchiver unarchiveObjectWithData:[info objectForKey:@"indexSet"]];
	NSIndexSetEnumerator *indexEnum = [[NSIndexSetEnumerator alloc] initWithIndexSet:indexSet];
    unsigned index, count = 0;
    while ([indexEnum nextIndex:&index])
    {
		[dockIconSwitchMenu removeItemAtIndex:(index - count)];
		count++;
    }
	[indexEnum release];
}

#pragma mark -

/*!
 * Returns the number of docks.
 */
- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
	return [[self arrangedObjects] count];
}

/*!
 * Returns the dock name at <code>rowIndex</code>.
 */
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex

{
	return [[self arrangedObjects] objectAtIndex:rowIndex];
}

/*!
 * Sets the dock name at <code>rowIndex</code> to <code>newName</code>. If there
 * is already a dock with the same name, the computer will beep and no change
 * will occur.
 *
 * @see #renameDock:
 * @see #tableView:shouldEditTableColumn:row:
 */
- (void)tableView:(NSTableView *)editTable setObjectValue:(NSString *)newName forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
{
	if (editedDockOriginalName != nil) {
		if ([newName isEqualToString:editedDockOriginalName])
			return;
		else if (![preferences renameDock:editedDockOriginalName to:newName]) {
			NSBeep();
			[editedDockName setString:editedDockOriginalName];
		}
		[editedDockName release];
		[editedDockOriginalName release];
		editedDockOriginalName = editedDockName = nil;
		
		NSUndoManager *undoManager = [tableView undoManager];
		[[undoManager prepareWithInvocationTarget:self]
			rename:newName to:editedDockOriginalName selectedRows:[NSIndexSet indexSetWithIndex:rowIndex]];
		[undoManager setActionName:NSLocalizedString(@"Rename Dock", @"Undo action title for renaming a Dock")];
	}
}

- (BOOL)tableView:(NSTableView*)tv
	   acceptDrop:(id <NSDraggingInfo>)info
			  row:(int)row
	dropOperation:(NSTableViewDropOperation)op
{
	NSArray *oldNames = [[preferences dockNames] retain];
	NSIndexSet *oldSelection = [[self selectionIndexes] copy];
	
	BOOL success = [super tableView:tv acceptDrop:info row:row dropOperation:op];
	if (success) {
		[preferences rearrange];
		
		NSUndoManager *undoManager = [tableView undoManager];
		[[undoManager prepareWithInvocationTarget:self] setDockNames:oldNames oldNames:[preferences dockNames] newSelection:oldSelection oldSelection:[self selectionIndexes]];
		[undoManager setActionName:NSLocalizedString(@"Reorder Docks", @"Undo action title for reordering Docks")];
	}
	
	[oldNames release];
	[oldSelection release];
	
	return success;
}

/*!
 * If the cell being displayed represents the current dock, displays a 16 by 16 icon
 * of the Dock next to it. Otherwise, makes sure the cell has no icon before displaying
 * it.
 */
- (void)tableView:(NSTableView *)tableView willDisplayCell:(NSCell *)cell forTableColumn:(NSTableColumn *)column row:(int)row
{
	static NSImage *dockIcon = nil;
	static NSImage *blankIcon = nil;
	static NSSize iconSize = {16, 16};
			
	if ([currentDock isEqual:[cell stringValue]]) {
		if (dockIcon == nil) {
			dockIcon = [[[NSWorkspace sharedWorkspace] iconForFile:@"/System/Library/CoreServices/Dock.app"] retain];
			[dockIcon setScalesWhenResized:YES];
			[dockIcon setSize:iconSize];
		}
		[cell setImage:dockIcon];
		
	} else {
		if (blankIcon == nil) {
			blankIcon = [[NSImage alloc] initWithSize:iconSize];
		}
		[cell setImage:blankIcon];
	}
}

/*!
 * Called when the user double-clicks on a table cell. If the click occured within the
 * cell's image, returns <code>NO</code, which will send the table's double-click action.
 * If the click occured within the cell's text, returns <code>YES</code>, which begins
 * a renaming action.
 *
 * @see #tableView:setObjectValue:forTableColumn:row:
 */
- (BOOL)tableView:(NSTableView *)editTable shouldEditTableColumn:(NSTableColumn *)tableColumn row:(int)rowIndex
{
	NSPoint mouseLocation = [editTable convertPoint:[[editTable window] convertScreenToBase:[NSEvent mouseLocation]] fromView:nil];
	NSRect cellFrame = [editTable frameOfCellAtColumn:0 row:rowIndex];
	NSRect textFrame = [[tableColumn dataCellForRow:rowIndex] titleRectForBounds:cellFrame];
	if (NSMouseInRect(mouseLocation, textFrame, [editTable isFlipped])) {
		editedDockName = [[[preferences dockNames] objectAtIndex:rowIndex] retain];
		editedDockOriginalName = [editedDockName copy];
		return YES;
	} else {
		return NO;
	}
}

/*!
 * Warns any observers that the number of selected docks and the name of the
 * selected dock are about to change.
 */
- (void)tableViewSelectionWillChange:(NSNotification *)note
{
	[self willChangeValueForKey:@"numberOfSelectedDocks"];
	[self willChangeValueForKey:@"selectedDock"];
}

/*!
 * Informs observers that the number of selected docks and the name of the selected
 * dock just changed. Also sets the title of the "Switch To" menu item to the
 * localized form of <code>@"Switch to <i>Dock Name</i>"</code>.
 */
- (void)tableViewSelectionDidChange:(NSNotification *)note
{
	[self didChangeValueForKey:@"numberOfSelectedDocks"];
	[self didChangeValueForKey:@"selectedDock"];
	NSString *selectedDock = [self selectedDock];
	if (selectedDock != nil) {
		[switchMenuItem setTitle:
			[NSString stringWithFormat:NSLocalizedString(@"Switch To %@", @"Title of menu item for changing docks when a single dock is selected"),
				selectedDock]];
	} else {
		[switchMenuItem setTitle:NSLocalizedString(@"Switch To", @"Title of menu item for changing docks when multiple docks are selected")];
	}
}

/*!
 * Only allows a <code>paste:</code> action if one dock is selected and it
 * is not the current one. This action is passed along to the table view's
 * next key view anyway, which is assumed to be the table containing dock
 * contents.
 *
 * @see DockContentsDataSource#paste:
 */
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
	SEL action = [menuItem action];
	if (action == @selector(paste:)) {
		NSString *selectedDock = [self selectedDock];
		return !(selectedDock == nil || [currentDock isEqual:selectedDock]);
	} else
		return YES;
}

@end
