This is File.m in view mode; [Download] [Up]
/* File File.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. */ /* ========================================================================== */ /* These objects represent single files and folders. The companion class FileList is used to build lists of multiple files, such as might appear in a browser selection. The workspace is used to find the necessary icons, titles, and file information. Files are a subclass of IKFolder so that the filesystem can be properly represented as a directed graph. Every file in the filesystem corresponds to a single node in the File graph. The graph is constructed lazily, with parts loaded in only as needed. A hash table is used to match new files dragged in from the workspace to existing nodes. It would be nice to demonstrate offloading, but that requires some care. In general, the graph structure will not correspond to the filesystem after the user has dragged around a few things, so we're likely to get some anomolous results reloading parts of the File graph that were changed. */ #import <appkit/appkit.h> #import <string.h> #import <sys/types.h> #import <sys/dir.h> #import <sys/stat.h> #import "File.h" #import "FileList.h" @implementation File /* ========================================================================== */ /* Two class variables are needed. The fileMap keeps track of the correspondence between File objects and filenames in the system. The draggedImage is used to steal the image for a new File directly from the dragging source. The workspace croacks if we ask it for images during a dragging session. */ static NXImage * draggedImage = nil; static id fileMap = nil; /* ========================================================================== */ /* Here are the class methods. The list of paste types only includes NXFilenamePboardType, since that type is sufficient to determine whether the File class can read an object from the pasteboard. */ + initialize { if (self == [File class]) fileMap = [[HashTable alloc] initKeyDesc: "*"]; return self; } + multipleSelectionClass { return [FileList class]; } + (NXAtom *) pasteTypes { static NXAtom pasteTypes[3] = { NULL, NULL, NULL }; pasteTypes[0] = NXFilenamePboardType; pasteTypes[1] = IKidPboardType; return pasteTypes; } /* ========================================================================== */ /* Here are the class methods that map filenames onto File objects. Every unique path name in the filesystem corresponds to a single node in the File graph, so a hash table is used to make the correspondence. Note that if the same file is reachable by two different paths, it will be mapped into two distinct File objects--one for each path. A logical improvement would be to get the inode number, or some such, and cache on that instead. */ + fileForPath: (const char *) thePath { id file = [fileMap valueForKey: thePath]; if (file == nil) { file = [[File alloc] initPath: thePath]; [fileMap insertKey: [file path] value: file]; } return file; } + fileForPathList: (const char *) thePath { id fileList; char paths [MAXPATHLEN], * p, * q; if (strchr(thePath, '\t') == NULL) fileList = [File fileForPath: thePath]; else { fileList = [[FileList alloc] init]; p = paths; strcpy(p, thePath); while (p != NULL) { if ((q = strchr(p, '\t')) != NULL) *q++ = '\0'; [fileList addObject: [File fileForPath: p]]; p = q; } } return fileList; } /* ========================================================================== */ /* Here is the initialization method. The images, parents, and children of a File are all loaded lazily to improve performance. Note that a File object initially only has access to a single parent; namely, the one that it can construct from its path. A File object installs itself in the parent list of all its children when it loads them in. */ - initPath: (const char *) thePath { struct stat status; char host [MAXNAMLEN + 1], * title = rindex(thePath, '/'); if (title == NULL) return nil; else if (title[1] == '\0') gethostname(title = host, MAXNAMLEN); else title++; if ((self = [super init: title]) != nil) { stat(thePath, &status); path = NXCopyStringBuffer(thePath); flags.dragAccepting = (status.st_mode & S_IFDIR) ? YES : NO; flags.leaf = !flags.dragAccepting || index(name, '.'); flags.hidden = name[0] == '.'; fileFlags.imageLoaded = NO; fileFlags.acceptingDragImageLoaded = NO; fileFlags.parentLoaded = NO; fileFlags.childrenLoaded = !flags.dragAccepting; } return self; } - copyFromZone: (NXZone *) zone; { File * copy = [super copyFromZone: zone]; copy->path = NXCopyStringBufferFromZone(path, zone); copy->fileFlags = fileFlags; return copy; } - free { [fileMap removeKey: path]; free(path); return [super free]; } /* ========================================================================== */ /* Here are the methods that retrieve information about a file. Files can be dragged and edited. Folders also accept drags. The acceptingDragImage is currently always the open folder. */ - image { if (!fileFlags.imageLoaded) image = draggedImage ? [draggedImage copy]: [[Application workspace] getIconForFile: path]; fileFlags.imageLoaded = 1; return image; } - acceptingDragImage { return [super acceptingDragImage]; } - (const char *) path { return path; } /* ========================================================================== */ /* The parent of a File is not loaded until requested, typically when the user selects a file sitting on the shelf. In that case, if the file was dragged in from the Workspace its parent will likely not yet be loaded. There's one bit of stickiness to watch out for. One would like to avoid accessing the disk when the first call to parent is via a call to addParent:. So addParent: always marks its parent list as loaded. */ - parents { char * p, directory [MAXNAMLEN + 1]; if (!fileFlags.parentLoaded) { fileFlags.parentLoaded = 1; strcpy (directory, path); if ((p = rindex (directory, '/')) != directory) { p[0] = '\0'; [[File fileForPath: directory] addChild: self]; } else if (p[1] != '\0') [[File fileForPath: "/"] addChild: self]; } return parents; } - addParent: parent { fileFlags.parentLoaded = 1; return [super addParent: parent]; } /* ========================================================================== */ /* The files contained in a directory are not loaded until actually needed. Aside from improving performance, such waiting avoids the mistake of reading in the entire file system whenever "/" is loaded. The File installs itself in the parent list of all its children. */ - children { struct direct ** file; char filename [MAXNAMLEN + 1]; int i, n, k; if (!fileFlags.childrenLoaded) { fileFlags.childrenLoaded = 1; strcpy(filename, path); if (filename[n = strlen(filename)-1] != '/') filename[++n] = '/'; filename[++n] = '\0'; k = scandir(filename, &file, NULL, alphasort); if (k != -1) { [users setSendAnnouncements: NO]; for (i = 0; i < k; i++) { strcpy(filename + n, file[i]->d_name); [self addChild: [File fileForPath: filename]]; free(file[i]); } [users setSendAnnouncements: YES]; free(file); } } return children; } /* ========================================================================== */ /* The workspace overloads when asked for a file icon during a dragging session, so the file class grabs the icon directly from the dragging source when it learns that a dragging session is about to begin. */ + shelf: sender dragWillEnter: (id <NXDraggingInfo>) source { draggedImage = [source draggedImage]; return self; } + shelf: sender dragWillExit: (id <NXDraggingInfo>) source { draggedImage = nil; return self; } + shelf: sender dragWillComplete: (id <NXDraggingInfo>) source { draggedImage = nil; return self; } /* ========================================================================== */ /* Files copy themselves both as local ids and file names. Notice that the read method checks for the local id type first. */ - copyToPasteboard: (Pasteboard *) pboard { NXAtom pasteTypes[] = { NXFilenamePboardType }; [super copyToPasteboard: pboard]; [pboard addTypes: pasteTypes num: 1 owner: nil]; [pboard writeType: NXFilenamePboardType data: path length: strlen(path)+1]; return self; } + readFromPasteboard: (Pasteboard *) pboard { NXAtom pasteTypes[] = { NXFilenamePboardType }; char * data; int n; id file = nil; if ((file = [super readFromPasteboard: pboard]) == nil && [pboard findAvailableTypeFrom: pasteTypes num: 1] != NULL) { [pboard readType: NXFilenamePboardType data: &data length: &n]; file = [File fileForPathList: data]; [pboard deallocatePasteboardData: data length: n]; } return file; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.