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

//
//  DockContentsDataSource.m
//  Dockyard
//

#import "DockContentsDataSource.h"

static NSString * const DockyardDockAppsPboardType	= @"com.belkadan.Dockyard.dockAppsPboardType";
static NSString * const DockyardDockItemsPboardType	= @"com.belkadan.Dockyard.dockItemsPboardType";
static NSString * const DockyardWidgetsPboardType	= @"com.belkadan.Dockyard.widgetsPboardType";

static NSString * const URLNamePboardType	= @"CorePasteboardFlavorType 0x75726C6E";

/*!
 * Provides data for the dock contents table in Dockyard Manager. Also provides support for
 * copy and paste, drag-and-drop, opening and revealing items, and undoing item operations.
 */
@implementation DockContentsDataSource
- (void)awakeFromNib
{
	dockItemsRow = INT_MAX;
	dashboardItemsRow = INT_MAX;
	
	NSNumber *shouldShowAsNumber = [preferences defaultsValueForKey:@"Manager:shouldShowCannotDragSheet"];
	if (shouldShowAsNumber == nil)
		shouldShowCannotDragSheet = YES;
	else
		shouldShowCannotDragSheet = [shouldShowAsNumber boolValue];
		
	shouldShowAsNumber = [preferences defaultsValueForKey:@"Manager:shouldShowCannotModifyDashboardSheet"];
	if (shouldShowAsNumber == nil)
		shouldShowCannotModifyDashboardSheet = YES;
	else
		shouldShowCannotModifyDashboardSheet = [shouldShowAsNumber boolValue];

	draggedIndexes	= nil;
	selectedDock	= nil;
	selectedData	= nil;
	selectedRow		= -1;
	
	dataForDocks = [NSMutableDictionary new];
	[self bind:@"selectedDock" toObject:dockNamesController withKeyPath:@"selectedDock" options:nil];
	
	[fileInfoView bind:@"objectValue"	toObject:self withKeyPath:@"selectedItem"		options:nil];
	[fileInfoView bind:@"image"			toObject:self withKeyPath:@"selectedItem.icon"	options:nil];
	[fileInfoView bind:@"toolTip"		toObject:self withKeyPath:@"selectedItem.path"	options:nil];
	
	[tableView registerForDraggedTypes:[NSArray arrayWithObjects:DockyardDockAppsPboardType, DockyardDockItemsPboardType, DockyardWidgetsPboardType, NSFilenamesPboardType, NSURLPboardType, nil]];
	[tableView setTarget:self];
	[tableView setDoubleAction:@selector(tableOpen:)];
}

- (void)dealloc
{
	[draggedIndexes release];
	[indexesToSelect release];
	[dataForDocks release];
	[selectedDock release];
	[super dealloc];
}

/*!
 * Returns the cached info of the currently selected item. If more than one item
 * is selected, or if no items are selected, returns <code>nil</code>.
 */
- (DockItemInfo *)selectedItem
{
	if (selectedRow == -1)
		return nil;
	else
		return [selectedData objectAtIndex:selectedRow];
}

/*!
 * Returns the name of the currently selected dock. This is provided mainly for KVC completion
 * and should not be used in code
 *
 * @see #setSelectedDock:
 * @see DockManager#selectedDock
 */
- (NSString *)selectedDock
{
	return selectedDock;
}

/*!
 * Sets the selected dock to <code>newDock</code>. This is used in Cocoa Bindings, and should not
 * be called directly. This will update the table view to reflect the contents of the newly
 * selected dock. If more than one dock is selected, or if no dock is selected, the table is blank.
 *
 * @see #selectedDock
 */
- (void)setSelectedDock:(NSString *)newDock
{
	if (newDock != selectedDock) {
		[selectedDock release];
		
		if (selectedDock == NSMultipleValuesMarker || selectedDock == NSNotApplicableMarker || selectedDock == NSNoSelectionMarker)
			selectedDock = nil;
		else
			selectedDock = [newDock retain];
		
		[tableView deselectAll:self]; // Have to update the selectedItem key somehow
		if (indexesToSelect != nil) {
			[tableView selectRowIndexes:indexesToSelect byExtendingSelection:NO];
			[indexesToSelect release];
			indexesToSelect = nil;
		}
		if (![dockNamesController isEditing])
			[tableView reloadData];
//		[tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0];
	}
}

/*!
 * Returns the number of rows in the table view at this time. If no dock is selected, there are
 * zero rows. If there is a dock selected, the number of rows is equal to the number of items in
 * the dock plus extra rows for headers (e.g., "Dock Applications"). If the user has Dashboard
 * switching enabled, the widgets in this dock are also shown.
 *
 * @see #tableView:objectForTableColumn:row:
 */
- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
	if (selectedDock == nil)
		return 0;
	else {
		selectedData = [dataForDocks objectForKey:selectedDock];
		if (selectedData == nil) {
			selectedData = [self createDataForDock:selectedDock];
			[dataForDocks setObject:selectedData forKey:selectedDock];
			[selectedData release];
		}
		dockItemsRow = [selectedData indexOfObject:NSLocalizedString(@"Dock Items", @"Title of row heading the Dock items (not apps)")];
		if (dockItemsRow == NSNotFound)
			dockItemsRow = INT_MAX;
		dashboardItemsRow = [selectedData indexOfObject:NSLocalizedString(@"Dashboard Widgets", @"Title of row heading the Dashboard items")];
		if (dashboardItemsRow == NSNotFound)
			dashboardItemsRow = INT_MAX;
		return [selectedData count];
	}
}

/*!
 * Returns the object at the specified <code>row</code>. If there is no dock currently selected,
 * returns <code>nil</code>.
 *
 * @see #numberOfRowsInTableView:
 */
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)row
{
	if (selectedDock != nil) {
		return [selectedData objectAtIndex:row];
		
	} else return nil;
}

/*!
 * If the cell at <code>row</code> is a dock item (not a header), sets its icon and enables it.
 * Otherwise, clears the icon and disables the cell.
 */
- (void)tableView:(NSTableView *)aTableView willDisplayCell:(NSCell *)cell forTableColumn:(NSTableColumn *)aTableColumn row:(int)row
{
	static NSSize iconSize = {16, 16};
	id cellData = [cell objectValue];
	if ([cellData isKindOfClass:[DockItemInfo class]]) {
		NSImage *image = [cellData icon];
		[image setSize:iconSize];
		[cell setImage:image];
		[cell setEnabled:YES];
		[cell setEditable:NO];
		
	} else {
		[cell setImage:nil];
		[cell setEnabled:NO];
		[cell setEditable:NO];
	}
}

/*!
 * Returns <code>YES</code> if the item at <code>row</code> is a dock item, <code>NO</code>
 * if it is a header cell (e.g., "Dock Applications").
 */
- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(int)row
{
	return !(row == 0 || row == dockItemsRow || row == dashboardItemsRow);
}

