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.