/* =============================================================================
    PROJECT:    Filie
    FILE:       UKFolderIconViewController.m
    
    COPYRIGHT:  (c) 2004 by M. Uli Kusterer, all rights reserved.
    
    AUTHORS:    M. Uli Kusterer - UK
    
    LICENSES:   GNU GPL
    
    REVISIONS:
        2004-04-15  UK  Created.
   ========================================================================== */

// -----------------------------------------------------------------------------
//  Headers:
// -----------------------------------------------------------------------------

#import "UKFolderIconViewController.h"
#import <UnitKit/UnitKit.h>

#ifndef __ETOILE__
#import "UKDirectoryEnumerator.h"
#endif

#import "UKFSItemViewProtocol.h"

#ifndef __ETOILE__
#import "UKDistributedView.h"
#import "UKFinderIconCell.h"
#import "NSImage+NiceScaling.h"
#import "NSFileManager+NameForTempFile.h"

#else
#import <EtoileExtensions/UKDistributedView.h>
#import <EtoileExtensions/UKFinderIconCell.h>
#import <EtoileExtensions/NSImage+NiceScaling.h>
#import <EtoileExtensions/NSFileManager+NameForTempFile.h>

#endif

#import "UKFSItem.h"

#ifndef __ETOILE__
#import "UKKQueue.h"

//#else
//#import <EtoileExtensions/UKKQueue.h>

#endif

#import "UKMainThreadActionQueue.h"

#ifndef __ETOILE__
#import "UKPushbackMessenger.h"

#else
#import <EtoileExtensions/UKPushbackMessenger.h>

#endif

#import "UKFolderMetaStorage.h"
#import "UKFolderDataSource.h"


// -----------------------------------------------------------------------------
//  Constants:
// -----------------------------------------------------------------------------

// List of int array-entries for the various item sizes.
//  Each item's index corresponds to the tag of one menu item.
#define ICON_SIZES      16, 32, 48, 64, 128, 256


@implementation UKFolderIconViewController

-(id) initForTest
{
	self = [UKFolderIconViewController alloc];
	
	fileListView = [[UKDistributedView alloc] initWithFrame: NSMakeRect(0, 0, 100, 100)];
	
	self = [self initWithPath: @"/"];
	
	return self;
}

// -----------------------------------------------------------------------------
//  Factory methods according to UKFSItemViewer protocol:
// -----------------------------------------------------------------------------

+(id)   viewForItemAtPath: (NSString*)path
{
	return [[[UKFolderIconViewController alloc] initWithPath: path] autorelease];
}


+(id)   viewForItemAtURL: (NSURL*)url
{
	return [[[UKFolderIconViewController alloc] initWithURL: url] autorelease];
}


// -----------------------------------------------------------------------------
//  initWithPath:
//      Crate a viewer for the item at the specified path. * DEPRECATED *
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(id)			initWithPath: (NSString*)path
{
    self = [self initWithURL: [NSURL fileURLWithPath: path]];
    return self;
}


// -----------------------------------------------------------------------------
//  initWithURL:
//      Crate a viewer for the item at the specified URL.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void)			testInitWithURL
{
	//UKRaisesException([self initWithURL: [NSURL fileURLWithPath: @"/__blabla"]]);	
}

-(id)			initWithURL: (NSURL*)url
{
	self = [super init];
	if( self )
	{
       	[self awakeFromNib];
		
		[self setURL: url];
		
        [self finishCreation];
	}
	
	return self;
}

-(void)			testSetURL
{
		NSURL *url;
		BOOL dummy;
	
		//UKRaisesException([self setURL: [NSURL fileURLWithPath: @"/__blabla"]]);
	
		[self setURL: [NSURL fileURLWithPath: @"~/"]];
		UKTrue([[NSFileManager defaultManager] fileExistsAtPath: folderPath isDirectory: &dummy]);
		UKNotNil(dataSource);
		UKNotNit([dataSource delegate]);
}

- (void) setURL: (NSURL *)url
{
		//NSLog(@"%@ setURL:%@", self, url);
		
		[folderPath release];
		[fileList release];
		
		#ifndef __ETOILE__
		[kqueue release];
		#endif
		
		folderPath = [[url path] copy];
		fileList = [[NSMutableArray alloc] init];
		
		#ifndef __ETOILE__
		kqueue = [[UKKQueue alloc] init];
		[kqueue setDelegate: self];
		[kqueue addPathToQueue: folderPath];
		#endif
		
        dataSource = [self newDataSourceForURL: url];
        //[dataSource setDelegate: self];
        filieStore = [self newMetaStorageForURL: url];
}


// -----------------------------------------------------------------------------
//  dealloc:
//      Destructor.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void)		dealloc
{
    [dataSource release];
	[searchString release];
	searchString = nil;
	[folderPath release];
	[fileList release];
	[coalescer release];
	
	#ifndef __ETOILE__
	[kqueue release];
	#endif
	
    [hiddenList release];
    [forceShowList release];
    [reloadIconQueue release];
    [filieStore release];
    [newItems release];
    
    #ifdef __ETOILE__
    [locks release];
    #endif
	
	[super dealloc];
}


// -----------------------------------------------------------------------------
//  newDataSourceForURL:
//      Method that creates the data source for this viewer. Called by
//      finishCreation. Provided for subclasses which may wish to get their
//      file list from another data source.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) testNewDataSourceForURL
{
	UKNotNil([self newDataSourceForURL: [NSURL fileURLWithPath: @"~/"]]);
}

-(id<UKFSDataSource,NSObject>)  newDataSourceForURL: (NSURL*)url
{
    id newDataSource = [[UKFolderDataSource alloc] initWithURL: url];
	
	[newDataSource setDelegate: self];
	
	return newDataSource;
}


// -----------------------------------------------------------------------------
//  newMetaStorageForURL:
//      Method that creates the metadata storage for this viewer. Called by
//      finishCreation. Provided for subclasses which may wish to get their
//      metadata from another storage.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) testNewMetaStorageForURL
{
	UKNotNil([self newMetaStorageForURL: [NSURL fileURLWithPath: @"~/"]]);
}

-(UKFolderMetaStorage*)  newMetaStorageForURL: (NSURL*)url
{
    return [[UKFolderMetaStorage alloc] initForURL: url];
}


// -----------------------------------------------------------------------------
//  recalcCellSize:
//      Recalculate and change the cell size of our icon view based on the
//      iconSize instance variable.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) testRecalcCellSize
{
	NSSize size;
	
	size = [self recalcCellSize];
	UKTrue(size.width >= 16 && size.height >= 16);
}

-(NSSize)   recalcCellSize
{
    NSSize cellSize = iconSize;
    
    cellSize.width += cellSize.width -1;
    cellSize.height += 22 +(UKFIC_TEXT_VERTMARGIN *2) + UKFIC_SELBOX_VERTMARGIN +(UKFIC_SELBOX_OUTLINE_WIDTH *2) +(UKFIC_IMAGE_VERTMARGIN *2);
    cellSize.width += (UKFIC_TEXT_HORZMARGIN *2) +(UKFIC_SELBOX_OUTLINE_WIDTH *2) +(UKFIC_IMAGE_HORZMARGIN *2);
	
    [fileListView setCellSize: cellSize];
    
    return cellSize;
}