/*!
 * Updates the value of <code>selectedItem</code> to reflect the currently selected row.
 *
 * @see #selectedItem
 */
- (void)tableViewSelectionDidChange:(NSNotification *)note
{
	[self willChangeValueForKey:@"selectedItem"];
	if ([tableView numberOfSelectedRows] == 1)
		selectedRow = [tableView selectedRow];
	else
		selectedRow = -1;
	[self didChangeValueForKey:@"selectedItem"];
}
#pragma mark -
/*!
 * Starts a drag of the rows at <code>rowIndexes</code>. If the dragged rows are all of one
 * type (applications, items, or widgets), they can be reordered within this dock. The rows
 * can also be dragged anywhere that accepts a URL (<code>NSURLPboardType</code>; only the
 * first row will be written), or a list of files (<code>NSFilenamesPboardType</code>).
 */
- (BOOL)tableView:(NSTableView *)dragTable writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard*)pboard
{
	int firstIndex	= [rowIndexes firstIndex];
	int lastIndex	= [rowIndexes lastIndex];
	NSArray *pboardTypes;
	if (firstIndex > 0 && lastIndex < dockItemsRow && lastIndex < dashboardItemsRow) {
		draggedPboardType = DockyardDockAppsPboardType;
		draggedItemSet = DockyardDockAppSet;
		
	} else if (firstIndex > dockItemsRow && lastIndex < dashboardItemsRow) {
		draggedPboardType = DockyardDockItemsPboardType;
		draggedItemSet = DockyardDockItemSet;
		
	} else if (firstIndex > dashboardItemsRow) {
		draggedPboardType = DockyardWidgetsPboardType;
		draggedItemSet = DockyardWidgetSet;
		
	} else {
		draggedPboardType = nil;
		draggedItemSet = DockyardUnknownSet;
	}
	pboardTypes = [[NSArray alloc] initWithObjects:NSURLPboardType, URLNamePboardType, NSFilenamesPboardType, draggedPboardType, nil];
	[pboard declareTypes:pboardTypes owner:self];
	[pboardTypes release];
	
	draggedIndexes = [rowIndexes retain];
	return YES;
}

/*!
 * Provided for compatibility with 10.3 systems. Forwards the message to
 * <code>tableView:writeRowsWithIndexes:toPasteboard:</code>.
 *
 * @see #tableView:writeRowsWithIndexes:toPasteboard:
 * @deprecated 10.4
 */
- (BOOL)tableView:(NSTableView *)tv writeRows:(NSArray *)rows toPasteboard:(NSPasteboard*)pboard
{
/*    NSMutableIndexSet *indexSet = [NSMutableIndexSet new];
    NSEnumerator *rowEnumerator = [rows objectEnumerator];
    NSNumber *idx;
    while (idx = [rowEnumerator nextObject])
    {
		[indexSet addIndex:[idx intValue]];
    } */
	
	NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInArray:rows];
	
	BOOL result = [self tableView:tv writeRowsWithIndexes:indexSet toPasteboard:pboard];
	[indexSet release];
	return result;
}

/*!
 * Decides whether or not to allow a drop. If no dock is selected, no drop is allowed.
 * If it's an external drag, makes sure that it is files or a URL that is being dragged.
 * If it's an internal drag, makes sure that all of the items are the same type.
 */
- (NSDragOperation)tableView:(NSTableView *)dragTable validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
{
	BOOL shouldDrop = NO;
	NSTableViewDropOperation dropOp = NSTableViewDropAbove;
	static NSArray *externalDropTypes = nil;
	
	if (externalDropTypes == nil)
		externalDropTypes = [[NSArray alloc] initWithObjects:NSFilenamesPboardType, NSURLPboardType, nil];
	
	if (selectedDock == nil)
		return NSDragOperationNone;

	if ([info draggingSource] != dragTable) {
		dropOp = NSTableViewDropOn;
		shouldDrop = ([[info draggingPasteboard] availableTypeFromArray:externalDropTypes]) != nil;
		row = -1;

	} else if (row == 0) {
		shouldDrop = (draggedItemSet == DockyardDockAppSet);
		dropOp = NSTableViewDropOn;
	
	} else if (row > 0 && row < dockItemsRow && row < dashboardItemsRow) {
		shouldDrop = (draggedItemSet == DockyardDockAppSet);
	
	} else if (row == dockItemsRow) {
		shouldDrop = (draggedItemSet == DockyardDockAppSet);
		if (!shouldDrop) {
			shouldDrop = (draggedItemSet == DockyardDockItemSet);
			dropOp = NSTableViewDropOn;
		}
	
	} else if (row > dockItemsRow && row < dashboardItemsRow) {
		shouldDrop = (draggedItemSet == DockyardDockItemSet);
	
	} else if (row == dashboardItemsRow) {
		if (dockItemsRow == INT_MAX)
			shouldDrop = (draggedItemSet == DockyardDockAppSet);
		else
			shouldDrop = (draggedItemSet == DockyardDockItemSet);
			
		if (!shouldDrop) {
			shouldDrop = (draggedItemSet == DockyardWidgetSet);
			dropOp = NSTableViewDropOn;
		}
				
	} else if (row > dashboardItemsRow) {
		shouldDrop = (draggedItemSet == DockyardWidgetSet);
	
	}
	
	[dragTable setDropRow:row dropOperation:dropOp];
	return (row == -1) ? NSDragOperationLink : (shouldDrop) ? NSDragOperationMove : NSDragOperationNone; // row == -1 when items are added, shouldDrop is true when items are moved
}

/*!
 * Accepts a drop from inside or outside the application. If the dragged item is a URL, adds it to the
 * selected dock's item set. If the items are files, adds them to their respective item sets. If the
 * drag is internal, and all of the items are from the same type, reorders the items.
 *
 * @see #addItemsWithPaths:
 * @see #addItemWithURL:label:
 * @see DockyardPreferences#moveItemsAtIndexes:toIndex:inSet:ofDock:
 */
