This is MiscDragCell.m in view mode; [Download] [Up]
/* MiscDragCell.m Copyright (C) 1996 Todd Thomas Use is governed by the MiscKit license This object is included in the MiscKit by permission from the author and its use is governed by the MiscKit license, found in the file "LICENSE.rtf" in the MiscKit distribution. Please refer to that file for a list of all applicable permissions and restrictions. */ // RCS identification information static char *rcsID = "$Id: MiscDragCell.m,v 1.4 1996/10/07 02:49:12 todd Exp $"; static void __AvoidCompilerWarning(void) {if(!rcsID)__AvoidCompilerWarning();} #import <AppKit/AppKit.h> #import "MiscAbbreviatedTextCell.h" #import "IKCellPS.h" #import "MiscDragCell.h" // Class ivars // Keeps track of who the source of a drag is (if it is one of us). // Use -isSourceDragInProgress to find out whether you are the // current source in a drag. (instead of accessing this ivar // or it's class method accessors). static MiscDragCell* _sourceDragCell = nil; // Keeps track of who the destination of a drag is (if it is one of us). // Use -isDestinationDragInProgress to find out whether you are // the current destination in a drag. (instead of accessing this ivar // or it's class method accessors). static MiscDragCell* _destinationDragCell = nil; // Places to keep dimmed images. Use the class accessors. static NSImage* _dimmedSourceImage = nil; static NSImage* _dimmedDestinationImage = nil; // Current class version for archiving. static int MiscDragCellCurrentVersion = 0; // Spacing between text and image (if drag cell displays both). static int MiscDragCellImageTextSpacing = 3; @implementation MiscDragCell /*" MiscDragCell is an abstract class that lays the groundwork for draggable cells to be used within some kind of drag view (either a MiscDragView or MiscDragMatrix subclass). For an example of a concrete subclass see MiscFileDragCell, which implements dragging using the NSFilenamePboardType. "*/ //------------------------------------------------------------------- // Class initialization //------------------------------------------------------------------- + (void) initialize /*" Our class initializer. Sets our class version for use with archiving. "*/ { if (self == [MiscDragCell class]) { [self setVersion:MiscDragCellCurrentVersion]; } } //------------------------------------------------------------------- // Drag support //------------------------------------------------------------------- + (MiscDragCell*) destinationDragCell /*" Returns the drag cell that's currently acting as the drag destination, or nil either if there's no drag going on or the destination is some other object. If instances want to know if they are the current drag destination they should use our #isDestinationDragInProgress instance method. "*/ { return _destinationDragCell; } + (void) setDestinationDragCell:(MiscDragCell*)newDest /*" Sets newDest as the current drag destination. When the destination drag ends this method should be called with a nil argument. "*/ { _destinationDragCell = newDest; } + (MiscDragCell*) sourceDragCell /*" Returns the drag cell that's currently acting as the drag source, or nil either if there's no drag going on or the source is some other object (that is not a subclass of MiscDragCell). If instances want to know if they are the current drag source they should use our #isSourceDragInProgress instance method. "*/ { return _sourceDragCell; } + (void) setSourceDragCell:(MiscDragCell*)newSource /*" Sets newSource as the current drag source. When the source drag ends this method should be called with a nil argument. "*/ { _sourceDragCell = newSource; } + (NSImage*) dimmedSourceImage /*" Used internally. Returns our dimmed source image. Since only a single drag can be taking place at any time I decided to use a class variable to hold the image. "*/ { return _dimmedSourceImage; } + (void) setDimmedSourceImage:(NSImage*)newImage /*" Sets our dimmed source image to newImage. "*/ { [_dimmedSourceImage autorelease]; _dimmedSourceImage = [newImage copy]; } + (NSImage*) dimmedDestinationImage /*" Used internally. Returns our dimmed destination image. Since only a single drag can be taking place at any time I decided to use a class variable to hold the image. "*/ { return _dimmedDestinationImage; } + (void) setDimmedDestinationImage:(NSImage*)newImage /*" Used internally. Sets our current dimmed image for the destination drag cell. "*/ { [_dimmedDestinationImage autorelease]; _dimmedDestinationImage = [newImage copy]; } //------------------------------------------------------------------- // Initialization / deallocation //------------------------------------------------------------------- - init /*" Our designated initializer. Since #initTextCell: and #initImageCell: don't seem to make much sense for a drag cell you have to use this method. Calling either of the other two more common NSCell initializer methods will result in a run-time error. "*/ { BOOL error = ([super init] == nil); if (!error) { // Background color is only used if we draw a border. [self setBackgroundColor:[NSColor lightGrayColor]]; _imageCell = [[NSCell alloc] init]; _textCell = [[MiscAbbreviatedTextCell alloc] initTextCell:@""]; [self setTitle:@""]; [self setAllowsSourceDragging:YES]; [self setAllowsDestinationDragging:YES]; [self setShadowsIncoming:YES]; } return error ? nil : self; } - initTextCell:(NSString*)text /*" Calls #doesNotRecognizeSelector:. Use init instead. "*/ { NSLog(@"[MiscDragCell initTextCell:] Use -init as the designated initializer."); [self doesNotRecognizeSelector:_cmd]; return nil; } - (id) initImageCell:(NSImage*)image /*" Calls #doesNotRecognizeSelector:. Use init instead. "*/ { NSLog(@"[MiscDragCell initImageCell:] Use -init as the designated initializer."); [self doesNotRecognizeSelector:_cmd]; return nil; } - (void) dealloc /*" Releases our resources. "*/ { [_acceptingImage release]; [_imageCell release]; [_textCell release]; [_backgroundColor release]; [super dealloc]; } //------------------------------------------------------------------- // NSCell overrides //------------------------------------------------------------------- - (BOOL) acceptsFirstResponder /*" Drag Views and Cells cannot be manipulated by anything other than the mouse so they don't accept first responder. "*/ { return NO; } - (BOOL) isOpaque /*" If we have a border then we cover our entire area (and return YES). If we don't, we won't (returns NO). "*/ { return ([self borderType] != NSNoBorder); } //------------------------------------------------------------------- // Internal Cell accessors //------------------------------------------------------------------- - (NSCell*) textCell /*" Returns the cell we use to draw our title. You shouldn't need to use this method except in other methods like #prepareTextCellForDisplay. "*/ { return _textCell; } - (void) setTextCell:(NSCell*)newTextCell /*" Sets the cell we use to draw our title. By default it is an instance of MiscAbbreviatedTextCell, which takes care of putting ".." at the end of titles that are too long to display. "*/ { [_textCell autorelease]; _textCell = [newTextCell retain]; } - (NSCell*) imageCell /*" Returns the cell we use to draw our image. You shouldn't need to use this method unless you create a subclass and want to use a custom cell. "*/ { return _imageCell; } - (void) setImageCell:(NSCell*)newImageCell /*" Sets the image cell we use to draw our image. The default is just an NSCell initialized with #initImageCell:. "*/ { [_imageCell autorelease]; _imageCell = [newImageCell retain]; } //------------------------------------------------------------------- // Our images //------------------------------------------------------------------- - (NSImage*) acceptingImage /*" Returns the image that's dislayed when we are accepting an image, or nil if there is no accepting image. This method overrides any value returned by our #shadowsIncoming method. "*/ { return _acceptingImage; } - (void) setAcceptingImage:(NSImage *)newImage /*" Sets the image that's displayed when we are the destination of a drag. This would be useful for instance if we were trying to write a folder subclass that showed the open folder when it accepts the drag. "*/ { [_acceptingImage autorelease]; _acceptingImage = [newImage copy]; } - (NSImage*) image /*" Returns the image we display or nil if we don't have an image. "*/ { return _image; } - (void) setImage:(NSImage*)newImage /*" Sets the image we display. If newImage is nil we set our title to be the empty string. "*/ { [_image autorelease]; _image = [newImage copy]; // There can be no title if there isn't an image. if (newImage == nil) { [self setTitle:@""]; } } //------------------------------------------------------------------- // Title manipulation //------------------------------------------------------------------- - (NSString*) title /*" Returns our current title. This may or may not be displayed, depending upon what #displaysTitle returns. "*/ { return _title; } - (void) setTitle:(NSString*)newTitle /*" Sets our title. This may or may not be displayed under our image depending on what our #displaysTitle method returns. returns YES. "*/ { newTitle = (newTitle != nil) ? newTitle : @""; [_title autorelease]; _title = [newTitle copy]; } - (BOOL) displaysTitle /*" Returns YES if we display our title under our image. "*/ { return _displaysTitle; } - (void) setDisplaysTitle:(BOOL)displays /*" Sets whether we display a title. "*/ { _displaysTitle = displays; } - (BOOL) isTitleEditable /*" Titles aren't currently editable so the return value doesn't really matter. "*/ { return _titleEditable; } - (void) setTitleEditable:(BOOL)isEditable /*" Not implemented yet. "*/ { _titleEditable = isEditable; } - (BOOL) isTitleMultiline /*" Not implemented yet. "*/ { return _titleMultiline; } - (void) setTitleMultiline:(BOOL)isMultiline /*" Not implemented yet. "*/ { _titleMultiline = isMultiline; } //------------------------------------------------------------------- // Dragging options //------------------------------------------------------------------- - (BOOL) allowsSourceDragging /*" Returns YES if we can be the source of a dragging session, or NO if we cannot. "*/ { return _allowSourceDragging; } - (void) setAllowsSourceDragging:(BOOL)aBool /*" Sets whether we can be the source of a dragging session. "*/ { _allowSourceDragging = aBool; } - (BOOL) allowsDestinationDragging /*" Returns YES if we are allowed to accept a drag. The default is YES. "*/ { return _allowDestinationDragging; } - (void) setAllowsDestinationDragging:(BOOL)aBool /*" Sets whether we are allowed to accept a drag. The default is YES. "*/ { _allowDestinationDragging = aBool; } - (BOOL) acceptsForeignDrag /*" Returns YES if we accept drags that don't originate from within our own application. The default is YES. Subclasses can override this to change this behavior. "*/ { return YES; } - (BOOL) acceptsLocalDrag /*" Returns YES if we accept drags that originate from within our own application. The default is YES. Subclasses can override this to change this behavior. "*/ { return YES; } - (BOOL) acceptsSelfDrag /*" Returns YES if we accept drags that originated from our own drag cell. The default is YES. You see this behavior in the Workspace shelf. If you drag a file from one of the cells you are still able to put it back in the same cell. If this method returns YES then #acceptsLocalDrag should also return YES. Subclasses can override this to change this behavior. "*/ { if ([self allowsDestinationDragging] == NO) { return NO; } return YES; } - (BOOL) retainsData /*" Returns YES if when we drag that we leave a copy of ourselves behind. If you were to emulate the Workspace shelf then this would return NO. The default is NO. Subclasses can override this to change this behavior. "*/ { return NO; } - (BOOL) shadowsIncoming /*" Returns YES if we show a dimmed image when there is an incoming drag. This assumes that we are currently accepting drags. The default is YES. "*/ { return _shadowIncoming; } - (void) setShadowsIncoming:(BOOL)nowShadow /*" Sets whether we show a dimmed image when there is an incoming drag. The default is YES. "*/ { _shadowIncoming = nowShadow; } - (NSColor *) shadowColor /*" Returns the color used to created a dimmed appearance for a destination image. The default is to return a partially transparent gray. If you want a different appearance for shadowing, you can override this method. "*/ { NSColor* shadowColor = [NSColor lightGrayColor]; shadowColor = [shadowColor colorWithAlphaComponent:0.5]; return shadowColor; } - (BOOL) dragImageSlidesBack /*" Returns YES if the dragging image should slide back to it's destination when it isn't deposited in another view. The default is to slide back only if we retain our data (#retainsData returns YES). "*/ { return [self retainsData]; } //------------------------------------------------------------------- // Destination dragging //------------------------------------------------------------------- - (NSArray*) acceptingPasteboardTypes /*" This method is for subclasses to override to return the pasteboard types that it will accept (as the destination of a drag). In contrast, for a source drag you don't have to define what you will drag. You can put data of any type on the pasteboard in our #prepareForSourceDrag method. Our implementation returns nil. "*/ { return nil; } - (void) cleanupAfterDestinationDrag /*" Internally used. This is called after a destination drag whether it was a success or failure. "*/ { [[self class] setDimmedDestinationImage:nil]; // If we are both the source and destination then we'll // take care of setting the dest cell to nil if (![self isSourceDragInProgress]) { [[self class] setDestinationDragCell:nil]; } if ([self image] == nil) { [self setTitle:@""]; } } //------------------------------------------------------------------- // Dragging pasteboard //------------------------------------------------------------------- - (NSPasteboard*) draggingPasteboard /*" Override this if you would like to use a different pasteboard for dragging. The default is the NSDragPboard. "*/ { return [NSPasteboard pasteboardWithName:NSDragPboard]; } //------------------------------------------------------------------- // Source dragging //------------------------------------------------------------------- - (BOOL) prepareForSourceDrag /*" This method is meant to be overridden in subclasses to allow you to put the data you want to send on the dragging pasteboard. Our implementation returns NO. "*/ { return NO; } #define STICKINESS 5.0 - (BOOL) trackMouse:(NSEvent*)theEvent inRect:(NSRect)rect ofView:(NSView*)controlView untilMouseUp:(BOOL)untilMouseUp; /*" We take care of starting a source drag, as long as the following conditions are met: (1) We allow source dragging, (2) our image is not nil, (3) prepareForSourceDrag returns YES, and the mouse is moved at least 5 pixels from where the original mouse down occured. Returns YES no matter what happens, though I'm not sure if that's correct. "*/ { NSEvent *origEvent=nil, *loopEvent; NSPoint zero = {0.0, 0.0}; NSPoint origPoint, newLoc; float distance = 0.0; BOOL error = NO; if (!error && ([self image] == nil || ![self allowsSourceDragging])) { error = YES; } // if the overridden prepareForSourceDrag returns YES continue if (!error) { error = ![self prepareForSourceDrag]; } // The mouse moved more than STICKINESS pixels code orignally // came from Mike Ferris's MODocumentWell class in the MOKit. if (!error) { origPoint = [theEvent locationInWindow]; origPoint = [controlView convertPoint:origPoint fromView:nil]; origEvent = theEvent; [[controlView window] setAcceptsMouseMovedEvents:YES]; loopEvent = [[controlView window] nextEventMatchingMask: (NSLeftMouseUpMask | NSLeftMouseDraggedMask)]; while ([loopEvent type] != NSLeftMouseUp && distance < STICKINESS) { newLoc = [loopEvent locationInWindow]; newLoc = [controlView convertPoint:newLoc fromView:nil]; // if we've gone at least STICKINESS units from the original // mousedown, start dragging our file. distance = sqrt(((newLoc.x - origPoint.x) * (newLoc.x - origPoint.x)) + ((newLoc.y - origPoint.y) * (newLoc.y - origPoint.y))); loopEvent = [[controlView window] nextEventMatchingMask: (NSLeftMouseUpMask | NSLeftMouseDraggedMask)]; } [[controlView window] setAcceptsMouseMovedEvents:NO]; // If the user didn't drag far enough then don't continue. error = (distance < STICKINESS); } if (!error) { NSImage* dragImage = [self imageToDrag]; [self calculateDragPoint:&origPoint andOffset:&zero]; if (![self retainsData]) { [self createDimmedSourceImage]; } [[self class] setSourceDragCell:self]; [controlView dragImage:dragImage at:origPoint offset:NSMakeSize((&zero)->x,(&zero)->y) event:origEvent pasteboard:[self draggingPasteboard] source:self slideBack:[self dragImageSlidesBack]]; [[self class] setSourceDragCell:nil]; [[self class] setDimmedSourceImage:nil]; // Update ourselves. [(NSControl*)controlView drawCell:self]; } // Does it even matter what I return? return YES; } - (void) calculateDragPoint:(NSPoint*)dragPoint andOffset:(NSPoint*)offset /*" This method should be overridden if you want to have some control on where the dragged image is first placed, and how far it is offset from the original mousedown (dragPoint). Making the dragPoint be the middle of the image is the default, "*/ { NSSize imageSize = [[self imageToDrag] size]; dragPoint->x -= imageSize.width/2.0; dragPoint->y -= imageSize.height/2.0; if ([[self controlView] isFlipped]) { dragPoint->y += imageSize.height; } } //------------------------------------------------------------------- // Drag in progress? //------------------------------------------------------------------- - (BOOL) isSourceDragInProgress /*" Returns YES if we are currently the source of a drag. This is most often used so we can draw shadowed images, etc. in our drawing methods. "*/ { return ([[self class] sourceDragCell] == self); } - (BOOL) isDestinationDragInProgress /*" Returns YES if we are currently the destination of a drag. This is most often used so we can draw shadowed images, etc. in our drawing methods. "*/ { return ([[self class] destinationDragCell] == self); } //------------------------------------------------------------------- // Sizing ourselves //------------------------------------------------------------------- - (void) calcDrawInfo:(NSRect)rect /*" Doesn't do anything yet. "*/ { NSLog (@"[NSDragCell calcDrawInfo:] was called."); } - (NSSize) cellSize /*" Returns our current cell size. "*/ { // TEMPORARY NSSize minCellSize = NSMakeSize (64.0, 64.0); if ([self displaysTitle]) { minCellSize.height += 10.0; minCellSize.width += 10.0; } NSLog (@"[NSDragCell cellSize] was called."); return minCellSize; } - (NSSize) cellSizeForBounds:(NSRect)bounds /*" This method hasn't been implemented correctly yet. It just returns the size from our #cellSize method. "*/ { NSLog (@"[NSDragCell cellSizeForBounds:] was called."); return [self cellSize];; } - (NSRect) drawingRectForBounds:(NSRect)bounds /*" This method hasn't been implemented correctly yet. "*/ { NSLog (@"[NSDragCell drawingRectForBounds:] was called."); return [super drawingRectForBounds:bounds]; } - (NSRect) imageRectForBounds:(NSRect)bounds /*" This method hasn't been implemented correctly yet. "*/ { NSLog (@"[NSDragCell imageRectForBounds:] was called."); return [[self imageCell] imageRectForBounds:bounds]; } - (NSRect) titleRectForBounds:(NSRect)bounds /*" This method hasn't been implemented correctly yet. "*/ { NSLog (@"[NSDragCell titleRectForBounds:] was called."); return [[self textCell] titleRectForBounds:bounds]; } //------------------------------------------------------------------- // Display options //------------------------------------------------------------------- - (NSColor*) backgroundColor /*" Returns the background color we use if happen to draw our background (which we only do if we have a border). Otherwise we are non-opaque and our view behind us will end up drawing our background. By default our background color is light gray. "*/ { return _backgroundColor; } - (void) setBackgroundColor:(NSColor*)newColor /*" Sets our background color. By default it is light gray. See our #backgroundColor method to see when we use our background color. "*/ { if (newColor != nil) { [_backgroundColor autorelease]; _backgroundColor = [newColor retain]; } } - (BOOL) isBezeled /*" Overridden from NSCell since we are using the NSBox-like methods #{setBorderType:}/#{borderType} to determine our border. We return YES if our border type is NSBezelBorder. "*/ { return ([self borderType] == NSBezelBorder); } - (void) setBezeled:(BOOL)nowBezeled /*" If nowBezeled is YES we will draw an NSBezelBorder. If nowBezeled is NO we set our border type to NSNoBorder. "*/ { [self setBorderType:(nowBezeled ? NSBezelBorder : NSNoBorder)]; } - (BOOL) isBordered /*" Overridden from NSCell since we are using the NSBox-like methods #{setBorderType:}/#{borderType} to determine our border. We return YES if our border type is NSLineBorder. "*/ { return ([self borderType] == NSLineBorder); } - (void) setBordered:(BOOL)nowBordered /*" If nowBordered is YES we will draw an NSLineBorder. If nowBordered is NO we set our border type to NSNoBorder. "*/ { [self setBorderType:(nowBordered ? NSLineBorder : NSNoBorder)]; } - (NSBorderType) borderType /*" Returns our border type, which will be one of NSNoBorder (the default), NSBezelBorder, NSLineBorder, or NSGrooveBorder. "*/ { return _borderType; } - (void) setBorderType:(NSBorderType)aType /*" Sets our border type. aType should be one of NSNoBorder, NSBezelBorder, NSLineBorder or NSGrooveBorder. "*/ { _borderType = aType; } - (NSImage*) imageToDisplay /*" Returns the image we should be displaying in our cell. This is checked every time we are asked to draw ourselves. If we are not the middle of a drag then we just display our image (as returned by [self image]). Otherwise if we are the destination drag cell we can either return our accepting image (if we have one) or a dimmed destination image (if #shadowsIncoming returns YES). If we are the current source drag cell then we'll either return our own image, or a dimmed source image (if we don't retain our data). "*/ { NSImage* imageToDisplay; // If we are not in the middle of a source or destination // drag then we display our image by default. imageToDisplay = [self image]; // Choose the image to display if we are the destination // drag view. if ([self isDestinationDragInProgress]) { // The accepting image (if there is one) overrides // shadowing the incoming image. if ([self acceptingImage] != nil) { imageToDisplay = [self acceptingImage]; } else if ([self shadowsIncoming]) { imageToDisplay = [[self class] dimmedDestinationImage]; } } // Choose the image to display if we are the source // drag view. This must be after the destination drag // image choice because we may be both the source and // destination of a drag at the same time (and the // source image overrides). if ([self isSourceDragInProgress]) { if (![self retainsData]) { imageToDisplay = [[self class] dimmedSourceImage]; } else { imageToDisplay = [self image]; } } return imageToDisplay; } - (NSString*) titleToDisplay /*" Returns the title that'll be displayed along with our image as long as #displaysTitle returns YES. By default we just return [self title] but subclasses can easily modify this method. "*/ { NSString* titleToDisplay = [self title]; if (titleToDisplay == nil) { titleToDisplay = @""; } return titleToDisplay; } - (NSImage*) imageToDrag /*" By default we just return [self image]. Subclasses can alter this to return a different image. For instance, a subclass that drags TIFF images could override this method to return the standard TIFF icon. "*/ { return [self image]; } //------------------------------------------------------------------- // Dimmed image creation //------------------------------------------------------------------- - (void) createDimmedSourceImage /*" Creates our dimmed source image by copying our image and compositing our shadow color over it. This copy can be retrieved by calling [[self class] dimmedSourceImage]. "*/ { NSImage* sourceImage = [[self image] copy]; NSSize sdiSize = [sourceImage size]; [sourceImage lockFocus]; [[self shadowColor] set]; PScompositerect(0.0, 0.0, sdiSize.width, sdiSize.height, NSCompositeSourceAtop); [sourceImage unlockFocus]; // This might be better served as an instance method. [[self class] setDimmedSourceImage:sourceImage]; [sourceImage release]; } - (void) createDimmedDestinationImageUsingImage:(NSImage*)origImage /*" Creates our dimmed destination image by copying origImage and compositing our shadow color over it. This copy can be retrieved by calling [[self class] dimmedDestinationImage]. "*/ { NSImage* draggingImage = [origImage copy]; NSSize ddiSize; ddiSize = [draggingImage size]; [draggingImage lockFocus]; // Dim the image. [[self shadowColor] set]; PScompositerect(0.0, 0.0, ddiSize.width, ddiSize.height, NSCompositeSourceAtop); [draggingImage unlockFocus]; [[self class] setDimmedDestinationImage:draggingImage]; [draggingImage release]; } //------------------------------------------------------------------- // Display hooks //------------------------------------------------------------------- - (void) prepareTextCellForDisplay /*" This method is called just before the text is drawn. We currently just set the cell's string value to the title we are displaying (whether we really display it or not depends on the value returned by #displaysTitle). If you extend this method make sure and do a [super prepareTextCellForDisplay]. "*/ { [[self textCell] setStringValue:[self titleToDisplay]]; } //------------------------------------------------------------------- // Drawing //------------------------------------------------------------------- - (void) drawWithFrame:(NSRect)frame inView:(NSView*)controlView /*" Draws the border if there is one, then offsets the NSRect and passes it to #drawInteriorWithFrame:inView:. "*/ { NSRect interiorFrame = frame; switch ([self borderType]) { case NSNoBorder: break; // All these cases haven't been tested yet so this may not // be correct. That's why they're all the same right now. // Of course if they turn out to be all the same offset // then this'll become an if-then statement. case NSBezelBorder: interiorFrame = NSInsetRect (interiorFrame, 2.0, 2.0); NSDrawGrayBezel (frame, frame); break; case NSGrooveBorder: interiorFrame = NSInsetRect (interiorFrame, 2.0, 2.0); NSDrawGroove (frame, frame); break; case NSLineBorder: interiorFrame = NSInsetRect (interiorFrame, 2.0, 2.0); NSFrameRect (frame); break; } [self drawInteriorWithFrame:interiorFrame inView:controlView]; } - (void) drawInteriorWithFrame:(NSRect)frame inView:(NSView*)controlView /*" Draws our background if we have a border (using our background color). We then draw our image and title (if #displaysTitle returns YES) if we have one. "*/ { NSImage* imageToDisplay = [self imageToDisplay]; NSRect imageFrame; NSSize imageSize; NSCell* textCell = [self textCell]; NSRect textFrame; // If we have a border we have to draw our entire background. if ([self borderType] != NSNoBorder) { [[self backgroundColor] set]; NSRectFill (frame); } // Figure out where image and text (if displayed) go. If there's // text to be drawn it will only affect the y placement of the // image. imageSize = [imageToDisplay size]; if ([self displaysTitle] && imageToDisplay != nil) { // NSCell* textCell = [self textCell]; // NSRect textFrame; float textAndImageHeight; [self prepareTextCellForDisplay]; textFrame.size = [textCell cellSize]; // Don't allow us to draw beyond our bounds. if (NSWidth (textFrame) > NSWidth (frame)) { textFrame.size.width = NSWidth (frame); } textAndImageHeight = (NSHeight(textFrame) + imageSize.height + MiscDragCellImageTextSpacing); textFrame.origin.y = NSMinY(frame) + ((NSHeight(frame) - textAndImageHeight) / 2.0); if ([controlView isFlipped]) { textFrame.origin.y += textAndImageHeight - NSHeight(textFrame); } // textFrame.origin.x = NSMinX(frame) + ((NSWidth(frame) - NSWidth(textFrame)) / 2.0); textFrame.origin.x = frame.origin.x; if ([controlView isFlipped]) { imageFrame.origin.y = NSMinY(frame) + ((NSHeight(frame) - textAndImageHeight) / 2.0); } else { imageFrame.origin.y = textFrame.origin.y + NSHeight(textFrame) + MiscDragCellImageTextSpacing; } } else { imageFrame.origin.y = NSMinY(frame) + ((NSHeight(frame) - imageSize.height) / 2.0); } // Calculate where image shuld go. (Needed to do highlighting below). imageFrame.size = imageSize; imageFrame.origin.x = NSMinX(frame) + ((NSWidth(frame) - imageSize.width) / 2.0); // Draw highlighting. if ([self isHighlighted] && [self image] != nil) { [[NSColor whiteColor] set]; PSiconBackdrop (imageFrame.origin.x - 4.0, imageFrame.origin.y - 4.0, NSWidth(imageFrame) + 8.0, NSHeight(imageFrame) + 8.0); } // Draw title. [textCell highlight:[self isHighlighted] withFrame:textFrame inView:controlView]; [textCell drawWithFrame:textFrame inView:controlView]; // Draw image. [[self imageCell] setImage:imageToDisplay]; [[self imageCell] drawWithFrame:imageFrame inView:controlView]; } //------------------------------------------------------------------- // Copying //------------------------------------------------------------------- - (id) copyWithZone:(NSZone*)zone /*" Returns a copy of the receiver. "*/ { id copy = [[[self class] allocWithZone:zone] init]; [copy setAcceptingImage:[self acceptingImage]]; [copy setImage:[self image]]; [copy setTitle:[self title]]; [copy setBorderType:[self borderType]]; [copy setBackgroundColor:[self backgroundColor]]; [copy setAllowsSourceDragging:[self allowsSourceDragging]]; [copy setAllowsDestinationDragging:[self allowsDestinationDragging]]; [copy setShadowsIncoming:[self shadowsIncoming]]; [copy setDisplaysTitle:[self displaysTitle]]; [copy setTitleEditable:[self isTitleEditable]]; [copy setTitleMultiline:[self isTitleMultiline]]; return copy; } //------------------------------------------------------------------- // Archiving methods //------------------------------------------------------------------- - (id) initWithCoder:(NSCoder*)aDecoder /*" Unarchives an instance of MiscDragCell. "*/ { int version; [super initWithCoder:aDecoder]; version = [aDecoder versionForClassName:@"MiscDragCell"]; switch (version) { case 0: // Current version [self setAcceptingImage:[aDecoder decodeObject]]; [self setImageCell:[aDecoder decodeObject]]; [self setImage:[aDecoder decodeObject]]; [self setTextCell:[aDecoder decodeObject]]; [self setTitle:[aDecoder decodeObject]]; [self setBackgroundColor:[aDecoder decodeObject]]; [aDecoder decodeValueOfObjCType:@encode(NSBorderType) at:&_borderType]; [aDecoder decodeValuesOfObjCTypes:"ccc", &_allowSourceDragging, &_allowDestinationDragging, &_shadowIncoming]; [aDecoder decodeValuesOfObjCTypes:"ccc", &_displaysTitle, &_titleEditable, &_titleMultiline]; break; default: NSLog(@"[%@ %@] - unknown version found in initWithCoder:.", NSStringFromClass([self class]), NSStringFromSelector(_cmd)); break; } return self; } - (void) encodeWithCoder:(NSCoder*)aCoder /*" Archives an instance of MiscDragCell. "*/ { [super encodeWithCoder:aCoder]; [aCoder encodeObject:[self acceptingImage]]; [aCoder encodeObject:[self imageCell]]; [aCoder encodeObject:[self image]]; [aCoder encodeObject:[self textCell]]; [aCoder encodeObject:[self title]]; [aCoder encodeObject:[self backgroundColor]]; [aCoder encodeValueOfObjCType:@encode(NSBorderType) at:&_borderType]; [aCoder encodeValuesOfObjCTypes:"ccc", &_allowSourceDragging, &_allowDestinationDragging, &_shadowIncoming]; [aCoder encodeValuesOfObjCTypes:"ccc", &_displaysTitle, &_titleEditable, &_titleMultiline]; } @end @implementation MiscDragCell (NSDraggingSource) - (unsigned int) draggingSourceOperationMaskForLocal:(BOOL)flag /*" By default we return NSDragOperationAll. Subclasses can override to customize behavior. "*/ { return NSDragOperationAll; } - (void) draggedImage:(NSImage*)image endedAt:(NSPoint)screenPoint deposited:(BOOL)deposited /*" Takes care of details after the image has been deposited. "*/ { // If we don't retain our data then get rid of it. if (![self retainsData]) { // If we deposited somewhere else besides back on ourselves // then get rid of the image. if (deposited) { if ([self isDestinationDragInProgress]) { // If we are both the source and dest then we're // responsible for setting this (if the dest sets it // then our if statement wouldn't have worked because // the dest drag cell would have already been reset). [[self class] setDestinationDragCell:nil]; } else { [self setImage:nil]; } } } } @end @implementation MiscDragCell (NSDraggingDestination) - (unsigned int) draggingEntered:(id <NSDraggingInfo>)sender /*" Checks to see whether we should accept the drag by checking whether we allow destination dragging, whether we accept self drags, foreign drags or local drags and returns NO if any of the above conditions don't agree with our instance's current settings. "*/ { if ([self allowsDestinationDragging] == NO) { return NSDragOperationNone; } if ([self acceptsSelfDrag] == NO && [sender draggingSource] == self) { return NSDragOperationNone; } if ([self acceptsForeignDrag] == NO && [sender draggingSource] == nil) { return NSDragOperationNone; } if ([self acceptsLocalDrag] == NO && [sender draggingSource] != nil) { return NSDragOperationNone; } [[self class] setDestinationDragCell:self]; if ([self shadowsIncoming]) { [self createDimmedDestinationImageUsingImage:[sender draggedImage]]; // Display our new dimmed image. [(NSControl*)[self controlView] drawCell:self]; } return NSDragOperationCopy; } - (unsigned int) draggingUpdated:(id <NSDraggingInfo>)sender /*" By default we just return NSDragOperationCopy (and I'm not sure why anymore). "*/ { return NSDragOperationCopy; } - (void) draggingExited:(id <NSDraggingInfo>)sender /*" Calls our #cleanupAfterDestinationDrag and asks our control view to draw ourselves again. If you extend this method make sure to call [super draggingExited:sender];. "*/ { [self cleanupAfterDestinationDrag]; [(NSControl*)[self controlView] drawCell:self]; } - (BOOL) prepareForDragOperation:(id <NSDraggingInfo>)sender /*" Returns YES. "*/ { return YES; } - (BOOL) performDragOperation:(id <NSDraggingInfo>)sender /*" Returns YES. "*/ { return YES; } - (void) concludeDragOperation:(id <NSDraggingInfo>)sender /*" Calls our #cleanupAfterDestinationDrag and asks our control view to draw ourselves again. If you extend this method make sure to call [super concludeDragOperation:sender];. "*/ { [self cleanupAfterDestinationDrag]; [(NSControl*)[self controlView] drawCell:self]; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.