- (void) awakeFromNib
{
	   //NSLog(@"%@ awakeFromNib with view", self, fileListView);
	   
	   reloadIconQueue = [[UKMainThreadActionQueue alloc] initWithMessage: @selector(loadItemIcon:)];
       iconSize = NSMakeSize(48,48);
       forceShowList = [[NSMutableArray alloc] initWithObjects: @".htaccess", nil];
       hideDotFiles = YES;
       showIconPreviews = YES;
       keepArrangedMode = -1;

       #ifdef __ETOILE__
       locks = [[NSMutableDictionary alloc] initWithCapacity: 5];
       #endif
	   
	   [self finishCreation];
}

// -----------------------------------------------------------------------------
//  finishCreation:
//      Instead of awakeFromNib, because awake gets called when UKNibOwner loads
//      our NIB, but at that time the object hasn't been fully constructed yet.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) testFinishCreation
{

}

-(void)			finishCreation
{
	UKFinderIconCell*   fCell = [[[UKFinderIconCell alloc] init] autorelease];
	[fCell setEditable: YES];
	[fileListView setPrototype: fCell];
	[fileListView setDragMovesItems: YES];
	[fileListView registerForDraggedTypes: [NSArray arrayWithObject: NSFilenamesPboardType]];
    
    coalescer = [[UKPushbackMessenger alloc] initWithTarget: fileListView];
    [coalescer setDelay: 1.0];
    [coalescer setMaxPushTime: 4.0];
	
	#ifndef __ETOILE__
	[progress setUsesThreadedAnimation: YES];
				
	// Make sure we get spatial metaphor right and position our window where user left it:
	if( filieStore )
    {
        [[fileListView window] setFrame: [filieStore displayRect] display: NO];
        NSNumber*   num = [filieStore objectForKey: @"snapToGrid"];
        if( num )
            [fileListView setSnapToGrid: [num boolValue]];
        num = [filieStore objectForKey: @"showIconPreviews"];
        if( num )
        {
            showIconPreviews = [num boolValue];
            [previewButton setState: showIconPreviews];
        }
        NSValue*    val = [filieStore objectForKey: @"iconSize"];
        if( val )
            iconSize = [val sizeValue];
        NSString*    str = [filieStore objectForKey: @"filterString"];
        if( str )
        {
            [filterField setStringValue: str];
            [self setSearchString: str];
        }
        num = [filieStore objectForKey: @"keepArrangedMode"];
        if( num )
            keepArrangedMode = [num intValue];
    }
	
	#else
	
	#endif
	
    [self recalcCellSize];
    
    // Now finally kick off loading of our files:
	[NSThread detachNewThreadSelector:@selector(loadFolderContents:) toTarget:self withObject: nil];
}


// -----------------------------------------------------------------------------
//  loadItemIcon:
//      Add the specified UKFSItem to our queue of objects to get a loadItemIcon
//      message.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) testLoadItemIcon
{
	UKFSItem *item = [[UKFSItem alloc] 
		initWithURL: [NSURL fileURLWithPath: @"~/"] isDirectory: YES withAttributes: nil owner: self];
	
	UKTrue([self itemIsVisible: item]);
	UKNotNil(reloadIconQueue);
}

-(void)   loadItemIcon: (UKFSItem*)item
{
    if( [self itemIsVisible: item] )
        [reloadIconQueue addObject: item];
}


// -----------------------------------------------------------------------------
//  startProgress:
//      Start the progress indicator spinning.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) startProgress: (NSString*)statusText
{
    [self startProgress: statusText recordTiming: NO];
}


// -----------------------------------------------------------------------------
//  startProgress:recordTiming:
//      Start the progress indicator spinning. Can be called from another
//      thread. Calls to this can be nested. Only the outermost call's
//      statusText is displayed, all sub-tasks aren't shown.
//
//      NOTE: recordTiming: is only for debugging purposes. It'll be turned off
//      once we release.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) startProgress: (NSString*)statusText recordTiming: (BOOL)time
{
    #ifndef __ETOILE__
	if( progressCount == 0 )
    {
        [progress performSelectorOnMainThread:@selector(startAnimation:) withObject:self waitUntilDone: NO];
        [status performSelectorOnMainThread:@selector(setStringValue:) withObject:[NSString stringWithFormat: @"%d items - %@", [fileList count], statusText] waitUntilDone: NO];
        if( time )
            progressStarted = [[NSDate date] timeIntervalSinceReferenceDate];
    }
    
    progressCount++;
	#endif
}


// -----------------------------------------------------------------------------
//  stopProgress:
//      Balance a call to startProgress: or startProgress:recordTiming: once you
//      are done.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) stopProgress
{
    #ifndef __ETOILE__
	progressCount--;
    
    if( progressCount == 0 )
    {
        NSTimeInterval  ti = [[NSDate date] timeIntervalSinceReferenceDate] -progressStarted;
        [progress performSelectorOnMainThread:@selector(stopAnimation:) withObject:self waitUntilDone: NO];
        [status performSelectorOnMainThread:@selector(setStringValue:) withObject: [NSString stringWithFormat: @"%d items - took %f seconds", [fileList count], ti] waitUntilDone: NO];
    }
	#endif
}


// -----------------------------------------------------------------------------
//  UKDistributedView delegate methods that start/stop the progress indicator
//  when the view is busy:
// -----------------------------------------------------------------------------

-(void) distributedViewDidStartCachingItems: (UKDistributedView*)view
{
	[self startProgress: @"Cacheing items..."];
}


-(void) distributedViewWillEndCachingItems: (UKDistributedView*)view
{
	[self stopProgress];
}


// -----------------------------------------------------------------------------
//  itemForFile:
//      Return the UKFSItem for the specified file name (not path). Returns NIL
//      If there is no such file.
//
//      FIX ME! Right now this tries to pick up its search where it last left
//      off, which is an optimization, but it still performs a linear search.
//      Since this is called during loading, we may want to optimize this a lot
//      more, especially for sequential access of our file list.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) testItemForFile
{
	UKFSItem *item;
	
	[self loadFolderContents: nil];
	
	UKIntsNotEqual([fileList count], 0);
	NSLog(@"valueFor %@", [fileList valueForKey: @"name"]);
	UKTrue([[fileList valueForKey: @"name"]  containsObject: @"usr"]);
	
	item = [self itemForFile: @"usr"];
	
	UKNotNil(item);
}

-(UKFSItem*)    itemForFile: (NSString*)name
{
    int     flc = [fileList count];
    
    if( lastCheckedIndex >= flc )
        lastCheckedIndex = 0;

    UKFSItem*       item = nil;
    int             x, start = lastCheckedIndex, end = flc, y;
    
    for( y = 0; y < 2; y++ )    // Try twice in case we need to wrap around.
    {
        for( x = start; x < end; x++ )
        {
            item = [fileList objectAtIndex: x];
            if( [[item name] isEqualToString: name] )
                return item;
        }
        
        // Wrap around index:
        start = 0;
        end = lastCheckedIndex;
    }
    
    return nil;
}