- (BOOL)tableView:(NSTableView *)dragTable acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
{
	if ([[preferences currentDock] isEqualToString:selectedDock]) {
		NSBeep();
		if (shouldShowCannotDragSheet)
			NSBeginAlertSheet(NSLocalizedString(@"You cannot modify the current dock.", @"Error message when the current dock is modified"), nil,
				NSLocalizedString(@"Don't show again", @"This sheet can get annoying"), nil, [dragTable window], self, @selector(errorSheetEnded:returnCode:defaultsKey:),
				NULL, @"Manager:shouldShowCannotDragSheet", NSLocalizedString(@"Try switching to another dock, making your changes, and switching back.", @"Suggested action, since you can't modify the current dock"));
		return NO; // Can't modify current dock!
	}
	
	if ([info draggingSource] != dragTable) {
		NSPasteboard *pboard = [info draggingPasteboard];
		NSArray *pboardTypes = [pboard types];
		
		if ([pboardTypes containsObject:NSFilenamesPboardType]) {
			NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];
			[self addItemsWithPaths:filenames];	
				
		} else if ([pboardTypes containsObject:NSURLPboardType]) {
			NSURL *url = [NSURL URLFromPasteboard:pboard];
			NSString *label = nil;
			
			if ([pboardTypes containsObject:URLNamePboardType])
				label = [pboard stringForType:URLNamePboardType];
				
			[self addItemWithURL:url label:label];
		}
		
		return YES;
	
	} else {
		if (row == 0) {
			row = ((dockItemsRow == INT_MAX) ? dashboardItemsRow : dockItemsRow);
			
		} else if (row == dockItemsRow && draggedItemSet == DockyardDockItemSet) {
			row = ((dashboardItemsRow == INT_MAX) ? [dragTable numberOfRows] : dashboardItemsRow);
			
		} else if (row == dashboardItemsRow && draggedItemSet == DockyardWidgetSet) {
			row = [dragTable numberOfRows];
		}
		
		int offset;
		NSMutableIndexSet *realIndexes = [draggedIndexes mutableCopy];
		switch (draggedItemSet) {
		case DockyardDockAppSet:
			offset = -1;
			break;
		case DockyardDockItemSet:
			offset = -1 - dockItemsRow;
			break;
		case DockyardWidgetSet:
			offset = -1 - dashboardItemsRow;
			break;
		default:
			return NO;
		}
		
		[self willChangeValueForKey:@"selectedItem"];
		selectedRow = -1;
		[self didChangeValueForKey:@"selectedItem"];
		
		NSArray *oldItems = [[preferences itemsInSet:draggedItemSet ofDock:selectedDock] copy];
			
		[realIndexes shiftIndexesStartingAtIndex:0 by:offset];
		row = [preferences moveItemsAtIndexes:realIndexes toIndex:row + offset inSet:draggedItemSet ofDock:selectedDock];
		[draggedIndexes release];
		
		if (row != -1) {
			NSArray *data = [self createDataForDock:selectedDock];
			[dataForDocks setObject:data forKey:selectedDock];
			[data release];
			[dragTable reloadData];
			
			row -= offset;
			unsigned count = [realIndexes count];
			NSIndexSet *dropIndexes = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(row, count)];
			[dragTable selectRowIndexes:dropIndexes byExtendingSelection:NO];
			
			NSUndoManager *undoManager = [dragTable undoManager];
			[[undoManager prepareWithInvocationTarget:self]
				setItems:oldItems inSet:draggedItemSet ofDock:selectedDock newSelection:draggedIndexes prevSelection:dropIndexes];
			if (count == 1)
				[undoManager setActionName:@"Move Item"];
			else
				[undoManager setActionName:@"Move Items"];
				
			[dropIndexes release];
		}

		draggedItemSet = DockyardUnknownSet;
		draggedIndexes = nil;
		draggedPboardType = nil;
		
		[oldItems release];
		[realIndexes release];
	}
	
	return YES;
}

/*!
 * Allows the table view to drag copies or links of its items outside of the application.
 */
- (NSDragOperation)tableView:(NSTableView *)tableView draggingSourceOperationMaskForLocal:(BOOL)isLocal
{
	if (isLocal)
		return NSDragOperationMove | NSDragOperationDelete;
	else
		return NSDragOperationCopy | NSDragOperationGeneric | NSDragOperationLink;
}

/*!
 * Provides filename or URL data to <code>pboard</code> when requested.
 */
- (void)pasteboard:(NSPasteboard *)pboard provideDataForType:(NSString *)type
{
	if (draggedIndexes != nil) {
		if ([NSFilenamesPboardType isEqualToString:type])
			[pboard setPropertyList:[[selectedData pantherObjectsAtIndexes:draggedIndexes] valueForKey:@"path"] forType:NSFilenamesPboardType];
		else if ([NSURLPboardType isEqualToString:type])
			[[[selectedData objectAtIndex:[draggedIndexes firstIndex]] pathURL] writeToPasteboard:pboard];
		else if ([URLNamePboardType isEqualToString:type])
			[pboard setString:[[selectedData objectAtIndex:[draggedIndexes firstIndex]] label] forType:URLNamePboardType];
	}
	
}

/*!
 * Alerts the receiver that a warning sheet has been dismissed. If <code>returnCode</code> is
 * <code>NSAlertAlternateReturn</code>, stores <code>NO</code> for <code>defaultsKey</code> in
 * <code>DockyardPreferences</code>. In the future, <code>defaultsKey</code> should be checked
 * to see if this dialog should be shown again. This method should never be invoked directly.
 */
- (void)errorSheetEnded:(NSWindow *)sheet returnCode:(int)returnCode defaultsKey:(NSString *)defaultsKey
{
	if (returnCode == NSAlertAlternateReturn) {
		shouldShowCannotDragSheet = NO;
		[preferences setDefaultsValue:[NSNumber numberWithBool:NO] forKey:defaultsKey];
	}
}

#pragma mark -

/*!
 * Validates all item-related menu items by selector. This allows localization. Items that
 * access dock data require that a dock is selected, as well as one or more items. Items
 * that change the dock require that the selected dock is not the current dock.
 */
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
	SEL action = [menuItem action];
	if (action == @selector(copy:) || action == @selector(copyPath:) || action == @selector(open:) || action == @selector(reveal:)) {
		return !(selectedDock == nil || [tableView selectedRow] == -1);
	
	} else if (action == @selector(delete:) || action == @selector(cut:)) {
		return !(selectedDock == nil || [tableView selectedRow] == -1 || [selectedDock isEqualToString:[preferences currentDock]]);
	
	} else if (action == @selector(paste:)) {
		return !(selectedDock == nil || [selectedDock isEqualToString:[preferences currentDock]]);
		
	} else {
		return YES;
	}
}

static NSArray *filePboardTypeList = nil;
/*!
 * Copies the currently selected item to the clipboard, in the form of a URL,
 * a filename, its label, and its item type. If multiple items are being copied
 * only the first URL will be put on the pasteboard.
 *
 * @see #copyPath:
 * @see #cut:
 * @see #paste:
 */
