
/*
	Copyright 1993  Jeremy Slade.

	You are free to use all or any parts of the Locus project
	however you wish, just give credit where credit is due.
	The author (Jeremy Slade) shall not be held responsible
	for any damages that result out of use or misuse of any
	part of this project.

*/

/*

	Project: Locus
	
	Class: GroupBrowser
	
	Description: See GroupBrowser.h

	Original Author: Jeremy Slade
	
	Revision History:
		Created
			V.101	JSG Wed Feb  3 23:53:15 GMT-0700 1993
			
*/


#import "GroupBrowser.h"

#import "FolderController.h"
#import "Globals.h"
#import "GroupBrowserMatrix.h"
#import "ItemCell.h"

#import <objc/List.h>
#import <string.h>


@interface GroupBrowser ( Private )
- _tellDelegateSelChanged;
@end


@implementation GroupBrowser


// -------------------------------------------------------------------------
//   Creating, initializing methods
// -------------------------------------------------------------------------


+ initialize
{
	[self setVersion:GroupBrowser_VERSION];
	return ( self );
}



- initFrame:(const NXRect *)frameRect
{
	NXRect rect;

	[super initFrame:frameRect];
	
	NXSetRect ( &rect, 0, 0, 0, 0 );
	[ScrollView getContentSize:&rect.size forFrameSize:&frameRect->size
		horizScroller:NO vertScroller:YES borderType:NX_BEZEL];
		
	matrix = [[GroupBrowserMatrix alloc] initFrame:&rect
		mode:NX_LISTMODE
		cellClass:[ItemCell class]
		numRows:0
		numCols:1];
	rect.size.height = rect.size.width = 1.0;
	[matrix setIntercell:&rect.size];
	[matrix setAutoscroll:YES];
	[[[matrix setTarget:self]
		setAction:@selector(doAction:)]
		setDoubleAction:@selector(doDoubleAction:)];

	[self allowMultiSel:YES];
	[[self setDocView:matrix] free];
	[self setBorderType:NX_BEZEL];
	[[self setDynamicScrolling:YES] setDisplayOnScroll:YES];
	[self setVertScrollerRequired:YES];
	
	[self setAutoresizeSubviews:YES];
	
	[self registerForDraggedTypes:dragInTypes count:DRAGINTYPES];

	lastTime = 0;
	return ( self );
}



- free
{
	return ( [super free] );
}



// -------------------------------------------------------------------------
//   Setting the Group to be displayed
// -------------------------------------------------------------------------


- showGroup:group
{
	id oldList;

//if ( DEBUGGING ) printf ( "GroupBrowser: loading browser...\n" );

	oldList = [matrix setGroup:group];
	if ( ![oldList isKindOf:[Group class]] ) [oldList free];

	// Set the group to be our target
	[self setTarget:group];
	
	[self display];
//if ( DEBUGGING ) printf ( "	Done.\n" );
	return ( self );
}



// -------------------------------------------------------------------------
//   Drawing
// -------------------------------------------------------------------------


- resizeSubviews:(const NXSize *)oldSize
/*
	Retile the browser
*/
{
	NXRect rect;
	NXSize size;
	
	[super resizeSubviews:oldSize];
	
	[self getDocVisibleRect:&rect];
	[matrix getCellSize:&size];
	size.width = rect.size.width;
	[[matrix setCellSize:&size] sizeToCells];
	
	[matrix getFrame:&rect];
	[self getContentSize:&size];
	if ( rect.size.height < size.height ) {
		rect.size.height = size.height;
		[matrix setFrame:&rect];
	}
	
	return ( self );
}



// -------------------------------------------------------------------------
//   Setting the delegate
// -------------------------------------------------------------------------


- setDelegate:anObject
{
	id old = delegate;
	delegate = anObject;
	return ( old );
}



- delegate
{
	return ( delegate );
}



// -------------------------------------------------------------------------
//   Setting behavior
// -------------------------------------------------------------------------


- (BOOL)acceptsFirstMouse
{
	return ( YES );
}



- allowMultiSel:(BOOL)flag
{
	allowMultiSel = flag;
	if ( allowMultiSel == YES ) [matrix setMode:NX_LISTMODE];
		else [matrix setMode:NX_RADIOMODE];
	return ( self );
}



- (BOOL)doesAllowMultiSel
{
	return ( allowMultiSel );
}



- setEnabled:(BOOL)flag
{
	[matrix setEnabled:flag];
	return ( self );
}



- (BOOL)isEnabled
{
	return ( [matrix isEnabled] );
}



// -------------------------------------------------------------------------
//   Setting target and action
// -------------------------------------------------------------------------


- setTarget:anObject
{
	target = anObject;
	return ( self );
}



- setAction:(SEL)selector
{
	action = selector;
	return ( self );
}



- setDoubleAction:(SEL)selector
{
	doubleAction = selector;
	return ( self );
}



- target
{
	return ( target );
}



- (SEL)action
{
	return ( action );
}



- (SEL)doubleAction
{
	return ( doubleAction );
}



- doAction:sender
{
	[matrix determineSelectionCount];
	return ( [target perform:action with:self] );
}



- doDoubleAction:sender
{
	[matrix determineSelectionCount];
	return ( [target perform:doubleAction with:self] );
}



// -------------------------------------------------------------------------
//   Making selections
// -------------------------------------------------------------------------


- selectAll:sender
{
	[matrix selectAll:self];
	return ( self );
}