// -----------------------------------------------------------------------------
//  loadFolderContents:
//      Called in a new thread whenever the folder contents need to be updated.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) testLoadFolderContents
{
	UKNotNil(dataSource);
}

-(void) loadFolderContents: (id)sender
{
    //NSLog(@"%@ loadFolderContents:", self);
	
	if( !newItems )     // newItems functions as flag to avoid re-entrancy.
    {
        NSAutoreleasePool*  pool = [[NSAutoreleasePool alloc] init];
        [dataSource reload: sender];    // Tell FSItem data source to list files:
        [pool release];
    }
}


// -----------------------------------------------------------------------------
//  dataSourceWillReload:
//      Called by the FSItem data source before it starts telling us about the
//      files it has available.
//
//      This makes a new array into which the new list of files is loaded. That
//      way, the user can still work with the old list until the update has
//      finished. However, since old items are shared between the two sources,
//      the user will already get part of the update while working with the old
//      list.
//
//      *this runs in another thread*
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) testDataSourceWillReload
{
	[self performSelector: @selector(dateSourceWillReload:) withObject: nil];
	
	UKNotNil(fileList);	
	UKNotNil(newItems);	
	UKIntsEqual([finalItems count], 0);
	if ([fileList count] == 0)
	{
		UKObjectsEqual(fileList, finalItems);
	}
	else
	{
		UKObjectsNotEqual(fileList, finalItems);
	}
}

-(void) dataSourceWillReload: (id<UKFSDataSource>)dSource
{
    //NSLog(@"%@ dataSourceWillReload:%@", self, dSource);
	
	[self startProgress: @"Loading files..." recordTiming: YES];
    
    #ifdef __ETOILE__
    NSRecursiveLock *mutex = [locks objectForKey: @"lockDataSourceWillReload"];
    
    if ( !mutex )
    {
        mutex = [[[NSRecursiveLock alloc] init] autorelease];
        [locks setObject: mutex forKey: @"lockDataSourceWillReload"];  
    }
    
    [mutex lock];
    //NSLog(@"reload - newItems FLAGGED");
    newItems = [[NSMutableArray alloc] init];
    finalItems = [[NSMutableArray alloc] init];
    if( [fileList count] == 0 ) // Was empty till now? First load!
        fileList = [finalItems retain]; // Let user watch while it's loading.
    [mutex unlock];
    
    #else
    @synchronized(self)
    {
        //NSLog(@"reload - newItems FLAGGED");
        newItems = [[NSMutableArray alloc] init];
        finalItems = [[NSMutableArray alloc] init];
        if( [fileList count] == 0 ) // Was empty till now? First load!
            fileList = [finalItems retain]; // Let user watch while it's loading.
    }
    #endif
}


// -----------------------------------------------------------------------------
//  listItem:withAttributes:source:
//      Called by the FSItem data source for each file it offers us. We ignore
//      invisible files if the user wants us to. This works on a list that is
//      not displayed in a window, but carries over old FSItems.
//
//      *this runs in another thread*
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) testListItemWithAttributesSource
{
	NSString *testPath = [[[NSFileManager defaultManager] subpathsAtPath: folderPath] objectAtIndex: 0];
	NSDictionary *storeDict = [filieStore dictionaryForFile: testPath];
	UKFSItem *item;
	NSPoint storePos, itemPos;
	
	UKObjectsNotEqual(fileList, finalItems);
	UKIntsNotEqual([storeDict count], 0);
	
	item = [self itemForFile: testPath];
	UKStringsEqual([item path], testPath);
	
	[self loadFolderContents: nil];
	
	UKTrue([[finalItems valueForKey: @"path"] containsObject: [item path]]);
	UKObjectsEqual(fileList, finalItems);
	
	storePos = [[storeDict objectForKey: @"position"] pointValue];
	itemPos = [item position];
	UKTrue(storePos.x == itemPos.x && storePos.y == itemPos.y);

}

-(void) listItem: (NSURL*)url withAttributes: (NSDictionary*)attrs source: (id<UKFSDataSource>)dSource
{
    //NSLog(@"%@ listItem:%@withAttributes:source:", self, url);
	
	// We're at file system root? Hide all items named in the .hidden file:
    if( !hiddenList && [folderPath isEqualToString: @"/"] )
    {
        hiddenList = [[NSString stringWithContentsOfFile: @"/.hidden"] componentsSeparatedByString: @"\n"];
        hiddenList = [[hiddenList arrayByAddingObject: @"Network"] retain];  // We also hide "Network", because we display that in a special location.
    }
    
    NSString*       filePath = [url path];
    NSString*       fname = [filePath lastPathComponent];
    BOOL            showFile = [fname characterAtIndex:0] != '.' || (hideDotFiles == NO);   // Begins with dot and we're not supposed to show dotted files? hide!
    
    if( !showFile ) // Hidden, but user added it to the "always show" list?
        showFile = [forceShowList containsObject: fname];
    
    if( showFile )  // Showing, but user (or .hidden file) added it to "always hide" list?
        showFile = ![hiddenList containsObject: fname];
    
    #ifndef __ETOILE__
    if( showFile )
        showFile = ![[attrs objectForKey: UKItemIsInvisible] boolValue];
        // UKItemIsInvisible is defined in UKDirectoryEnumerator.h
	#else
		showFile = YES;
    #endif
    
    if( showFile )  // All these checks still mean we should show them
    {
        UKFSItem*       theItem = nil;
        NSDictionary*   moreInfo = [filieStore dictionaryForFile: fname];
        BOOL            alreadyLoaded = NO;
        
        if( fileList != finalItems )
            theItem = [self itemForFile: fname];
        
        if( !theItem )
            theItem = [[[UKFSItem alloc] initWithURL: url isDirectory: [[attrs objectForKey: NSFileType] isEqualToString: NSFileTypeDirectory]
                                            withAttributes: attrs owner: self] autorelease];
        else
        {
            [theItem setAttributes: attrs];
            alreadyLoaded = YES;
        }
        
        if( theItem )
        {
            if( moreInfo || alreadyLoaded )  // Item we already know?
            {
                if( !alreadyLoaded )
                    [theItem setPosition: [[moreInfo objectForKey: @"position"] pointValue]];
                [finalItems addObject: theItem];
            }
            else
                [newItems addObject: theItem];  // Remember for later. We can't pick a new position yet, because we don't know what spaces are occupied before everything's been loaded.
        }
    }
}


// -----------------------------------------------------------------------------
//  dataSourceWillRecache:
//      Called by the FSItem data source occasionally when it hits a short pause
//      in listing files, e.g. when it's replenishing its cache. If this is the
//      first time we're listing items in our window, this lets the user watch
//      and causes a redraw of the icon view.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) dataSourceWillRecache: (id<UKFSDataSource>)dSource
{
    if( fileList == finalItems )    // Let user watch if this is first time we're loading.
        [fileListView performSelectorOnMainThread:@selector(noteNumberOfItemsChanged) withObject:nil waitUntilDone: NO];
}