- (IBAction)copy:(id)sender
{
	NSIndexSet *rowIndexes = [tableView selectedRowIndexes];
	NSArray *selectedItems = [selectedData pantherObjectsAtIndexes:rowIndexes];
	NSArray *selectedPaths = [selectedItems valueForKey:@"path"];
	NSArray *selectedFilenames = [selectedItems valueForKey:@"label"];
	
	if (filePboardTypeList == nil) {
		filePboardTypeList = [[NSArray alloc] initWithObjects: NSURLPboardType, URLNamePboardType, NSFilenamesPboardType, NSStringPboardType, nil];
	}
	
	NSMutableArray *pboardTypes = [filePboardTypeList mutableCopy];
	unsigned dockAppIndex, dockItemIndex, widgetIndex;
	
	dockAppIndex = [rowIndexes firstIndex];
	if (dockAppIndex < dockItemsRow && dockAppIndex < dashboardItemsRow) {
		[pboardTypes addObject:DockyardDockAppsPboardType];
	} else dockAppIndex = NSNotFound;
	
	dockItemIndex = [rowIndexes indexGreaterThanIndex:dockItemsRow];
	if (dockItemIndex != NSNotFound && dockItemIndex < dashboardItemsRow) {
		[pboardTypes addObject:DockyardDockItemsPboardType];
	} else dockItemIndex = NSNotFound;
	
	widgetIndex = [rowIndexes indexGreaterThanIndex:dashboardItemsRow];
	if (widgetIndex != NSNotFound) {
		[pboardTypes addObject:DockyardWidgetsPboardType];
	} else widgetIndex = NSNotFound;
	
	NSPasteboard *pboard = [NSPasteboard generalPasteboard];
	[pboard declareTypes:pboardTypes owner:nil];
	[pboard setString:[selectedFilenames componentsJoinedByString:@"\n"] forType:NSStringPboardType];
	[pboard setPropertyList:selectedPaths forType:NSFilenamesPboardType];
	[pboard setString:[[selectedItems objectAtIndex:0] label] forType:URLNamePboardType];
	[[[selectedItems objectAtIndex:0] pathURL] writeToPasteboard:pboard];
	
	NSMutableArray *itemsInType;
	if (dockAppIndex != NSNotFound) {
		itemsInType = [NSMutableArray new];
		unsigned max = (dockItemsRow == INT_MAX) ? dashboardItemsRow : dockItemsRow;
		while (dockAppIndex < max && dockAppIndex != NSNotFound) {
			[itemsInType addObject:[[selectedData objectAtIndex:dockAppIndex] representedData]];
			dockAppIndex = [rowIndexes indexGreaterThanIndex:dockAppIndex];
		}
		[pboard setPropertyList:itemsInType forType:DockyardDockAppsPboardType];
		[itemsInType release];
	}

	if (dockItemIndex != NSNotFound) {
		itemsInType = [NSMutableArray new];
		while (dockItemIndex < dashboardItemsRow && dockItemIndex != NSNotFound) {
			[itemsInType addObject:[[selectedData objectAtIndex:dockItemIndex] representedData]];
			dockItemIndex = [rowIndexes indexGreaterThanIndex:dockItemIndex];
		}
		[pboard setPropertyList:itemsInType forType:DockyardDockItemsPboardType];
		[itemsInType release];
	}

	if (widgetIndex != NSNotFound) {
		itemsInType = [NSMutableArray new];
		while (widgetIndex != NSNotFound) {
			[itemsInType addObject:[[selectedData objectAtIndex:widgetIndex] representedData]];
			widgetIndex = [rowIndexes indexGreaterThanIndex:widgetIndex];
		}
		[pboard setPropertyList:itemsInType forType:DockyardWidgetsPboardType];
		[itemsInType release];
	}
}

/*!
 * Copies the currently selected item to the clipboard, in the form of a URL,
 * a filename, its <em>path</em> (not its label), and its item type. If multiple
 * items are being copied only the first URL will be put on the pasteboard.
 *
 * @see #copy:
 * @see #cut:
 * @see #paste:
 */
- (IBAction)copyPath:(id)sender
{
	NSIndexSet *rowIndexes = [tableView selectedRowIndexes];
	NSArray *selectedItems = [selectedData pantherObjectsAtIndexes:rowIndexes];
	NSArray *selectedPaths = [selectedItems valueForKey:@"path"];
	
	if (filePboardTypeList == nil) {
		filePboardTypeList = [[NSArray alloc] initWithObjects: NSURLPboardType, URLNamePboardType, NSFilenamesPboardType, NSStringPboardType, nil];
	}
	
	NSPasteboard *pboard = [NSPasteboard generalPasteboard];
	[pboard declareTypes:filePboardTypeList owner:nil];
	[[[selectedItems objectAtIndex:0] pathURL] writeToPasteboard:pboard];
	[pboard setString:[[selectedItems objectAtIndex:0] label] forType:URLNamePboardType];
	[pboard setString:[selectedPaths componentsJoinedByString:@"\n"] forType:NSStringPboardType];
	[pboard setPropertyList:selectedPaths forType:NSFilenamesPboardType];
}

/*!
 * Cuts the currently selected items by calling <code>copy:</code>, <code>delete:</code>.
 *
 * @see #copy:
 * @see #copyPath:
 * @see #paste:
 */
- (IBAction)cut:(id)sender
{
	[self copy:sender];
	[self delete:sender];
	
	NSUndoManager *undoManager = [tableView undoManager];
	[undoManager setActionName:NSLocalizedStringFromTableInBundle(@"Cut", @"Undo", [NSBundle bundleWithIdentifier:@"com.apple.AppKit"], @"")];
}

/*!
 * Pastes the item(s) on the clipboard into the currently selected dock.
 * Does not support pasting from outside of the application. This method
 * automatically sorts pasted items into their proper item sets.
 *
 * @see #copy:
 * @see #copyPath:
 * @see #cut:
 */
