ftp.nice.ch/pub/next/developer/objc/iconkit/IconKit.1.2.s.tar.gz#/IconKit-1.2/Classes/IKFolder.m

This is IKFolder.m in view mode; [Download] [Up]

/*

File IKFolder.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.

*/


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


/*

Browsing and editing a graph is one of the most natural uses for an IKBrowser.  This class defines a node in a directed graph.  The class is fully capable of handling dragging and editing events, as well as searching the graph to find paths.

The IKFolder is configurable in all its features: editing, dragging, accepting drags, and so forth.

Note that because an IKFolder maintains an explicit double link structure, it does not maintain IKDependencies with either its parents or children.  Instead, it simply sends them messages directly whenever the need arises.

*/

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

@implementation IKFolder 


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


static int		currentMark = 1;

static NXImage	* document;
static NXImage	* folder;
static NXImage	* openFolder;


+ initialize
{
	if (self == [IKFolder  class])
	{
		IKInitIDpboardType();
		document = [NXImage  findImageNamed: "Document"];
		folder = [NXImage  findImageNamed: "Folder"];
		openFolder = [NXImage  findImageNamed: "OpenFolder"];
	}
	
	return self;
}


+ multipleSelectionClass
{
	return [IKList  class];
}


- (NXImage *) getIBImage
{
	return folder;
}


- (const char *) getInspectorClassName
{
	return "IKFolderInspector";
}


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


/*

Here is the initialization.  IKFolders use the folder, open folder, and document images to represent different parts of the graph.  Notice that copying a graph only copies the children, and not the parents as well.

*/


- init
{
	return [self  init: "IconKit"];
}


- init: (const char *) theName
{
	if ((self = [super  init]) != nil)
	{
		users = [[IKAnnouncer  alloc]  initOwner: self];
		searchMarker = 0;
		dragging = nil;
		draggingSession = -1;
		
		name = theName ? NXCopyStringBuffer(theName) : NULL;
		image = nil;
		acceptingDragImage = nil;
		parents =  [[List  alloc]  init];
		children = [[List  alloc]  init];
		
		flags.editable = YES;
		flags.draggable = YES;
		flags.dragAccepting = YES;
		flags.hidden = NO;
		flags.leaf = NO;
		flags.freeing = NO;
	}
	
	return self;
}


- copyFromZone: (NXZone *) zone
{
	IKFolder
		* copy = [super  copyFromZone: zone];
	
	if (copy)
	{
		copy->flags = flags;
		copy->name = name ? NXCopyStringBufferFromZone (name, zone) : NULL;
		copy->users = [[IKAnnouncer  alloc]  initOwner: copy];
		copy->parents = [[List  alloc]  init];
		copy->children = [children  copy];
		copy->image = image ? [image  copyFromZone: zone] : nil;
		copy->acceptingDragImage = acceptingDragImage ?
							[acceptingDragImage  copyFromZone: zone] : nil;
										
		[copy->children  makeObjectsPerform: @selector(addParent:)  with: copy];
	}
	
	return copy;
}


- free
{
	IKdprintf ("freeing folder: %s\n", name ? name : "");
	
	flags.freeing = YES;
	[parents   makeObjectsPerform: @selector(removeChild:)   with: self];
	[children  makeObjectsPerform: @selector(removeParent:)  with: self];
	[users  announce: @selector(willFree:)];
	
	parents = [parents  free];
	children = [children  free];
	users = [users  free];
	if (name) free(name);
	if (image && ![image  name]) [image  free];
	if (acceptingDragImage && ![acceptingDragImage  name])
			[acceptingDragImage  free];
	
	return self = [super  free];
}


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


/*

Here are the archiving methods.  Archiving saves all the parents and children, but not the users.

*/

- read: (NXTypedStream *) stream
{
	char
		draggable,
		dragAccepting,
		editable,
		hidden,
		leaf;
	
    [super  read: stream];
	
	NXReadTypes (stream, "*", &name);
	NXReadTypes (stream, "ccccc", &draggable, &dragAccepting, &editable,
			&hidden, &leaf);
			
	flags.draggable = draggable;
	flags.dragAccepting = dragAccepting;
	flags.editable = editable;
	flags.hidden = hidden;
	flags.leaf = leaf;
	flags.freeing = NO;
	
	image = NXReadObject (stream);
	acceptingDragImage = NXReadObject (stream);
	parents = NXReadObject (stream);
	children = NXReadObject (stream);
	
	users = [[IKAnnouncer  alloc]  initOwner: self];
    
	return self;
}


- write: (NXTypedStream *) stream
{
	char
		draggable = flags.draggable,
		dragAccepting = flags.dragAccepting,
		editable = flags.editable,
		hidden = flags.hidden,
		leaf = flags.leaf;
	
    [super  write: stream];
	
	NXWriteTypes (stream, "*", &name);
	NXWriteTypes (stream, "ccccc", &draggable, &dragAccepting, &editable,
			&hidden, &leaf);
	
	NXWriteObject (stream, image);
	NXWriteObject (stream, acceptingDragImage);
	NXWriteObject (stream, parents);
	NXWriteObject (stream, children);
    
	return self;
}


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