// -----------------------------------------------------------------------------
//  dataSourceDidReload:
//      Called by the FSItem data source once it's finished telling us about its
//      list of files. We know swap the old list with the new, updated list and
//      quickly pick positions for all newly-arrived items.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) testDataSourceDidReload
{
	NSPoint pos;
	
	[self loadFolderContents: nil];
	pos = [fileListView suggestedPosition];

	//UKIntsNotEqual([newItems count], 0);
	UKTrue(pos.x > 3 && pos.y > 3);
}

-(void) dataSourceDidReload: (id<UKFSDataSource>)dSource
{
    //NSLog(@"%@ dataSourceDidReload:%@", self, dSource);
	
	// Add all new items in one fell swoop:
    [fileListView setMultiPositioningMode: YES];    // Caches the last free position so we don't have to skip all those occupied slots again.
    NSEnumerator*   enny = [newItems objectEnumerator];
    UKFSItem*       theItem = nil;
    
    while( (theItem = [enny nextObject]) )
    {
        NSPoint pos = [fileListView suggestedPosition]; // Picks a free position that lies on the grid.
        [theItem setPosition: pos];
        [finalItems addObject: theItem];
    }
    [fileListView setMultiPositioningMode: NO];
    
    // Re-sort if needed:
    if( keepArrangedMode >= 0 )
        [self rearrangeItemsByTag: keepArrangedMode];
    
    #ifdef __ETOILE__
    NSRecursiveLock *mutex = [locks objectForKey: @"lockDataSourceWillReload"];
    
    if ( !mutex )
    {
        mutex = [[[NSRecursiveLock alloc] init] autorelease];
        [locks setObject: mutex forKey: @"lockDataSourceWillReload"];  
    }
    
    [mutex lock];
    // Let user see new stuff:
    [fileList release];
    fileList = finalItems;  // Swap in updated list.
    finalItems = nil;
        
    [fileListView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone: NO];
        
    // Clean up:
    [self stopProgress];
    
    //NSLog(@"reload - newItems UN-FLAGGED");
    [newItems release];
    newItems = nil;     // newItems doubles as a flag for finding out whether we're busy reloading. Clear flag!
    [mutex unlock];
    
    #else
    @synchronized(self)
    {
        // Let user see new stuff:
        [fileList release];
        fileList = finalItems;  // Swap in updated list.
        finalItems = nil;
        
        [fileListView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone: NO];
        
        // Clean up:
        [self stopProgress];
    
        //NSLog(@"reload - newItems UN-FLAGGED");
        [newItems release];
        newItems = nil;     // newItems doubles as a flag for finding out whether we're busy reloading. Clear flag!
    }
    #endif
}


// -----------------------------------------------------------------------------
//  fsDataSource:
//      Return our FSItem data source.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(id)   fsDataSource
{
    return dataSource;
}


// -----------------------------------------------------------------------------
//  fileStorePath:
//      Return the path to use for our metadata store. * DEPRECATED *
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(NSString*)	fileStorePath
{
	return [folderPath stringByAppendingPathComponent: @".Filie_Store"];
}


// -----------------------------------------------------------------------------
//  displayName:
//      Return the display name for this viewer, to be used in GUI lists etc.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(NSString*)	displayName
{
	return [[NSFileManager defaultManager] displayNameAtPath: folderPath];
}


// -----------------------------------------------------------------------------
//  searchString:
//      Return the string we're filtering by.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(NSString *) searchString
{
    return searchString; 
}

// -----------------------------------------------------------------------------
//  setSearchString:
//      Specify the string to filter by. Called by our filter field.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) setSearchString: (NSString *) theSearchString
{
    if (searchString != theSearchString)
    {
        [searchString release];
        searchString = [theSearchString retain];
        //NSLog(@"searching for: %@", searchString);
        [fileListView selectItemContainingString: searchString];
        [fileListView setNeedsDisplay: YES];
    }
}


// -----------------------------------------------------------------------------
//  itemNeedsDisplay:
//      Find the specified item and make the distributed view update it.
//
//      FIX ME! We could probably optimize this by just taking the item's
//      position directly and adding a method to UKDistributedView for updating
//      an item at a specific coordinate.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------


-(void) testItemNeedsDisplay
{
	UKFSItem *item = [[UKFSItem alloc] 
		initWithURL: [NSURL fileURLWithPath: @"/usr"] isDirectory: YES withAttributes: nil owner: self];
	
	[self loadFolderContents: nil];
	
	UKTrue([fileList containsObject: item]);
	UKIntsNotEqual([fileList count], 0);
	
	UKNotNil(fileListView);
}

-(void) itemNeedsDisplay: (UKFSItem*)item
{
    int ind = [fileList indexOfObject: item];
    [fileListView itemNeedsDisplay: ind];
}

-(void) testNumberOfItemsInDistributedView
{
	[self setURL: [NSURL fileURLWithPath: @"/"]];
	
	UKIntsNotEqual([fileList count], 0);
}

-(int)			numberOfItemsInDistributedView: (UKDistributedView*)distributedView
{
	//NSLog(@"%@ numberOfItemsInDistributedView: with return value %d", self, [fileList count]);
	
	return [fileList count];
}


// -----------------------------------------------------------------------------
//  distributedView:positionForCell:atItemIndex:
//      Delegate method called by UKDistributedView to display each item.
//      Sets up the cell and returns the item's position.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(NSPoint)		distributedView: (UKDistributedView*)distributedView
						positionForCell:(NSCell*)cell /* may be nil if the view only wants the item position. */
						atItemIndex: (int)row
{
	UKFSItem*	fsItem = [fileList objectAtIndex: row];
	NSPoint     pos = [fsItem position];
	
	//NSLog(@"%@ distributedView:positionForCell:atItemIndex:", self);
    
    if( cell )
	{
		NSString*   dNam = [fsItem displayName];
		NSImage*	icn = [fsItem icon];
        NSColor*    labelCol = [fsItem labelColor];
		
		[cell setTitle: dNam];
		[cell setImage: icn];
        [(UKFinderIconCell *)cell resetColors];
        [(UKFinderIconCell *)cell setNameColor: [labelCol colorWithAlphaComponent: 0.5]];
        if( labelCol != [NSColor whiteColor] )
            [(UKFinderIconCell *)cell setSelectionColor: labelCol];
        if( searchString && [searchString length] > 0
            && [[fsItem displayName] rangeOfString: searchString options: NSCaseInsensitiveSearch].location == NSNotFound )
            [(UKFinderIconCell *)cell setAlpha: 0.3];
        else
            [(UKFinderIconCell *)cell setAlpha: 1.0];
	}
	
    if( pos.x == -1 && pos.y == -1 )
    {
        pos = [distributedView suggestedPosition];
        [fsItem setPosition: pos];
    }
    
	return pos;
}


// -----------------------------------------------------------------------------
//  distributedView:setPosition:forItemIndex:
//      Delegate method called by UKDistributedView when an item has been moved
//      through dragging it.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void)			distributedView: (UKDistributedView*)distributedView
						setPosition: (NSPoint)pos
						forItemIndex: (int)row
{
    UKFSItem*	fsItem = [fileList objectAtIndex: row];
    [fsItem setPosition: pos];
}