- (IBAction)paste:(id)sender
{
	NSPasteboard *pboard = [NSPasteboard generalPasteboard];
	NSArray *pboardTypes = [pboard types];
	NSArray *data;
	BOOL undoWasRecorded = NO;
	
	if (selectedDock == nil) return;
	if ([[preferences currentDock] isEqualToString:selectedDock]) {
		NSBeep();
		if (shouldShowCannotDragSheet)
			NSBeginAlertSheet(NSLocalizedString(@"You cannot modify the current dock.", @"Error message when the current dock is modified"), nil,
				NSLocalizedString(@"Don't show again", @"This sheet can get annoying"), nil, [tableView window], self, @selector(errorSheetEnded:returnCode:defaultsKey:),
				NULL, @"Manager:shouldShowCannotDragSheet", NSLocalizedString(@"Try switching to another dock, making your changes, and switching back.", @"Suggested action, since you can't modify the current dock"));
		return;
	}
		
	NSUndoManager *undoManager = [tableView undoManager];
	[undoManager beginUndoGrouping];
	
	if ([pboardTypes containsObject:DockyardDockAppsPboardType]) {
		[[undoManager prepareWithInvocationTarget:self]
			setItems:[[[preferences itemsInSet:DockyardDockAppSet ofDock:selectedDock] copy] autorelease] inSet:DockyardDockAppSet ofDock:selectedDock newSelection:nil prevSelection:nil];
		undoWasRecorded = YES;
			
		data = [pboard propertyListForType:DockyardDockAppsPboardType];
		[preferences addItemsWithData:data toSet:DockyardDockAppSet ofDock:selectedDock];
	}
	
	if ([pboardTypes containsObject:DockyardDockItemsPboardType]) {
		[[undoManager prepareWithInvocationTarget:self]
			setItems:[[[preferences itemsInSet:DockyardDockItemSet ofDock:selectedDock] copy] autorelease] inSet:DockyardDockItemSet ofDock:selectedDock newSelection:nil prevSelection:nil];
		undoWasRecorded = YES;
			
		data = [pboard propertyListForType:DockyardDockItemsPboardType];
		[preferences addItemsWithData:data toSet:DockyardDockItemSet ofDock:selectedDock];
	}
	
	if ([pboardTypes containsObject:DockyardWidgetsPboardType]) {
		[[undoManager prepareWithInvocationTarget:self]
			setItems:[[[preferences itemsInSet:DockyardWidgetSet ofDock:selectedDock] copy] autorelease] inSet:DockyardWidgetSet ofDock:selectedDock newSelection:nil prevSelection:nil];
			
		data = [pboard propertyListForType:DockyardWidgetsPboardType];
		if ([preferences addItemsWithData:data toSet:DockyardWidgetSet ofDock:selectedDock]) {
			undoWasRecorded = YES;
		} else {
			NSBeep();
			if (shouldShowCannotModifyDashboardSheet) {
				NSBeginAlertSheet(NSLocalizedString(@"This dock contains the default Dashboard, which cannot be modified.", @"Error message when the default dashboard is modified"), nil,
					NSLocalizedString(@"Don't show again", @"This sheet can get annoying"), nil, [tableView window], self,
					@selector(errorSheetEnded:returnCode:defaultsKey:), NULL, @"Manager:shouldShowCannotModifyDashboardSheet",
					NSLocalizedString(@"Try switching to this dock, changing the Dashboard, and switching back.", @"Suggested action, since you can't modify the default dashboard"));
			}
		}
	}
	
	[undoManager endUndoGrouping];
	if (undoWasRecorded) {
		[undoManager setActionName:NSLocalizedStringFromTableInBundle(@"Paste", @"Undo", [NSBundle bundleWithIdentifier:@"com.apple.AppKit"], @"")];
	}
}

static NSIndexSet *emptyIndexSet = nil;
/*!
 * Deletes the currently selected items, unless the selected dock is the current dock.
 *
 * @see DockyardPreferences#removeItemsAtIndexes:fromSet:ofDock:
 */
- (IBAction)delete:(id)sender
{
	if (selectedDock == nil)
		return;
	
	if ([[preferences currentDock] isEqualToString:selectedDock]) {
		NSBeep();
		if (shouldShowCannotDragSheet)
			NSBeginAlertSheet(NSLocalizedString(@"You cannot modify the current dock.", @"Error message when the current dock is modified"), nil,
				NSLocalizedString(@"Don't show again", @"This sheet can get annoying"), nil, [tableView window], self, @selector(errorSheetEnded:returnCode:defaultsKey:),
				NULL, @"Manager:shouldShowCannotDragSheet", NSLocalizedString(@"Try switching to another dock, making your changes, and switching back.", @"Suggested action, since you can't modify the current dock"));
		return;
	}
	
	if (emptyIndexSet == nil) {
		emptyIndexSet = [NSIndexSet new];
	}

	NSIndexSet *rowIndexes = [[tableView selectedRowIndexes] retain];
	[tableView deselectAll:sender];
	
	unsigned totalCount = [rowIndexes count];
	if (totalCount == 0) return;
	
	NSUndoManager *undoManager = [tableView undoManager];
	[undoManager beginUndoGrouping];
	
	NSMutableIndexSet *setIndexes = [rowIndexes mutableCopy];
	unsigned offset = 1;
	unsigned setMax = (dockItemsRow != INT_MAX) ? dockItemsRow : dashboardItemsRow;
	unsigned count;
	
	[setIndexes removeIndex:0];
	[setIndexes removeIndexesInRange:NSMakeRange(setMax, INT_MAX - setMax)];
	count = [setIndexes count];
	if (count > 0) {
		[setIndexes shiftIndexesStartingAtIndex:0 by:-offset];
		[[undoManager prepareWithInvocationTarget:self]
			setItems:[[[preferences itemsInSet:DockyardDockAppSet ofDock:selectedDock] copy] autorelease] inSet:DockyardDockAppSet ofDock:selectedDock newSelection:rowIndexes prevSelection:emptyIndexSet];
		[preferences removeItemsAtIndexes:setIndexes fromSet:DockyardDockAppSet ofDock:selectedDock];
	}
	
	if (setMax != INT_MAX) {
		[setIndexes removeAllIndexes];
		[setIndexes addIndexes:rowIndexes];
//		[setIndexes shiftIndexesStartingAtIndex:0 by:-firstCount];
		
		offset = setMax + 1;
		[setIndexes removeIndexesInRange:NSMakeRange(0, offset)];
		
		setMax = dashboardItemsRow;
		if (setMax != INT_MAX) {
			[setIndexes removeIndexesInRange:NSMakeRange(setMax, INT_MAX - setMax)];
		}
		count = [setIndexes count];
		if (count > 0) {
			[setIndexes shiftIndexesStartingAtIndex:0 by:-offset];
			[[undoManager prepareWithInvocationTarget:self]
				setItems:[[[preferences itemsInSet:DockyardDockItemSet ofDock:selectedDock] copy] autorelease] inSet:DockyardDockItemSet ofDock:selectedDock newSelection:rowIndexes prevSelection:emptyIndexSet];
			[preferences removeItemsAtIndexes:setIndexes fromSet:DockyardDockItemSet ofDock:selectedDock];
		}

		if (setMax != INT_MAX) {
			[setIndexes removeAllIndexes];
			[setIndexes addIndexes:rowIndexes];
//			[setIndexes shiftIndexesStartingAtIndex:0 by:-(secondCount + firstCount)];

			offset = setMax + 1;
			[setIndexes removeIndexesInRange:NSMakeRange(0, offset)];
			
			if ([setIndexes count] > 0) {
				[setIndexes shiftIndexesStartingAtIndex:0 by:-offset];
				[[undoManager prepareWithInvocationTarget:self]
					setItems:[[[preferences itemsInSet:DockyardWidgetSet ofDock:selectedDock] copy] autorelease] inSet:DockyardWidgetSet ofDock:selectedDock newSelection:rowIndexes prevSelection:emptyIndexSet];
				[preferences removeItemsAtIndexes:setIndexes fromSet:DockyardWidgetSet ofDock:selectedDock];
			}
		}
	}
	[rowIndexes release];
	[setIndexes release];
	[undoManager endUndoGrouping];
	if (totalCount == 1)
		[undoManager setActionName:NSLocalizedString(@"Delete Item", @"Undo action title for removing an item from a Dock")];
	else
		[undoManager setActionName:NSLocalizedString(@"Delete Items", @"Undo action title for removing multiple items from a Dock")];
}

