This is IKShelf.m in view mode; [Download] [Up]
/* File IKShelf.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. */ /* ========================================================================== */ /* This class specializes the IKIconPath to conveniently set things up as needed for a shelf. In particular, its cells are initialized to act as locked containers, its mode is set to highlight, and it automatically adds new cells when its size changes. */ #import <appkit/appkit.h> #import <apps/InterfaceBuilder.h> #import <objc/objc-runtime.h> #import "iconkit.h" @implementation IKShelf /* ========================================================================== */ /* An IKShelf needs access to the Sheaf image so that it can put something in its cells for the user to see in InterfaceBuilder. */ static NXImage * folder = nil; static NXSize defaultCellSize = { 70.0, 74.0 }; + initialize { if (self == [IKShelf class]) { IKInitIDpboardType(); folder = [NXImage findImageNamed: "Folder"]; } return self; } - (const char *) getInspectorClassName { return ([NXApp currentEvent]->flags & NX_ALTERNATEMASK) ? [super getInspectorClassName]: "IKShelfInspector"; } /* ========================================================================== */ /* The default initFrame: sets up a prototype IKCell that's appropriate for a shelf. It also does a little snooping and adds an appropriate image and title if it decides its running as part of the InterfaceBuilder palette. */ - initFrame: (const NXRect *) frameRect { if ((self = [self initFrame: frameRect mode: NX_HIGHLIGHTMODE cellClass: [IKCell class] numRows: 0 numCols: 0]) != nil) { [self setPrototype: [[[[[IKCell alloc] init] setLocked: YES] setReallyLocked: NO] setContainer: YES]]; if ([NXApp conformsTo: @protocol(IB)]) [[[self prototype] setTitle: "IconKit"] setImage: folder]; [[[self fillWithCells] sizeCellsToFit] sizeToCells]; } return self; } - initFrame: (const NXRect *) frameRect mode: (int) aMode cellClass: class numRows: (int) rows numCols: (int) cols { if ((self = [super initFrame: frameRect mode: aMode cellClass: class numRows: rows numCols: cols]) != nil) { users = [[IKAnnouncer alloc] initOwner: self]; objectToPaste = nil; lastIsEmptyContainer = NO; dynamic = YES; [[[[self setAutosizeCells: NO] setClassToHold: ""] setCellSize: &defaultCellSize] sizeCellsToFit]; } return self; } - free { [self unregisterDraggedTypes]; [users announce: @selector(willFree:)]; [users free]; return [super free]; } /* ========================================================================== */ /* Here are the archiving methods. Archiving allows an IKShelf to be used on an InterfaceBuilder palette. */ - read: (NXTypedStream *) stream { char * name; [super read: stream]; users = [[IKAnnouncer alloc] initOwner: self]; NXReadTypes (stream, "*c", &name, &dynamic); [self setPrototype: NXReadObject (stream)]; [self setClassToHold: name]; free(name); return self; } - write: (NXTypedStream *) stream { [super write: stream]; NXWriteTypes (stream, "*c", &classToHold, &dynamic); NXWriteObject (stream, [self prototype]); return self; } /* ========================================================================== */ /* Here are the IKDependency methods. They all simply get forwarded to the users object. */ - addUser: who { return [users addUser: who]; } - addListener: who { return [users addListener: who]; } - removeUser: who { return [users removeUser: who]; } - removeListener: who { return [users removeListener: who]; } /* ========================================================================== */ /* Here are the methods to adjust the properties of the shelf. The delegate message returns the delegate of the currently selected cell. */ - (BOOL) isDynamic { return dynamic; } - (const char *) classToHold { return classToHold; } - setDynamic: (BOOL) flag { dynamic = flag; return self; } - setClassToHold: (const char *) name { NXAtom * pasteTypes; int n = 0; id class; if (classToHold) free(classToHold); classToHold = name ? NXCopyStringBuffer(name) : NULL; class = classToHold ? objc_lookUpClass(name) : nil; pasteTypes = classToHold == NULL ? NULL: [class respondsTo: @selector(pasteTypes)] ? [class pasteTypes]: classToHold[0] == '\0' ? IKIDPasteTypes(): NULL; if (pasteTypes != NULL) { while (pasteTypes[n] != NULL) n++; [self unregisterDraggedTypes]; [self registerForDraggedTypes: pasteTypes count: n]; } return self; } - delegate { return [selectedCell delegate]; } /* ========================================================================== */ /* Here are methods to automatically resize and add cells to the shelf. The fillWithCells method fills the shelf with as many cells of the current size as it can. The sizeCellsToFit method adjusts the intercell spacing so that the cells exactly fill the frame. When dynamic sizing is active, the shelf calls both these methods whenever its size changes. The net effect is that the shelf will always contain the maximum number of cells of the original size. If the cells have been set to autoSize, dynamic sizing does nothing. */ - sizeTo: (NXCoord) width : (NXCoord) height { [super sizeTo: width : height]; if (dynamic) [[self fillWithCells] sizeCellsToFit]; return self; } - fillWithCells { int rows = frame.size.height / cellSize.height, cols = frame.size.width / cellSize.width; [self renewRows: rows cols: cols]; [self sizeCellsToFit]; return self; } - sizeCellsToFit { NXSize gap; gap.height = numRows > 1 ? (frame.size.height - cellSize.height * numRows) / (numRows - 1) : 0.0; gap.width = numCols > 1 ? (frame.size.width - cellSize.width * numCols) / (numCols - 1) : 0.0; [self setIntercell: &gap]; return self; } /* ========================================================================== */ /* The standard matrix highlighting mode seems somewhat strange, in that it leaves the final cell selected. This code makes sure that cells act like buttons when a shelf is in NX_HIGHLIGHTMODE. */ - mouseDown: (NXEvent *) event { [super mouseDown: event]; if ([self mode] == NX_HIGHLIGHTMODE) { [self drawCell: [selectedCell setState: 0]]; [self selectCellAt: -1 : -1]; } return self; } /* ========================================================================== */ /* The method below determine how an empty container responds to drags. The first two should give an appropriate icon and title; the last should determine the allowable dragging operations. The default implementations use the dragged image and take the title from the pasteboard. */ - (NXDragOperation) draggingOperation: (id <NXDraggingInfo>) sender { id delegates = [users usersAndListeners], who; int i = [delegates count]; while (i--) { who = [delegates objectAt: i]; if ([who respondsTo: @selector(shelf:willDrag:from:)]) return [who shelf: self isDragging: objectToPaste from: sender]; } return objectToPaste != nil ? NX_DragOperationAll: NX_DragOperationNone; } /* ========================================================================== */ /* The object on the pasteboard is read once when dragging enters the shelf. */ - (NXDragOperation) draggingEntered: (id <NXDraggingInfo>) sender { id class; [users announce: @selector(shelf:dragWillEnter:) with: sender]; lastIsEmptyContainer = NO; class = classToHold ? objc_lookUpClass(classToHold) : nil; objectToPaste = [class respondsTo: @selector(readFromPasteboard:)] ? [class readFromPasteboard: [sender draggingPasteboard]]: IKReadID ([sender draggingPasteboard]); [objectToPaste addUser: self]; return [super draggingEntered: sender]; } - draggingExited: (id <NXDraggingInfo>) sender { [users announce: @selector(shelf:dragWillExit:) with: sender]; [objectToPaste removeUser: self]; return [super draggingExited: sender]; } /* ========================================================================== */ /* When a new cell is entered, the shelf checks to see whether the current cell is an empty container. If so it checks to see what operation to perform and puts an appropriate title and icon in the container. When the cell is exited, the title and icon are erased. */ - (NXDragOperation) cellEntered: (id <NXDraggingInfo>) sender { NXDragOperation op; if ([current isEmptyContainer] && ((op = [self draggingOperation: sender]) != NX_DragOperationNone)) { [[current setGhosted: YES] setDelegate: objectToPaste]; lastIsEmptyContainer = YES; return op; } else return [super cellEntered: sender]; } - (NXDragOperation) cellUpdated: (id <NXDraggingInfo>) sender { return lastIsEmptyContainer ? [self draggingOperation: sender]: [super cellUpdated: sender]; } - cellExited: (id <NXDraggingInfo>) sender { if (lastIsEmptyContainer) { [[last setGhosted: NO] setDelegate: nil]; lastIsEmptyContainer = NO; return self; } else return [super cellExited: sender]; } /* ========================================================================== */ /* The shelf processes the standard drag protocol messages itself whenever the destination is an empty container cell. */ - (BOOL) prepareForDragOperation: (id <NXDraggingInfo>) sender { return !lastIsEmptyContainer ? [super prepareForDragOperation: sender]: YES; } - (BOOL) performDragOperation: (id <NXDraggingInfo>) sender { [objectToPaste removeUser: self]; objectToPaste = nil; if (lastIsEmptyContainer) { [current setGhosted: NO]; [self drawCell: current]; } return !lastIsEmptyContainer ? [super performDragOperation: sender]: YES; } - concludeDragOperation: (id <NXDraggingInfo>) sender { [users announce: @selector(shelf:dragWillComplete:)]; return !lastIsEmptyContainer ? [super concludeDragOperation: sender]: nil; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.