// -----------------------------------------------------------------------------
//  distributedView:setObjectValue:forItemIndex:
//      Delegate method called by UKDistributedView when an item has been
//      inline-edited. This is where we rename the item.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void)			distributedView: (UKDistributedView*)distributedView
						setObjectValue: (id)val
						forItemIndex: (int)row
{
	//NSLog(@"%@ distributedView:setObjectValue:forItemIndex:", self);
  
    if( !newItems )
    {
        UKFSItem*	fsItem = nil;
        
        #ifdef __ETOILE__
        NSRecursiveLock *mutex1 = [locks objectForKey: @"lockDistributedView:setObjectValue:forItemIndex:1"];
        
        if ( !mutex1 )
        {
            mutex1 = [[[NSRecursiveLock alloc] init] autorelease];
            [locks setObject: mutex1 forKey: @"lockDistributedView:setObjectValue:forItemIndex:1"];  
        }
        
        [mutex1 lock];
        //NSLog(@"setObjectValue - newItems FLAGGED");
        newItems = [[NSMutableArray alloc] init];
        fsItem = [[[fileList objectAtIndex: row] retain] autorelease];
        [mutex1 unlock];
        
        #else
        @synchronized(self)
        {
            //NSLog(@"setObjectValue - newItems FLAGGED");
            newItems = [[NSMutableArray alloc] init];
            fsItem = [[[fileList objectAtIndex: row] retain] autorelease];
        }
        #endif
        
        [fsItem setName: val];
        usleep(1000);   // Give kqueue thread a chance to ignore update notification.
        
        #ifdef __ETOILE__
        NSRecursiveLock *mutex2 = [locks objectForKey: @"lockDistributedView:setObjectValue:forItemIndex:2"];
        
        if ( !mutex2 )
        {
            mutex2 = [[[NSRecursiveLock alloc] init] autorelease];
            [locks setObject: mutex2 forKey: @"lockDistributedView:setObjectValue:forItemIndex:2"];  
        }
        
        [mutex2 lock];
        //NSLog(@"setObjectValue - newItems UN-FLAGGED");
        [newItems release];
        newItems = nil;
        [mutex2 unlock];
        
        #else
        @synchronized(self)
        {
            //NSLog(@"setObjectValue - newItems UN-FLAGGED");
            [newItems release];
            newItems = nil;
        }
        #endif
    }
    else
        NSBeep();
}


// -----------------------------------------------------------------------------
//  distributedView:cellDoubleClickedAtItemIndex:
//      Delegate method called by UKDistributedView when an item has been
//      double-clicked. This is where we open all selected items.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) distributedView: (UKDistributedView*)distributedView cellDoubleClickedAtItemIndex: (int)item
{
    NSEnumerator*   enny = [fileListView selectedItemEnumerator];
    NSNumber*       index;
	
	//NSLog(@"%@ distributedView:cellDoubleClickedAtItemIndex:", self);
	
    /*
    while( (index = [enny nextObject]) )
        [[fileList objectAtIndex: [index intValue]] openViewer: self];
	 */
}


// -----------------------------------------------------------------------------
//  distributedView:toolTipForItemAtIndex:
//      Delegate method called by UKDistributedView when it needs to know what
//      tool tip ("help tag") to display for an item. This is where we display
//      the full, un-shortened name.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(NSString*)    distributedView: (UKDistributedView*)distributedView toolTipForItemAtIndex: (int)row
{
    return [[fileList objectAtIndex: row] displayName];
}


// -----------------------------------------------------------------------------
//  distributedView:itemIndexForString:options:
//      Delegate method called by UKDistributedView when it needs to know what
//      item to select during type-ahead selection. Also called by the filter
//      field to select the first matching item.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(int)  distributedView: (UKDistributedView*)distributedView itemIndexForString: (NSString*)str options: (unsigned)opts
{
    NSEnumerator*   enny = [fileList objectEnumerator];
    UKFSItem*       fsItem;
    UKFSItem*       foundItem = nil;
    int             foundItemIndex = -1, x = -1;
    
    while( (fsItem = [enny nextObject]) )
    {
        x++;
        
        if( opts == (NSAnchoredSearch | NSCaseInsensitiveSearch) )
        {
            NSComparisonResult comp = [[fsItem displayName] compare: str options: NSCaseInsensitiveSearch];
            if( comp == NSOrderedDescending || comp == NSOrderedSame )
            {
                if( foundItem != nil )
                {
                    comp = [[fsItem displayName] compare: [foundItem displayName] options: NSCaseInsensitiveSearch];
                    if( comp == NSOrderedAscending )
                    {
                        foundItem = fsItem;
                        foundItemIndex = x;
                    }
                }
                else
                {
                    foundItem = fsItem;
                    foundItemIndex = x;
                }
            }
        }
        else
        {
            NSRange found = [[fsItem displayName] rangeOfString:str options: opts];
            if( found.location != NSNotFound )
            {
                foundItemIndex = x;
                
                if( [self itemIsVisible: fsItem] )  // Visible? Good enough.
                    return foundItemIndex;
                // Not visible? Keep looking for another, possibly visible item so we avoid scrolling stuff away the user wants.
            }
        }
    }
    
    return foundItemIndex;
}


// -----------------------------------------------------------------------------
//  distributedView:writeItems:toPasteboard:
//      Delegate method called by UKDistributedView when a drag has been
//      started. UKDV automatically adds the item positions to the drag and
//      generates a nice drag image.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(BOOL)				distributedView: (UKDistributedView*)dv writeItems:(NSArray*)indexes
						toPasteboard: (NSPasteboard*)pboard
{
    NSEnumerator*   enny = [indexes objectEnumerator];
    NSNumber*       index;
    NSMutableArray* filenames = [NSMutableArray array];
    
    while( (index = [enny nextObject]) )
    {
        UKFSItem*   item = [fileList objectAtIndex: [index intValue]];
        NSString*   thePath = [item path];
        [filenames addObject: thePath];
    }
    
    [pboard declareTypes: [NSArray arrayWithObject: NSFilenamesPboardType] owner: nil];
    [pboard setPropertyList: filenames forType: NSFilenamesPboardType];
    
    return YES;
}


// -----------------------------------------------------------------------------
//  distributedView:validateDrop:proposedItem:
//      Delegate method called by UKDistributedView when a drag has entered
//      this view. It will propose to have the drop occur on the item that
//      the mouse is over, or -1 to mean inside the view itself.
//
//      This returns NSDragOperationNone to say it doesn't accept the drag,
//      otherwise says how it will accept the drag.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(NSDragOperation)  distributedView: (UKDistributedView*)dv validateDrop: (id <NSDraggingInfo>)info
						proposedItem: (int*)row
{
    if( (*row) != -1 )
    {
        UKFSItem*       target = [fileList objectAtIndex: *row];
        
        if( ![target isDirectory] )     // We can only drop into folders.
            *row = -1;                  // Drop in container if it's on top of a file.
        else
        {
            NSPasteboard*   pb = [info draggingPasteboard];
            NSArray*        files = [pb propertyListForType: NSFilenamesPboardType];
            
            if( [files containsObject: [target path]] ) // Attempt to drop an item on itself? User is probably moving it just a little.
                *row = -1;      // Make the target the container.
        }
    }
    
    return NSDragOperationEvery;
}