/*!
 * Opens the selected item(s), using their default file associations. If the item
 * is a URL, opens it using the user's default browser. If the item is a widget
 * and the selected dock is the current one, merely opens the Dashboard, as opposed
 * to adding the item once more.
 *
 * @see #reveal:
 */
- (IBAction)open:(id)sender
{
	if (selectedDock == nil)
		return;
	
	NSWorkspace *workspace = [NSWorkspace sharedWorkspace];

	NSIndexSet *selectedRowIndexes = [tableView selectedRowIndexes];
	NSArray *itemsToOpen = [selectedData pantherObjectsAtIndexes:selectedRowIndexes];
	NSEnumerator *itemEnumerator = [itemsToOpen objectEnumerator];
	DockItemInfo *nextItem;
	
	BOOL isCurrentDock = [selectedDock isEqualToString:[preferences currentDock]];
		
	while (nextItem = [itemEnumerator nextObject]) {
		if (isCurrentDock && [nextItem itemType] == DockyardWidgetType)
			[workspace openFile:@"/Applications/Dashboard.app"];
		else
			[workspace openURL:[nextItem pathURL]];
	}
}

/*!
 * Makes sure the row clicked in the table view was not one of the header cells
 * (e.g., "Dock Applications") before calling <code>open:</code>.
 *
 * @see #open:
 */
- (IBAction)tableOpen:(id)sender
{
	int clickedRow = [tableView clickedRow];
	if (clickedRow > 0 && clickedRow != dockItemsRow && clickedRow != dashboardItemsRow) {
		[self open:sender];
	}
}

/*!
 * Reveals the currently selected item(s) in the Finder. If more than one item
 * is a certain folder, only one of them will be selected.
 *
 * @see #open:
 */
- (IBAction)reveal:(id)sender
{
	if (selectedDock == nil)
		return;
	
	NSWorkspace *workspace = [NSWorkspace sharedWorkspace];

	NSIndexSet *selectedRowIndexes = [tableView selectedRowIndexes];
	NSArray *itemsToOpen = [selectedData pantherObjectsAtIndexes:selectedRowIndexes];
	NSEnumerator *itemEnumerator = [itemsToOpen objectEnumerator];
	DockItemInfo *nextItem;
	
	while (nextItem = [itemEnumerator nextObject]) {
		[workspace selectFile:[nextItem path] inFileViewerRootedAtPath:nil];
	}
}

/*!
 * Runs an open panel that allows the user to choose items to add to the selected dock.
 *
 * @see addItemsWithPaths:
 */
- (IBAction)addItem:(id)sender
{
	if (selectedDock == nil)
		return;
	
	if ([[preferences currentDock] isEqualToString:selectedDock]) {
		NSBeep();
		if (shouldShowCannotDragSheet)
			NSBeginAlertSheet(NSLocalizedString(@"You cannot modify the current dock.", @"Error message when the current dock is modified"), nil,
				NSLocalizedString(@"Don't show again", @"This sheet can get annoying"), nil, [tableView window], self, @selector(errorSheetEnded:returnCode:defaultsKey:),
				NULL, @"Manager:shouldShowCannotDragSheet", NSLocalizedString(@"Try switching to another dock, making your changes, and switching back.", @"Suggested action, since you can't modify the current dock"));
		return;
	}
	
	NSOpenPanel *openPanel = [NSOpenPanel openPanel];
	[openPanel setTitle:NSLocalizedString(@"Add to Dock", @"Title of Open panel used to add items")];
	[openPanel setPrompt:NSLocalizedString(@"Add", @"Title on button in Open panel used to add items")];
	[openPanel setAllowsMultipleSelection:YES];
	[openPanel setResolvesAliases:YES];
	[openPanel setCanChooseDirectories:YES];
	[openPanel setCanChooseFiles:YES];
	
	int result = [openPanel runModalForTypes:nil];
	if (result == NSOKButton) {
		[self addItemsWithPaths:[openPanel filenames]];
	}
}

/*!
 * Adds the items at <code>paths</code> to the selected docks, automatically figuring out
 * which item set they belong to.
 *
 * @see DockyardPreferences#addItem:toSet:ofDock:
 * @see #addItemWithURL:label:
 */
- (void)addItemsWithPaths:(NSArray *)paths
{
	static LSRequestedInfo infoRequest = kLSRequestBasicFlagsOnly | kLSRequestExtension;
	LSItemInfoRecord fileInfo;
		
	NSUndoManager *undoManager = [tableView undoManager];
	[undoManager beginUndoGrouping];
	
	[[undoManager prepareWithInvocationTarget:self]
		setItems:[[[preferences itemsInSet:DockyardDockAppSet ofDock:selectedDock] copy] autorelease] inSet:DockyardDockAppSet ofDock:selectedDock newSelection:nil prevSelection:nil];
	[[undoManager prepareWithInvocationTarget:self]
		setItems:[[[preferences itemsInSet:DockyardDockItemSet ofDock:selectedDock] copy] autorelease] inSet:DockyardDockItemSet ofDock:selectedDock newSelection:nil prevSelection:nil];
//	[[undoManager prepareWithInvocationTarget:self] // Not yet necessary until I figure out how to add things to the Dashboard -- see below
//		setItems:[[[preferences itemsInSet:DockyardWidgetSet ofDock:selectedDock] copy] autorelease] inSet:DockyardWidgetSet ofDock:selectedDock newSelection:nil prevSelection:nil];
		
	[undoManager endUndoGrouping];
	if ([paths count])
		[undoManager setActionName:NSLocalizedString(@"Add Item", @"Undo action title for adding an item to a Dock")];
	else
		[undoManager setActionName:NSLocalizedString(@"Add Items", @"Undo action title for adding multiple items to a Dock")];
	
	NSEnumerator *itemEnum = [paths objectEnumerator];
	NSString *path;
	NSURL *pathURL;
	enum DockyardItemSet itemSet;
	
	while (path = [itemEnum nextObject]) {
		pathURL = [[NSURL alloc] initFileURLWithPath:path];
		LSCopyItemInfoForURL((CFURLRef) pathURL, infoRequest, &fileInfo);
		if (fileInfo.flags & kLSItemInfoIsApplication) {
			itemSet = DockyardDockAppSet;
			
/*		} else if ((fileInfo.flags & kLSItemInfoIsPackage) &&
					[preferences shouldSwitchDashboard] &&
					[(NSString *)fileInfo.extension isEqualToString:@"wdgt"]) {
			itemSet = DockyardWidgetSet; // If this is uncommented, make sure to uncomment the undo manager lines above
			itemSet = DockyardDockItemSet; // Temporary, I hope...it should add it to the Dashboard
		*/
		} else {
			itemSet = DockyardDockItemSet;
		}
		[preferences addItem:path toSet:itemSet ofDock:selectedDock];
		
		[pathURL release];
		[(NSString *)fileInfo.extension release];
	}
}

