/*

File IKList.m

Release 1.2, 7 June 1994
Copyright (C) 1994 by H. Scott Roy

This code is part of IconKit, a general toolbox for drag-and-drop applications.  IconKit is free for noncommercial use, but costs money for a commercial license.  You should have received a copy of the license agreement with this file.  If not, a copy of the license and the complete source of IconKit can be obtained from the author:

		H. Scott Roy
		2573 Stowe Ct.
		Northbrook, IL  60062-8103
		iconkit@cs.stanford.edu

For your editing convenience, this file is best viewed using an editor that automatically wraps long lines, in a fixed point font at 80 columns, with tabs every 4 spaces.

*/


/* ========================================================================== */


/*

IKList specializes the list class in two ways.  First, it incorporates reference counting to take care of automatic memory management.  Second, it conforms to the IKIconObject protocol so that it can display itself and be dragged around the workspace.

A typical use is to represent a multiple selection in an IKBrowser.  The icon of an IKList is the hand-and-sheaf when there is more than one element in the list; otherwise it is the icon of the first element.

*/

#import <appkit/appkit.h>
#import <apps/InterfaceBuilder.h>
#import "iconkit.h"

@implementation  IKList


/* ========================================================================== */


/*

All IKLists share the same sheaf image.  They also share a common buffer for writing their names.

*/

static char			name [1000];
static NXImage		* sheaf = nil;


+ initialize
{
    if (self == [IKList  class])
	{
		IKInitIDpboardType();
		sheaf = [NXImage  findImageNamed: "Sheaf"];
	}
	
	return self;
}


- (NXImage *) getIBImage
{
	return sheaf;
}


/* ========================================================================== */


/*

Here are the methods required to initialize, copy, free.  Note that copying is broken in the List class, so all classes that inherit from IKList must implement "copyFromZone:" to work correctly.

*/


- initCount: (unsigned int) numSlots
{
	if ((self = [super  initCount: numSlots]) != nil)
				users = [[IKAnnouncer  alloc]  initOwner: self];
	
	return self;
}


- copyFromZone: (NXZone *) zone
{
	IKList
		* copy = [super  copyFromZone: zone];
	
	if (copy) copy->users = [[IKAnnouncer  alloc]  initOwner: copy];
	return copy;
}


- free
{
	IKdprintf("freeing list: %s\n", [self  name]);

	[users  announce: @selector(willFree:)];
	[[[users  usersAndListeners]  empty]  addObject: self];
	[self  makeObjectsPerform: @selector(removeUser:)   with: self];
	users = [users  free];
	
	return self = [super  free];
}


/* ========================================================================== */


/*

An IKList will forward unknown messages to the first object in the list.

*/


- forward: (SEL) message : (marg_list) argFrame
{
	id
		destination = [self  objectAt: 0];
	
	return [destination  respondsTo: message] ?
			[destination  performv: message  : argFrame]:
			[super  forward: message  : argFrame];
}


- (BOOL) respondsTo: (SEL) method
{
	return	[super  respondsTo: method] ||
			[[self  objectAt: 0]  respondsTo: method];
}


/* ========================================================================== */


/*

An IKList needs to create a new IKAnnouncer when being unarchived from a stream.

*/


- read: (NXTypedStream *) stream
{
    [super  read: stream];
	users = [[IKAnnouncer  alloc]  initOwner: self];
    
	return self;
}


/* ========================================================================== */


/*

An IKList performs automatic reference counting memory management.  When it has no more users it considers itself inaccessible and frees itself.  Users can negate this behavior by overriding the garbageCollect method.

Other than removeUser:, the IKDependency methods are all simply forwarded to the users object.

*/


- addUser: who			{		return [users  addUser: who];			}
- addListener: who		{		return [users  addListener: who];		}
- removeListener: who	{		return [users  removeListener: who];	}


- removeUser: who
{
	[users  removeUser: who];
	return
			[users  numUsers] == 0	&&
			[self  garbageCollect] &&
		   ![NXApp  conformsTo: @protocol(IB)] ?
		
				self = [self  free] : self;
	
}


- (BOOL) garbageCollect
{
	return YES;
}


/* ========================================================================== */


/*

The methods below find an appropriate icon and title for the list.  The name buffer cannot be allocated on the stack since it would get clobbered when the code returned.

*/


- image
{
	if (numElements == 0)	return nil;						else
	if (numElements == 1)	return [dataPtr[0]  image];		else
	
	return sheaf;
}


- (const char *) name
{
	if (numElements == 0)	return NULL;					else
	if (numElements == 1)	return [dataPtr[0]  name];
	else
	{
		sprintf(name, "%d items", numElements);
		return  name;
	}
}


- (BOOL) isDraggable		{	return dataPtr ? [dataPtr[0]  isDraggable]:NO; }
- (BOOL) isDragAccepting	{	return NO;									   }
- (BOOL) isEditable			{	return NO;									   }
- setDraggable: (BOOL)flag;	{	return nil;									   }


/* ========================================================================== */


/*

An IKList sends out notification messages before and after its contents change.  Objects that use the list can intercede to abort the operation: all users must give their approval for the operation to take place.

Note that these methods do not send out a didChangeName: notification, even though it might be appropriate.  The reason is that the list can't really tell whether it's name actually changed, since these methods might be invoked by a subclass that provides its own names (like IKSuitcase).

*/


- appendList: (List *) objects
{
	if (![users  poll: @selector(list:willAppendList:)  with: objects])
			return nil;
	
	[users  setSendAnnouncements: NO];
	[super  appendList: objects];
	[users  setSendAnnouncements: YES];
	[users  announce: @selector(list:didAppendList:)  with: objects];
	
	return self;
}


- removeList: (List *) objects
{
	int
		i;
	
	if (![users  poll: @selector(list:willRemoveList:)  with: objects])
			return nil;
	
	[users  setSendAnnouncements: NO];
	for (i = 0; i < [objects  count]; i++)
			[self  removeObject: [objects  objectAt: i]];
	
	[users  setSendAnnouncements: YES];
	[users  announce: @selector(list:didRemoveList:)  with: objects];
	
	return self;
}


- insertObject: object  at: (unsigned int) i
{
	if (![users  poll: @selector(list:willAdd:)  with: object])
			return nil;
	
	[super  insertObject: object  at: i];
	[object  addUser: self];
	[users  announce: @selector(list:didAdd:)  with: object];
	
	return self;
}


- removeObjectAt: (unsigned int) i
{
	id
		object = [self  objectAt: i];
	
	if (![users  poll: @selector(list:willRemove:)  with: object])
			return nil;
	
	[super  removeObjectAt: i];
	[object  removeUser: self];
	[users  announce: @selector(list:didRemove:) with: object];
	
	return self;
}


/* ========================================================================== */


/*

An IKList unloads any object that wants to free itself.

*/


- willFree: who
{
	[self  removeObject: who];
	return self;
}


/* ========================================================================== */


/*

IKLists copy and read from the pasteboard using local object id.

*/


+ (NXAtom *) pasteTypes
{
	return IKIDPasteTypes();
}


- copyToPasteboard: (Pasteboard *) pboard
{
	IKCopyID (pboard, self);
	return self;
}


+ readFromPasteboard: (Pasteboard *) pboard;
{
	id
		object = IKReadID (pboard);
	
	return [object  isKindOf: [self  class]] ? object : nil;
}


@end