- selectUp:sender
/*
	Move the selection up one item, or start at the bottom if nothing (or more than 1) is already selected.
*/
{
	int row, col;
	if ( [matrix selectionCount] ) {
		[matrix getRow:&row andCol:&col ofCell:[matrix selectedCell]];
		if ( row ) row--;
		[self selectRow:row];
	} else
		[self selectRow:[[matrix cellList] count]-1]; // Select last cell
	return ( self );
}


- selectDown:sender
/*
	Move the selection down one item, or start at the top if nothing (or more than 1) is already selected.
*/
{
	int row, col;
	if ( [matrix selectionCount] ) {
		[matrix getRow:&row andCol:&col ofCell:[matrix selectedCell]];
		if ( row < [matrix cellCount] ) row++;
		[self selectRow:row];
	} else
		[self selectRow:0]; // Select first cell
	return ( self );
}


- selectedCell
{
	return ( [matrix selectedCell] );
}


- selection
/*
	This method returns the currently selected item in the matrix if there is only one selected, other wise it creates a ItemList object, fills it with the selected items, and returns it.  The caller does not need to free the returned List because it is kept as a static internal to this method.
*/
{
	return ( [matrix selection] );
}


- selectionList
/*
	This method is like the -selection method, but it always returns an ItemList object., that must be freed by the caller
*/
{
	return ( [matrix selectionList] );
}


- (int)selectionCount
/*
	Returns the number of items curerntly selected
*/
{
	return ( [matrix selectionCount] );
}


const char *upStr ( const char *str )
/* Convert str to uppercase */
{
	static char s[MAXPATHLEN+1];
	const char *src;
	char *dest;
	
	src = str;
	dest = s;
	do {
		*dest++ = NXToUpper ( *src++ );
	} while ( *src );
	*dest = '\0';
	return ( s );
}


#define KEY_TRAP_INTERVAL	120
- selectKey:(unsigned int)charCode time:(long)time
/*
	Searches the cells by filename, looking for the first cell beginning with/ the selected key.  Search is not case sensitive.  If a cell is found, it is selected.
*/
{
	int count, row;
	id list = [matrix cellList];
	id cell;
	const char *filename;
	char match[21];
	
	/*
		Determime if selectFile needs to be reset
	*/
	if ( time-lastTime > KEY_TRAP_INTERVAL ) {
		selectKey = 0;
		strcpy ( selectFile, "" );
	} 
		
	lastTime = time;

	// Convert charCode to upper case for case-insensitive matching
	charCode = NXToUpper ( charCode );
	
	// First search selected cell for match
	if (  selectKey  ) {
		filename = upStr ( [[matrix selectedCell] filename] );
		if ( charCode == selectFile[selectKey] ) {
			selectKey++;
			return ( self );
		}
	}
	
	// Search [matrix cellList] for matching cell
	row = selectKey ? [matrix selectedRow] : 0;
	for ( count=[list count]; row<count; row++ ) {
		cell = [list objectAt:row];
		filename = upStr ( [cell filename] );
		sprintf ( match, "%.*s%c", selectKey, selectFile, (char)charCode );
		if ( selectKey && !strncmp ( match, filename, selectKey+1 ) ) {

			// Found a match
			[matrix selectCell:cell];
			[matrix scrollCellToVisible:row :0];
			selectKey++;
			strcpy ( selectFile, filename );
			
			[self _tellDelegateSelChanged];
			return ( self );
		}
		if ( charCode == filename[0] ) {
			
			// Found a match
			[matrix selectCell:cell];
			[matrix scrollCellToVisible:row :0];
			selectKey = 1;
			strncpy ( selectFile, filename, 10 );
				
			[self _tellDelegateSelChanged];
			return ( self );
		}
	}
		
	// No match was found
	NXBeep ();
	return ( self );
}				


- selectRow:(unsigned int)row
{
	[matrix selectCellAt:row :0];
	[matrix scrollCellToVisible:row :0];
	[self _tellDelegateSelChanged];
	return ( self );
}


- scrollRowToVisible:(unsigned int)row
{
	[matrix scrollCellToVisible:row :0];
	return ( self );
}


- scrollToSelection
{
	[matrix scrollCellToVisible:[matrix selectedRow] :0];
	return ( self );
}



// -------------------------------------------------------------------------
//   NXDraggingDestination Protocol - forward to FolderController
// -------------------------------------------------------------------------


- (NXDragOperation)draggingEntered:(id <NXDraggingInfo>)sender
{
	[folderController setDragDest:self];
	return ( [folderController draggingEntered:sender] );
}



- (NXDragOperation)draggingUpdated:(id <NXDraggingInfo>)sender
{
	return ( [folderController draggingUpdated:sender] );
}



- draggingExited:(id <NXDraggingInfo>)sender
{
	return ( [folderController draggingExited:sender] );
}



- (BOOL)prepareForDragOperation:(id <NXDraggingInfo>)sender
{
	return ( [folderController prepareForDragOperation:sender] );
}



- (BOOL)performDragOperation:(id <NXDraggingInfo>)sender
{
	return ( [folderController performDragOperation:sender] );
}



- concludeDragOperation:(id <NXDraggingInfo>)sender
{
	return ( [folderController concludeDragOperation:sender] );
}


@end


@implementation GroupBrowser ( Private )

- _tellDelegateSelChanged
{
	if ( [delegate respondsTo:@selector(browserSelectionChanged:)] )
		[delegate browserSelectionChanged:self];
	return ( self );
}


@end