/*!
 * Adds the URL to the item set of the current dock, using <code>label</code>
 * as the item's title.
 *
 * @see DockyardPreferences#addURL:withLabel:toDock:
 * @see #addItemsWithPaths:
 */
- (void)addItemWithURL:(NSURL *)url label:(NSString *)label
{
	NSUndoManager *undoManager = [tableView undoManager];
	[[undoManager prepareWithInvocationTarget:self]
		setItems:[[[preferences itemsInSet:DockyardDockItemSet ofDock:selectedDock] copy] autorelease] inSet:DockyardDockItemSet ofDock:selectedDock newSelection:nil prevSelection:nil];
	[undoManager setActionName:NSLocalizedString(@"Add Item", @"Undo action title for adding an item to a Dock")];
	
	[preferences addURL:url withLabel:label toDock:selectedDock];
}

/*!
 * Sets the items in the given item set of the specified dock to <code>items</code>.
 * This method is used for undo support and should never be called directly. Also
 * updates the selection indexes of the tables to match what they were previously.
 *
 * @see DockyardPreferences#itemsInSet:ofDock:
 * @see DockyardPreferences#setItems:inSet:ofDock:
 */
- (void)setItems:(NSArray *)items inSet:(enum DockyardItemSet)itemSet ofDock:(NSString *)dockName newSelection:(NSIndexSet *)rowIndexes prevSelection:(NSIndexSet *)prevIndexes
{
	if (emptyIndexSet == nil) {
		emptyIndexSet = [NSIndexSet new];
	}

	[tableView deselectAll:nil];

	NSArray *oldItems = [[preferences itemsInSet:itemSet ofDock:dockName] retain];
	[preferences setItems:items inSet:itemSet ofDock:dockName];
	
	[[[tableView undoManager] prepareWithInvocationTarget:self] setItems:oldItems inSet:itemSet ofDock:dockName newSelection:prevIndexes prevSelection:rowIndexes];
	[oldItems release];
	
	[dockNamesController setSelectedObjects:[NSArray arrayWithObject:dockName]];
	[indexesToSelect release];
	indexesToSelect = [rowIndexes retain];
}

#pragma mark -

- (void)dockyardDockChanged:(NSNotification *)note
{
	NSString *newName = [[note userInfo] objectForKey:@"newDock"];
	NSArray *newDockData = [self createDataForDock:newName];
	NSString *oldName = [[note userInfo] objectForKey:@"oldDock"];
	NSArray *oldDockData = [self createDataForDock:oldName];
	
	[self willChangeValueForKey:@"selectedItem"];
	[dataForDocks setObject:newDockData forKey:newName];
	[dataForDocks setObject:oldDockData forKey:oldName];
	[newDockData release];
	[oldDockData release];
	
	if ([newName isEqualToString:selectedDock] || [oldName isEqualToString:selectedDock])
		[tableView reloadData];
		
	[self didChangeValueForKey:@"selectedItem"];
}

- (void)dockyardDockItemsChanged:(NSNotification *)note
{
	if (draggedPboardType == nil) {
		NSString *dockName = [[note userInfo] objectForKey:@"dockName"];
		NSArray *data = [self createDataForDock:dockName];
		
		[self willChangeValueForKey:@"selectedItem"];
		[dataForDocks setObject:data forKey:dockName];
		[data release];
		
		if ([dockName isEqualToString:selectedDock])
			[tableView reloadData];
			
		[self didChangeValueForKey:@"selectedItem"];
		
		if (indexesToSelect != nil) {
			[tableView selectRowIndexes:indexesToSelect byExtendingSelection:NO];
			[indexesToSelect release];
			indexesToSelect = nil;
		} 
	}
}

/*!
 * Creates an array of DockItemInfo objects, to be used as data when displaying
 * <code>dockName</code>'s contents. Uses localized NSStrings to head each section:
 * <code>@"Dock Applications"</code>, <code>@"Dock Items"</code>, and
 * <code>@"Dashboard Widgets"</code>. If a section has no items in it, the header
 * will not be displayed. If Dashboard switching is disabled, the widget section
 * will not be displayed. If there ends up being no items in the Dock, a header cell
 * will be displayed, saying <code>@"Empty Dock"</code>. This data will <em>not</em>
 * be autoreleased.
 */
- (NSArray *)createDataForDock:(NSString *)dockName
{
//	NSDictionary *dockInfo = [preferences dataForDockWithName:dockName];
	
//	if (dockInfo == nil) return nil; // should never occur.
	
	NSBundle *bundle = [NSBundle mainBundle];
	NSMutableArray *cells = [NSMutableArray new];
	id currentCell;
	
	NSDictionary *itemData;
	enum DockyardItemType itemType;
	NSEnumerator *itemEnum;
	NSArray *itemsArray;
	
//	itemsArray = [dockInfo objectForKey:@"persistent-apps"];
	itemsArray = [preferences itemsInSet:DockyardDockAppSet ofDock:dockName];
	if ([itemsArray count] > 0) {
/*		[dockMenu addItem:[NSMenuItem separatorItem]];
		currentCell = [[NSTextFieldCell alloc] initTextCell:@"Dock Applications"];
		[currentCell setEnabled:NO];
		[cells addObject:currentCell];
		[currentCell release]; */
		[cells addObject:NSLocalizedStringFromTableInBundle(@"Dock Applications", nil, bundle, @"Title of row heading the Dock applications")];
			
		itemEnum = [itemsArray objectEnumerator]; // persistant-apps is an NSArray
		
		while ((itemData = [itemEnum nextObject]) != nil) {
			itemType = [self typeForDockItemData:[itemData objectForKey:@"tile-data"] rawType:[itemData objectForKey:@"tile-type"]];

			currentCell = [self dockItemInfoWithData:itemData type:itemType];
			[cells addObject:currentCell];
		}
	}
	
//	itemsArray = [dockInfo objectForKey:@"persistent-others"];
	itemsArray = [preferences itemsInSet:DockyardDockItemSet ofDock:dockName];
	if ([itemsArray count] > 0) {	
/*		[dockMenu addItem:[NSMenuItem separatorItem]];
		currentCell = [[NSBrowserCell alloc] initTextCell:@"Dock Items"];
		[currentCell setEnabled:NO];
		[currentCell setLeaf:YES];
		[cells addObject:currentCell];
		[currentCell release]; */
		[cells addObject:NSLocalizedStringFromTableInBundle(@"Dock Items", nil, bundle, @"Title of row heading the Dock items (not apps)")];
				
		itemEnum = [itemsArray objectEnumerator]; // persistant-others is an NSArray
		
		while ((itemData = [itemEnum nextObject]) != nil) {
			itemType = [self typeForDockItemData:[itemData objectForKey:@"tile-data"] rawType:[itemData objectForKey:@"tile-type"]];

			currentCell = [self dockItemInfoWithData:itemData type:itemType];
			[cells addObject:currentCell];
		}
	}
	
	if ([preferences shouldSwitchDashboard]) {
//		dockInfo = [preferences dashboardDataForDockWithName:dockName];
//		if (dockInfo != nil) {
			itemsArray = [preferences itemsInSet:DockyardWidgetSet ofDock:dockName];
//			itemsArray = [dockInfo objectForKey:@"layer-gadgets"];
			if (itemsArray != nil && [itemsArray count] > 0) {			
/*				currentCell = [[NSBrowserCell alloc] initTextCell:@"Dashboard Widgets"];
				[currentCell setEnabled:NO];
				[currentCell setLeaf:YES];
				[cells addObject:currentCell];
				[currentCell release]; */
				[cells addObject:NSLocalizedStringFromTableInBundle(@"Dashboard Widgets", nil, bundle, @"Title of row heading the Dashboard items")];
					
				itemEnum = [itemsArray objectEnumerator]; // persistant-apps is an NSArray
				
				while ((itemData = [itemEnum nextObject]) != nil) {
					currentCell = [self dockItemInfoWithData:itemData type:DockyardWidgetType];
					[cells addObject:currentCell];
				}
			}
//		}
	}
	
	if ([cells count] == 0)
		[cells addObject:NSLocalizedStringFromTableInBundle(@"Empty Dock", nil, bundle, @"Title displayed when a dock is completely empty")];
	
	return cells;
}