/*

An IKFolder performs automatic reference counting memory management.  When it has no more users and no parents, 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];
	self = [self  checkForFree];
	
	return self;
}


- checkForFree
{
	return
			[users  numUsers] == 0	&&
			[parents  count]  == 0	&&
			[self  garbageCollect]	&&
		   !flags.freeing			&&
		   ![NXApp  conformsTo: @protocol(IB)] ?
		
				[self  free] : self;
}


- (BOOL) garbageCollect
{
	return YES;
}


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


/*

Here are the methods that indicate how an IKFolder behaves.  

*/


- image
{
	return	image ? image :
			flags.leaf ? document :
			folder;
}


- acceptingDragImage
{
	return	acceptingDragImage ? acceptingDragImage :
			openFolder;
}


- (const char *) name		{		return name;					}
- (BOOL) isDraggable		{		return flags.draggable;			}
- (BOOL) isDragAccepting	{		return flags.dragAccepting;		}
- (BOOL) isEditable			{		return flags.editable;			}
- (BOOL) isHidden			{		return flags.hidden;			}
- (BOOL) isLeaf				{		return flags.leaf;				}
- parents					{		return parents;					}
- children					{		return children;				}


- setDraggable: (BOOL) flag		{	flags.draggable = flag;		return self;   }
- setDragAccepting: (BOOL) flag	{	flags.dragAccepting = flag;	return self;   }
- setEditable: (BOOL) flag		{	flags.editable = flag;		return self;   }
- setHidden: (BOOL) flag		{	flags.hidden = flag;		return self;   }


- setLeaf: (BOOL) flag
{
	if (flags.leaf != flag)
	{
		flags.leaf = flag;
		if (image == nil) [users  announce: @selector(didChangeImage:)];
		[users  announce: @selector(didChangeProperties:)];
	}
	
	return self;
}


- setName: (const char *) theName
{
	if (name != NULL) free(name);
	name = theName ? NXCopyStringBuffer(theName) : NULL;
	[users  announce: @selector(didChangeName:)];
	
	return self;
}


- setImage: theImage
{
	id
		old = image;
	
	if (image != theImage)
	{
		image = theImage;
		[users  announce: @selector(didChangeImage:)];
	}
	
	return old;
}


- setAcceptingDragImage: theImage
{
	id
		old = acceptingDragImage;
	
	if (acceptingDragImage != theImage)
	{
		acceptingDragImage = theImage;
		[users  announce: @selector(didChangeAcceptingDragImage:)];
	}
	
	return old;
}


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


/*

Here are the search methods.  There are two; pathToNode: searches in the forward direction, following the child links, whereas pathFromNode: searches in the backwards directions, following the parent links. Both do a simple depth first search, leaving a marker at each node to avoid loops in the graph.  Both return a list containing the first path found.

The two methods will give radically different performance when the forward and backward branching factors are different.  In applications like a filesystem browser, the forward branching factor is almost certainly by far the larger, and one should use the pathFromNode: method.

-WARNING-

Depth first search will fall off the deep end if your graph is infinite.  You'll need to write a different algorithm that uses either iterative deepening or breadth first search.

Also, the marking strategy guarantees that this code is not thread safe (doubtless it's not for a zillion other reasons as well).  If you try to write a thread safe version, you'll need to put a mutex around the search algorithm.

*/


- pathToNode: destination
{
	currentMark++;
	return [self  searchFor: destination  via: @selector(children)];
}


- pathFromNode: source
{
	currentMark++;
	return [self  searchFor: source  via: @selector(parents)];
}


- searchFor: goal  via: (SEL) getNext
{
	id
		path = nil,
		next;
	int
		i,
		n;
	
	if (self == goal)
			path = [[List  alloc]  init];
	
	else if (searchMarker != currentMark)
	{
		searchMarker = currentMark;
		next = [self  perform: getNext];
		n = next ? [next  count] : 0;
		
		for (i = 0;  i < n;  i++)
			if ((path = [[next  objectAt: i]
									searchFor: goal  via: getNext]) != nil)
				break;
	}
	
	return path ? [path  addObject: self] : nil;
}


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


/*

Here are the methods to add parents and children.  The current implementation does not allow multiple links between two nodes, mostly because it makes graph operations more of a pain in the butt.  IKFolders should always invoke these methods, and never change the parent and children lists directly, so that subclasses can take additional action as needed.

Note that adding a child link creates a new parent link, but not vica versa.  The real graph structure is always given by the child links.  The parent links simply exist to allow convenient bidirectional traversals.

Subclasses may wish to load their children lazily, so these methods make sure to give them a chance before accessing the children variable.

*/