// -----------------------------------------------------------------------------
//  itemForPath:
//      Return the item at the specified path in this viewer, or NIL if this
//      viewer doesn't contain the specified item.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) testItemForPath
{
	UKFSItem *item;
	
	UKNotNil(fileList);
	
	[self setURL: [NSURL fileURLWithPath: @"/usr"]];
	
	[self loadFolderContents: nil];
	
	item = [self itemForPath: @"/usr/bin"];
	UKNotNil(item);
	UKTrue([[item path] isEqualToString: @"/usr/bin"]);
}

-(UKFSItem*)    itemForPath: (NSString*)path
{
    NSEnumerator*   enny = [fileList objectEnumerator];
    UKFSItem*       item = nil;
    
    while( (item = [enny nextObject]) )
    {
        if( [[item path] isEqualToString: path] )
            return item;
    }
    
    return nil;
}


// -----------------------------------------------------------------------------
//  distributedView:acceptDrop:onItem:
//      The user has dropped something on our icon view. Actually drop the
//      items now and add them to our file list (i.e. move/copy the files).
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(BOOL)  distributedView: (UKDistributedView*)dv acceptDrop: (id <NSDraggingInfo>)info
						onItem: (int)row
{
    NSPasteboard*   pb = [info draggingPasteboard];
    NSPoint         pos = [info draggedImageLocation];
    NSArray*        positions = [dv positionsOfItemsOnPasteboard: pb forImagePosition: pos];
    NSArray*        files = [pb propertyListForType: NSFilenamesPboardType];
    NSEnumerator*   enny = [files objectEnumerator];
    NSEnumerator*   posEnny = [positions objectEnumerator];
    NSString*       currFile;
    NSValue*        currPos;
    
    // Loop over the files dropped:
    while( (currFile = [enny nextObject]) )
    {
        // Determine the position this item was dropped at:
        currPos = [posEnny nextObject];
        if( currPos )
            pos = [currPos pointValue];
        else
            pos = [dv suggestedPosition];
        UKFSItem*   item = [self itemForPath: currFile];
        
        if( !item ) // It wasn't an item in here that was just moved?
        {
            NSString*   newPath = [folderPath stringByAppendingPathComponent: [currFile lastPathComponent]];
            BOOL        success = NO;
            
            // Copy/Link/Move the object over and add an item for it:
            
            if( [info draggingSourceOperationMask] == NSDragOperationCopy )
                success = [[NSFileManager defaultManager] copyPath: currFile
                                                            toPath: newPath
                                                            handler: nil];
            else if( [info draggingSourceOperationMask] == NSDragOperationLink )
                success = [[NSFileManager defaultManager] linkPath: currFile
                                                            toPath: newPath
                                                            handler: nil];
            else
                success = [[NSFileManager defaultManager] movePath: currFile
                                                            toPath: newPath
                                                            handler: nil];
            if( success )
            {
                NSDictionary* attrs = [[NSFileManager defaultManager] fileAttributesAtPath: newPath traverseLink: YES];
                item = [[[UKFSItem alloc] initWithPath: newPath isDirectory: [[attrs objectForKey: NSFileType] isEqualToString: NSFileTypeDirectory]
                                            withAttributes: attrs owner: self] autorelease];
                [fileList addObject: item];
            }
            
        }
        
        // Place the (possibly new) item at its new position:
        [item setPosition: pos];
    }
    
    [fileListView reloadData];  // Update the icon view.
    
    return YES;     // We're accepting this drop.
}


// -----------------------------------------------------------------------------
//  distributedView:dragEndedWithOperation:
//      The user has dropped something from our icon view somewhere else.
//      If it was dropped on the trash, we re-route this to mean "delete".
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void)		distributedView: (UKDistributedView*)dv dragEndedWithOperation: (NSDragOperation)operation
{
    if( operation == NSDragOperationDelete )
        [self delete: nil];   // Just pretend somebody had chosen the "clear" menu item.
}