/*!
 * Returns the item type for the given data, where <code>rawType</code> is in the
 * form of a @"tile-type" string from the defaults domain com.apple.dock.
 */
- (enum DockyardItemType)typeForDockItemData:(NSDictionary *)itemData rawType:(NSString *)rawType
{
	if (rawType == nil) rawType = [itemData objectForKey:@"tile-type"]; // Weird quirk in Apple's default Dock...
	if (rawType != nil) {
		if ([rawType isEqualToString:@"file-tile"])
			return DockyardFileType;
		else if ([rawType isEqualToString:@"url-tile"])
			return DockyardURLType;
		else if ([rawType isEqualToString:@"dashboard-tile"])
			return DockyardDashboardType;
		else if ([rawType isEqualToString:@"dockling-tile"])
			return DockyardDocklingType;
	}
	// Assume no "tile-data" info was found, or that it denotes an unknown type.
	if ([itemData objectForKey:@"DockExtraType"] != nil) return DockyardDocklingType;
	else if ([itemData objectForKey:@"file-data"] != nil) return DockyardFileType;
	else if ([itemData objectForKey:@"url"] != nil) return DockyardURLType;
	else return DockyardUnknownType;
}

/*!
 * This method takes the data of an item in a dock and creates an instance of DockItemInfo.
 * The two attributes focused on are the label and path of the item (so it can be opened
 * later) but these are taken from labelForDockItemData: and pathForDockItemData:.
 */
- (DockItemInfo *)dockItemInfoWithData:(NSDictionary *)itemData type:(enum DockyardItemType)itemType
{
	NSDictionary *tileData;
	if (DOCKYARD_TYPE_IS_FROM_DOCK(itemType))
		tileData = [itemData objectForKey:@"tile-data"];
	else
		tileData = itemData;
	NSString *path = [self pathForDockItemTileData:tileData type:itemType];
	NSString *label = [self labelForDockItemTileData:tileData type:itemType];
	
	DockItemInfo *itemInfo = [[DockItemInfo alloc] initWithPath:path label:label type:itemType];
	[itemInfo setRepresentedData:itemData];
	return [itemInfo autorelease];
}
 
/*!
 * Finds the path of the given data by first looking for a file path, then URL data.
 */
- (NSString *)pathForDockItemTileData:(NSDictionary *)itemData type:(enum DockyardItemType)itemType
{
	static NSString * const kDashboardPath = @"/Applications/Dashboard.app";
	static NSString *widgetManagerPath = nil;

	NSString *path;
	switch (itemType) {
	case DockyardURLType:
		path = [[itemData objectForKey:@"url"] objectForKey:@"_CFURLString"];
		break;
	case DockyardFileType:
	case DockyardDocklingType:
		path = [[itemData objectForKey:@"file-data"] objectForKey:@"_CFURLString"];
		break;
	case DockyardDashboardType:
		path = kDashboardPath;
		break;
	case DockyardWidgetType:
		path = [itemData objectForKey:@"path"];
		if (path == nil && [[itemData objectForKey:@"manager"] boolValue]) {
			if (widgetManagerPath == nil) {
				NSBundle *dockBundle = [[NSBundle alloc] initWithPath:@"/System/Library/CoreServices/Dock.app"];
				widgetManagerPath = [[dockBundle pathForResource:@"Widgets" ofType:@"wdgt"] retain];
				[dockBundle release];
			}
			path = widgetManagerPath;
		}
		break;
	default:
		path = [[itemData objectForKey:@"file-data"] objectForKey:@"_CFURLString"];
		if (path == nil)
			path = [[itemData objectForKey:@"url"] objectForKey:@"_CFURLString"];
	}

	if (path == nil) path = @"";
	else if ([path hasSuffix:@"/"]) path = [path substringToIndex:([path length] - 1)];
	
	return path;
}

/*!
 * Finds a label for the specified data by searching the item data for the value of "file-label"
 * or "label". If neither is found, the display name of the program or file is returned.
 */
- (NSString *)labelForDockItemTileData:(NSDictionary *)itemData type:(enum DockyardItemType)itemType
{
	static NSString *dashboardLabel = nil;
	static NSFileManager *manager = nil;

	NSString *label;
	switch (itemType) {
	case DockyardFileType:
	case DockyardDocklingType:
		label = [itemData objectForKey:@"file-label"];
		break;
	case DockyardURLType:
		label = [itemData objectForKey:@"label"];
		break;
	case DockyardDashboardType:
		if (dashboardLabel == nil) {
			NSBundle *dockBundle = [[NSBundle alloc] initWithPath:@"/System/Library/CoreServices/Dock.app"];
			dashboardLabel = [[dockBundle objectForInfoDictionaryKey:@"DashboardName"] retain];
			[dockBundle release];
		}
		label = dashboardLabel;
		break;
	case DockyardWidgetType:
		{
			NSString *path = [self pathForDockItemTileData:itemData type:itemType];
			NSBundle *widgetBundle = [[NSBundle alloc] initWithPath:path];
			label = [widgetBundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];
			[widgetBundle release];
		}
		if (label != nil)
			break;
	default:
		label = [itemData objectForKey:@"file-label"];
		if (label == nil)
			label = [itemData objectForKey:@"label"];
	}
	
	if (label == nil) { // if (label STILL equals nil)
//		label = [[self pathForDockItemData:itemData] lastPathComponent];
		if (manager == nil)
			manager = [NSFileManager defaultManager];
		label = [manager displayNameAtPath:[self pathForDockItemTileData:itemData type:itemType]];
	}
	
	return label;
}

@end