- addChild: child
{
	children = [self  children];
	if ([children  indexOf: child] == NX_NOT_IN_LIST)
	{
		[child  addParent: self];
		[children  addObject: child];
		[users  announce: @selector(didAddChild:)];
	}
	
	return self;
}


- addChildren: theChildren
{
	int
		i,
		n = [[self  children]  count];
	
	[users  setSendAnnouncements: NO];
	for (i = 0;  i < [theChildren  count];  i++)
			[self  addChild: [theChildren  objectAt: i]];
	
	[users  setSendAnnouncements: YES];
	if (n != [children  count])
			[users  announce: @selector(didAddChildren:)];
	
	return self;
}


- addParent: parent
{
	if ([parents  indexOf: parent] == NX_NOT_IN_LIST)
	{
		[parents  addObject: parent];
		[users  announce: @selector(didAddParent:)];
	}
	
	return self;
}


- removeChild: child
{
	children = [self  children];
	[child  removeParent: self];
	return	[children  removeObject: child] &&
			[users  announce: @selector(didRemoveChild:)] ?
	
				self : nil;
}


- removeChildren: theChildren
{
	int
		i;
	BOOL
		result = NO;
	
	[users  setSendAnnouncements: NO];
	for (i = 0;  i < [theChildren  count];  i++)
			result |= ([self  removeChild: [theChildren  objectAt: i]] != nil);
	
	[users  setSendAnnouncements: YES];
	return	result &&
			[users  announce: @selector(didRemoveChildren:)] ?
			
				self : nil;
}


- removeParent: parent
{
	return	[[self  parents]  removeObject: parent] &&
			[users  announce: @selector(didRemoveParent:)] &&
			[self  checkForFree] ?
			
				self : nil;
}


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


/*

An IKFolder will accept drags from other objects of its class.  There are three types of dragging operations that are defined.  Each effectively operates on the arcs of the graph:

	1 -- Linking adds a new arc from the destination to the source.
	2 -- Moving transfers an arc from a parent of the source to the destination.
	3 -- Copying adds a new arc from the destination to a copy of the source.

A picture paints a thousand words; see the documentation for clarification.

Moving is only defined when either the dragged object comes from an IKIconPath, or when the dragged object has only a single parent.  In other situations there's no clear indiciation of where the object should be moved from.  Copying is the only valid operation when the dragged object is already a child of the destination.

Users are kept informed throughout a drag operation so that they can take appropriate action.

*/


- (NXDragOperation) draggingEntered: (id <NXDraggingInfo>) sender
{
	int
		n = [sender  draggingSequenceNumber];
	
	if (draggingSession != n)
	{
		draggingSession = n;
		dragging = [[self  class]  readFromPasteboard:
											[sender  draggingPasteboard]];
		operationMask =
			(!dragging || (dragging == self)) ?
				NX_DragOperationNone:
	
			([[self  children]  indexOf: dragging] != NX_NOT_IN_LIST) ?
				NX_DragOperationCopy:
				
			(([[sender  draggingSource]  class] == [IKIconPath  class]) ||
					[[dragging  parents]  count]  <= 1) ?
				NX_DragOperationAll:
				NX_DragOperationLink | NX_DragOperationCopy;
	}
	
	return [self  draggingOperation: sender];
}


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


- (NXDragOperation) draggingOperation: (id <NXDraggingInfo>) sender
{
	NXDragOperation
		choices = operationMask & [sender  draggingSourceOperationMask]
						& ~NX_DragOperationPrivate;
	
 	while (choices & (choices - 1)) choices &= choices - 1;
	return choices;
}


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


/*

Here's the method that actually processes drags.  Nothing complicated, just lots of cases to consider.

*/

- (BOOL) performDragOperation: (id <NXDraggingInfo>) sender
{
	NXDragOperation
		op = [self  draggingOperation: sender];
	id
		iconPath,
		parent = nil,
		original = [[List  alloc]  init],
		final = original;
	int
		i;
	BOOL
		multiple = [dragging  isKindOf: [[self  class] multipleSelectionClass]],
		moving = op & NX_DragOperationGeneric,
		copying = !moving && (op & NX_DragOperationCopy);
	
	if (multiple)
			[original  appendList: dragging];	else
			[original  addObject:  dragging];
	
	if (moving)
	{
		if ([[dragging  parents]  count] <= 1)
				parent = [[dragging  parents]  objectAt: 0];
		else
		{
			iconPath = [sender  draggingSource];
			parent = [[iconPath  cellAt: 0
									: [iconPath  selectedCol] - 1]  delegate];
		}
	}
	
	if (copying)
	{
		final = [original  copy];
		for (i = 0;  i < [final  count];  i++)
				[final  replaceObjectAt: i  with: [[final  objectAt: i]  copy]];
	}
	
	[self  addChildren: final];
	if (parent != self) [parent  removeChildren: original];
	[original  free];
	if (copying) [final  free];
	
	return YES;
}


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


/*

IKFolders 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  isKindOf: [self  multipleSelectionClass]] ?
						
			object : nil;
}


@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.