// -----------------------------------------------------------------------------
//  delete:
//      Someone pressed the "delete" key or dropped some items on the trash.
//      Move them to the trash using the appropriate NSWorkspace method.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) delete: (id)sender
{
    NSEnumerator*   enny = [fileListView selectedItemEnumerator];
    NSNumber*       itemNb = nil;
    NSMutableArray* arr = [NSMutableArray array];
    int             tag = 0;
    
    while( (itemNb = [enny nextObject]) )
        [arr addObject: [[[fileList objectAtIndex: [itemNb intValue]] path] lastPathComponent]];
    
    [[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
                                    source: folderPath destination: @""
                                    files: arr tag: &tag];
}


// -----------------------------------------------------------------------------
//  distributedView:draggingSourceOperationMaskForLocal:
//      The user is trying to drag something out of this view. Happily say yes.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(NSDragOperation)  distributedView: (UKDistributedView*)dv draggingSourceOperationMaskForLocal: (BOOL)local
{
    return NSDragOperationEvery;
}


// -----------------------------------------------------------------------------
//  createNewFolder:
//      The "new folder" menu item.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(IBAction)     createNewFolder: (id)sender
{
    NSString*   path = [folderPath stringByAppendingPathComponent: NSLocalizedString(@"New Folder",@"")];
    path = [[NSFileManager defaultManager] uniqueFileName: path];
    [[NSFileManager defaultManager] createDirectoryAtPath: path attributes: [NSDictionary dictionary]];
}


// -----------------------------------------------------------------------------
//  changeItemSize:
//      Menu item action for the entries in the "icon size" popup menu.
//      This determines what size to switch to based on the menu item's tag and
//      then "scales" the item positions so it behaves as if the view was just
//      zoomed.
//
//      FIX ME! This could potentially cause a loss of precision. We may want
//      to instead keep the positions for 128x128 icon sizes in the metadata
//      store and multiply them by a factor for the smaller sizes upon display.
//      That way, changing the icon size will *never* modify item positions.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(IBAction)     changeItemSize: (NSMenuItem*)sender
{
    int     idx = [sender tag];
    int     sizes[] = { ICON_SIZES };
    NSSize  oldSize = [fileListView cellSize];
    
    iconSize = NSMakeSize(sizes[idx], sizes[idx]);
    [self recalcCellSize];
    
    // Now "scale" item positions:
    float hscale = [fileListView cellSize].width / oldSize.width;
    float vscale = [fileListView cellSize].height / oldSize.height;
    
    NSEnumerator*   enny = [fileList objectEnumerator];
    UKFSItem*       item;
    
    while( (item = [enny nextObject]) )
    {
        NSPoint pos = [item position];
        
        pos.x *= hscale;
        pos.y *= vscale;
        
        [item setPosition: pos];
    }
    
    [fileListView reloadData];
}


// -----------------------------------------------------------------------------
//  changeShowIconPreview:
//      Button action for the "show icon preview" button. Toggles the option
//      and causes a repaint.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(IBAction)     changeShowIconPreview: (NSButton*)sender
{
    showIconPreviews = !showIconPreviews;
    //[previewButton setState: showIconPreviews];
    [fileListView setNeedsDisplay: YES];
}


// -----------------------------------------------------------------------------
//  UKRearrangeStringCompareFunc:
//      Function used for sorting items by name. Case-insensitive. For items
//      that come up the same, this uses the file's actual name as a secondary
//      criterion.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

int UKRearrangeStringCompareFunc( id a, id b, void* context )
{
    NSString*   ap = [a valueForKeyPath: (NSString*)context];
    NSString*   bp = [b valueForKeyPath: (NSString*)context];
    
    if( !ap ) ap = @"";
    if( !bp ) bp = @"";
    
    int comp = [ap localizedCaseInsensitiveCompare: bp];
    if( comp == NSOrderedSame )
    {
        ap = (NSString *)[a name];
        bp = (NSString *)[b name];
        comp = [ap localizedCaseInsensitiveCompare: bp];
    }
    
    return comp;
}


// -----------------------------------------------------------------------------
//  UKRearrangeCompareFunc:
//      Function used for sorting items. For items that come up the same, this
//      uses the display name as a secondary criterion by calling
//      UKRearrangeStringCompareFunc, which in turn uses the actual name as a
//      final fallback that should ensure a stable sort order.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

int UKRearrangeCompareFunc( id a, id b, void* context )
{
    NSString*   ap = [a valueForKeyPath: (NSString*)context];
    NSString*   bp = [b valueForKeyPath: (NSString*)context];
    
    if( !ap ) { ap = @""; NSLog(@"."); }
    if( !bp ) { bp = @""; NSLog(@"."); }
    
    int comp = [ap compare: bp];
    if( comp == NSOrderedSame )
        comp = UKRearrangeStringCompareFunc( a, b, @"displayName" );
    return comp;
}


// -----------------------------------------------------------------------------
//  rearrangeItemsBy:
//      Menu item action for sorting items in the window. Uses the tag of the
//      menu item to determine the actual criterion. Calls rearrangeItemsByTag:
//      to do the actual work.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(IBAction)     rearrangeItemsBy: (NSMenuItem*)sender
{
    [self rearrangeItemsByTag: [sender tag]];
}

// -----------------------------------------------------------------------------
//  keepArrangedBy:
//      Menu item action for keeping items sorted in the window. Uses the tag of
//      the menu item to determine the actual criterion. Changes the
//      keepArrangedMode accordingly.
//      Calls rearrangeItemsByTag: to do the actual work.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(IBAction)    keepArrangedBy: (NSMenuItem*)sender
{
    keepArrangedMode = [sender tag];
    if( keepArrangedMode >= 0 )
        [self rearrangeItemsByTag: keepArrangedMode];
}


// -----------------------------------------------------------------------------
//  rearrangeItemsByTag:
//      Main sorting bottleneck. This actually repositions the items according
//      to the specified sort criterion.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void)         rearrangeItemsByTag: (int)idx
{
    NSString*       keys[6] = { @"displayName", @"attributes.NSFileModificationDate",
                                @"attributes.NSFileCreationDate", @"attributes.NSFileSize",
                                @"attributes.NSFileType", @"attributes.UKLabelNumber" };
    NSString*       sortKey = keys[ idx ];
    NSArray*        sortedList = [fileList sortedArrayUsingFunction: ((idx == 0) ? UKRearrangeStringCompareFunc : UKRearrangeCompareFunc)
                                    context: sortKey];
    NSEnumerator*   enny = [sortedList objectEnumerator];
    UKFSItem*       item = nil;
    int             x = 0;
    
    while( (item = [enny nextObject]) )
    {
        NSPoint pos = [fileListView itemPositionBasedOnItemIndex: x];
        [item setPosition: pos];
        
        x++;
    }
    
    [fileListView performSelectorOnMainThread: @selector(reloadData) withObject: nil waitUntilDone: NO];
}


// -----------------------------------------------------------------------------
//  showInfoPanel:
//      Show info panels for the selected items.
//
//      TODO: Write a variation of this that creates a single info panel for all
//      selected items and hook that up to Command-Shift-I or so.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(IBAction) showInfoPanel: (id)sender
{
    NSEnumerator*   enny = [fileListView selectedItemEnumerator];
    NSNumber*       index;
    
    while( (index = [enny nextObject]) )
        [[fileList objectAtIndex: [index intValue]] openInfoPanel: self];
}


// -----------------------------------------------------------------------------
//  validateMenuItem:
//      Make sure all menu and popup menu items are correctly enabled and
//      checked.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(BOOL) validateMenuItem: (id<NSMenuItem>)item
{
    if( [item action] == @selector(openDocument:)
        || [item action] == @selector(showInfoPanel:)
        || [item action] == @selector(delete:) )
        return [fileListView selectedItem] != -1;
    else if( [item action] == @selector(changeItemSize:) )
    {
        int     sizes[] = { ICON_SIZES };
        
        [item setState: (iconSize.width == sizes[ [item tag] ])];
        
        return YES;
    }
    else if( [item action] == @selector(keepArrangedBy:) )
    {
        [item setState: ([item tag] == keepArrangedMode)];
        
        return YES;
    }
    else
        return [self respondsToSelector: [item action]];
}


// -----------------------------------------------------------------------------
//  openDocument:
//      Action for the "open..." menu item.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) openDocument: (id)sender
{
    [self openSelectedFiles: sender];
}


// -----------------------------------------------------------------------------
//  openSelectedFiles:
//      Open the selected items. This simply fakes a double-click on one of the
//      selected items, which implicitly opens all others as well, after all.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) openSelectedFiles: (id)sender
{
	[self distributedView: fileListView cellDoubleClickedAtItemIndex: [fileListView selectedItem]];
}


// -----------------------------------------------------------------------------
//  saveFileStore:
//      Save our metadata to the store. This saves item positions, viewer
//      dimensions etc.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) testSaveFileStore
{
	NSEnumerator*           enny = [fileList objectEnumerator];
    UKFSItem*               item;
	int i = 0;
	NSPoint prevPos = NSZeroPoint;
	
	UKNotNil(filieStore);
	UKNotNil([fileListView window]);
	
    while( (item = [enny nextObject]) )
    {
        NSPoint pos = [item position];
        NSMutableDictionary*    info = [filieStore newDictionaryForFile: [item name]];
        
		if (i > 3)
		{
			UKTrue(pos.x == prevPos.x  && pos.y == prevPos.y);
			break;
		}
		i++;
    }
}

-(void) saveFileStore
{
    [self startProgress: @"Saving item positions..." recordTiming: YES];

    NSEnumerator*           enny = [fileList objectEnumerator];
    UKFSItem*               item;
    
    // Save viewer-wide prefs:
    [filieStore setObject: [NSNumber numberWithBool: [fileListView snapToGrid]] forKey: @"snapToGrid"];
    [filieStore setObject: [NSValue valueWithSize: iconSize] forKey: @"iconSize"];
    [filieStore setObject: [NSNumber numberWithBool: showIconPreviews] forKey: @"showIconPreviews"];
    #ifndef __ETOILE__
    [filieStore setObject: [filterField stringValue] forKey: @"filterString"];
    #endif
    [filieStore setObject: [NSNumber numberWithInt: keepArrangedMode] forKey: @"keepArrangedMode"];
    [filieStore setDisplayRect: [[fileListView window] frame]];

    // Save file icon positions to file store:
    while( (item = [enny nextObject]) )
    {
        NSPoint pos = [item position];
        NSMutableDictionary*    info = [filieStore newDictionaryForFile: [item name]];
        [info setObject: [NSValue valueWithPoint: pos] forKey: @"position"];
    }
        
    BOOL    ours = NO;
    
    #ifdef __ETOILE__
    NSRecursiveLock *mutex = [locks objectForKey: @"lockSaveFileStore1"];
    
    if ( !mutex )
    {
        mutex = [[[NSRecursiveLock alloc] init] autorelease];
        [locks setObject: mutex forKey: @"lockSaveFileStore1"];  
    }
    
    [mutex lock];
    // Save changed file store:
    if( !newItems )
    {
        newItems = [[NSMutableArray alloc] init];    // Make sure this save doesn't cause an unnecessary reload of the view.
        ours = YES;
        //NSLog(@"saveFileStore - newItems FLAGGED");
    }
    [mutex unlock];
    
    #else
    @synchronized(self)
    {
        // Save changed file store:
        if( !newItems )
        {
            newItems = [[NSMutableArray alloc] init];    // Make sure this save doesn't cause an unnecessary reload of the view.
            ours = YES;
            //NSLog(@"saveFileStore - newItems FLAGGED");
        }
    }
    #endif
        
    [filieStore synchronize];
        
    if( ours )
    {
        #ifdef __ETOILE__
        NSRecursiveLock *mutex = [locks objectForKey: @"lockSaveFileStore2"];
        
        if ( !mutex )
        {
            mutex = [[[NSRecursiveLock alloc] init] autorelease];
            [locks setObject: mutex forKey: @"lockSaveFileStore2"];  
        }
        
        [mutex lock];
        [newItems release];
        newItems = nil;
        //NSLog(@"saveFileStore - newItems UN-FLAGGED");
        [mutex unlock];
        
        #else
        @synchronized(self)
        {
            [newItems release];
            newItems = nil;
            //NSLog(@"saveFileStore - newItems UN-FLAGGED");
        }
        #endif
    }
    
    [self stopProgress];
}


// -----------------------------------------------------------------------------
//  retain:
//      Used during debugging. Can probably be deleted.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(id)   retain
{
    return [super retain];
}


// -----------------------------------------------------------------------------
//  windowWillClose:
//      User closed our window. Save and perform suicide.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) windowWillClose: (NSNotification*)notification
{
	[fileListView setDelegate: nil];
	
	#ifndef __ETOILE__
	[kqueue setDelegate: nil];
	#endif
    
    [self saveFileStore];
}


// -----------------------------------------------------------------------------
//  performKeepArranged:
//      This is called whenever items can change and will enforce the sort order
//      of the "keep arranged" submenu, if the user requested sorting.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) performKeepArranged
{
    NSRect  box = [fileListView frame];
    box.size.width = [[fileListView enclosingScrollView] documentVisibleRect].size.width;
    [fileListView setFrame: box];
    [self rearrangeItemsByTag: keepArrangedMode];
}


// -----------------------------------------------------------------------------
//  windowDidResize:
//      Our window has resized. Enforce keep arranged.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) windowDidResize: (NSNotification*)notification
{
    if( keepArrangedMode >= 0 )
        [coalescer performKeepArranged];
}


// -----------------------------------------------------------------------------
//  path:
//      Return the folder path for which this viewer is responsible.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(NSString*)	path
{
	return [[folderPath retain] autorelease];
}


// -----------------------------------------------------------------------------
//  isEqual:
//      Compare this item to another. For use with collection classes like
//      NSArray, but possibly broken.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(BOOL)			isEqual: (NSObject<UKFSItemView>*)other
{
    BOOL        result = NO;
    
	if( [other isKindOfClass: [NSString class]] )
		result = [folderPath isEqualToString: (NSString*) other];
	else if( [other conformsToProtocol: @protocol(UKFSItemView)] )
		result = [folderPath isEqualToString: [other path]];
    else
        result = [folderPath isEqualToString: [other description]];

    return result;
}


// -----------------------------------------------------------------------------
//  hash:
//      hash for comparing this item to another. For use with collection classes
//      like NSArray, but possibly broken.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(unsigned)     hash
{
    return [folderPath hash];
}


// -----------------------------------------------------------------------------
//  kqueue:receivedNotification:forFile:
//      Our kqueue that watches for file change notifications has noticed a
//      change. Reload our folder's contents.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------
#ifndef __ETOILE__
-(void) kqueue: (UKKQueue*)kq receivedNotification: (NSString*)nm forFile: (NSString*)fpath
{
	if( !newItems )
		[NSThread detachNewThreadSelector:@selector(loadFolderContents:) toTarget:self withObject: nil];
}
#endif

// -----------------------------------------------------------------------------
//  windowWillUseStandardFrame:defaultFrame:
//      Implement smarter zooming of our window.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(NSRect)   windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)newFrame
{
    return [fileListView windowFrameForBestSize];
}


// -----------------------------------------------------------------------------
//  itemIsVisible:
//      Return whether one of our FSItems is actually visible. Used to decide
//      which items to load first.
//
//      TODO: Could probably be optimized to get the position directly from the
//      item and then call a method on UKDistributedView that returns whether
//      an item is visible based on its position.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(BOOL)                 itemIsVisible: (UKFSItem*)item
{
    int ind = [fileList indexOfObject: item];
    return [fileListView itemIsVisible: ind];
}


// -----------------------------------------------------------------------------
//  showIconPreview:
//      Return whether we want our icons to contain previews. Called by our
//      FSItems when they load their final icon.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(BOOL)                 showIconPreview
{
    return showIconPreviews;
}


// -----------------------------------------------------------------------------
//  iconSizeForItem:
//      Return what size we want our icons to be. Called by our FSItems when
//      they load their final icons.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void) testIconSizeForItem
{
	//UKFSItem *item = [[UKFSItem alloc] initWithURL: [NSURL fileURLWIth@"~/"];
}

-(NSSize)               iconSizeForItem: (UKFSItem*)item
{
    return iconSize;
}


// -----------------------------------------------------------------------------
//  controlTextDidChange:
//      Sent by our filter search field when it is modified. Triggers a new
//      search.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(void)    controlTextDidChange:(NSNotification *)notification
{
    [coalescer performSelector: @selector(setSearchString:) withObject: [[notification object] stringValue]];
}


// -----------------------------------------------------------------------------
//  description:
//      Makes our item show up a tad prettier in NSLog() statements.
//
//  REVISIONS:
//      2004-12-22  UK  Documented.
// -----------------------------------------------------------------------------

-(NSString*)    description
{
    return [NSString stringWithFormat: @"UKFolderIconViewController { path = \"%@\" }", folderPath];
}

@end
