This is IKCell.m in view mode; [Download] [Up]
/* File IKCell.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 is the type of cell used throughout the appkit, in file viewers and the like, to display an icon with an associated title. The nomal use is for the icon to represent an object. If given a delegate, the icon cell treats that delegate as the object it should display and sends it query messages to determine its title, icon, and dragging characteristics. In conjuction with the IKIconPath class, IKCells support drag-and-drop, acting as both sources and destinations. The title of an editable IKCell can be changed by clicking on the title region and typing a new one. The delegate is kept informed throughout dragging and editing so that it can initiate appropriate changes throughout the application. */ #import <appkit/appkit.h> #import "iconkit.h" #import "IKCellPS.h" @implementation IKCell /* ========================================================================== */ #define DX 9 #define DY 2 #define OFFSET 0 #define GAP 0 #define INSET 4 #define HYSTERESIS 4 /* ========================================================================== */ /* The class shares a global Cell that's used to draw and format the titles. It's true that IKCell inherits from Cell, so that we might just call super, but it easier this way since we don't need to keep swapping values in and out to get super to behave the way we want. */ static TextFieldCell * text; static char textBuffer [1000]; /* ========================================================================== */ /* The class keeps a single element cache for ghosted images, since they appear far and away most frequently during dragging operations, when the same image is drawn repeatedly in multiple cells. */ static id ghostImage = nil, ghostHighlightMask = nil, ghosting = nil; static void initGhostImages (NXSize); /* ========================================================================== */ + initialize { if (self == [IKCell class]) { [self setVersion: 1]; text = [[[TextFieldCell alloc] initTextCell: ""] setStringValueNoCopy: textBuffer]; } return self; } /* ========================================================================== */ /* Cells are initially set up in appropriately for an icon path in a browser. The IKShelf class creates its own prototype to give the appropriate behavior for a shelf. */ - init { if ((self = [super initTextCell: ""]) != nil) { image = nil; delegate = nil; flags.showBranch = NO; flags.draggable = YES; flags.dragAccepting = YES; flags.editable = YES; flags.container = NO; flags.locked = YES; flags.reallyLocked = YES; flags.multipleLines = NO; flags.ghosted = NO; [self setAlignment: NX_CENTERED]; } return self; } - initTextCell: (const char *) theTitle { return [[self init] setTitle: theTitle]; } - initIconCell: (const char *) iconName { return [[self init] setImage: [NXImage findImageNamed: iconName]]; } - initImage: (NXImage *) theImage title: (const char *) theTitle { return [[[self init] setImage: theImage] setTitle: theTitle]; } - initDelegate: (id <IKIconObject, IKDependency>) theDelegate { return [[self init] setDelegate: theDelegate]; } - initFromCopy: (IKCell *) copy { [[[[[[[[[[[[self init] setBranch: [copy isBranch]] setDraggable: [copy isDraggable]] setDragAccepting: [copy isDragAccepting]] setEditable: [copy isEditable]] setContainer: [copy isContainer]] setLocked: [copy isLocked]] setReallyLocked: [copy isReallyLocked]] setMultipleLines: [copy isMultipleLines]] setGhosted: [copy isGhosted]] setTitle: [copy title]] setImage: [copy image]]; return self; } - free { [delegate removeUser: self]; return self = [super free]; } - (const char *) getInspectorClassName { return "IKCellInspector"; } /* ========================================================================== */ /* Here are the archiving methods. An IKCell saves its image and all its flags, but not its delegate. */ - read: (NXTypedStream *) stream { char showBranch, draggable, dragAccepting, editable, container, locked, reallyLocked, multipleLines; [super read: stream]; [self setAlignment: NX_CENTERED]; switch (NXTypedStreamClassVersion (stream, "IKCell")) { case 1: image = NXReadObject (stream); NXReadTypes (stream, "cccccccc", &showBranch, &draggable, &dragAccepting, &editable, &container, &locked, &reallyLocked, &multipleLines); flags.showBranch = showBranch; flags.draggable = draggable; flags.dragAccepting = dragAccepting; flags.editable = editable; flags.container = container; flags.locked = locked; flags.reallyLocked = reallyLocked; flags.multipleLines = multipleLines; flags.ghosted = NO; break; case 0: image = NXReadObject (stream); NXReadTypes (stream, "ccccccc", &showBranch, &draggable, &dragAccepting, &editable, &container, &locked, &reallyLocked); flags.showBranch = showBranch; flags.draggable = draggable; flags.dragAccepting = dragAccepting; flags.editable = editable; flags.container = container; flags.locked = locked; flags.reallyLocked = reallyLocked; flags.multipleLines = NO; flags.ghosted = NO; break; } return self; } - write: (NXTypedStream *) stream { char showBranch = flags.showBranch, draggable = flags.draggable, dragAccepting = flags.dragAccepting, editable = flags.editable, container = flags.container, locked = flags.locked, reallyLocked = flags.reallyLocked, multipleLines = flags.multipleLines; [super write: stream]; NXWriteObject (stream, image); NXWriteTypes (stream, "cccccccc", &showBranch, &draggable, &dragAccepting, &editable, &container, &locked, &reallyLocked, &multipleLines); return self; } /* ========================================================================== */ /* Here are all the set and get methods. When a delegate is set, it is queried for its title, icon, and editing and drag attributes. */ - delegate { return delegate; } - image { return image; } - (const char *) title { return contents; } - (BOOL) isBranch { return flags.showBranch; } - (BOOL) isContainer { return flags.container; } - (BOOL) isLocked { return flags.locked; } - (BOOL) isReallyLocked { return flags.reallyLocked; } - (BOOL) isEmptyContainer { return flags.container && !delegate; } - (BOOL) isMultipleLines { return flags.multipleLines; } - (BOOL) isGhosted { return flags.ghosted; } - (BOOL) isDraggable { return flags.draggable && (!delegate || [delegate isDraggable]); } - (BOOL) isDragAccepting { return flags.dragAccepting && (!delegate || [delegate isDragAccepting]); } - (BOOL) isEditable { return flags.editable && (!delegate || [delegate isEditable]); } - setImage: (NXImage *) theImage { id old = image; image = theImage; return old; } - setTitle: (const char *) theTitle { NXRect oldFrame, newFrame; id editor = [self editor]; [self setStringValue: theTitle ? theTitle : ""]; if (editor) { if (theTitle == NULL) [[self controlView] endEditing]; else { [[[editor getFrame: &oldFrame] setAutodisplay: NO] setText: contents]; if ([editor isHorizResizable]) [editor sizeToFit]; [[editor setAutodisplay: YES] getFrame: &newFrame]; [[self controlView] display: NXUnionRect (&newFrame, &oldFrame) : 1]; } } return self; } - setBranch: (BOOL) flag { flags.showBranch = flag; return self; } - setDraggable: (BOOL) flag { flags.draggable = flag; return self; } - setDragAccepting: (BOOL) flag { flags.dragAccepting = flag; return self; } - setEditable: (BOOL) flag; { flags.editable = flag; return self; } - setContainer: (BOOL) flag { flags.container = flag; return self; } - setLocked: (BOOL) flag { flags.locked = flag; return self; } - setReallyLocked: (BOOL) flag { flags.reallyLocked = flag; return self; } - setMultipleLines: (BOOL) flag { flags.multipleLines = flag; return self; } - setGhosted: (BOOL) flag { flags.ghosted = flag; return self; } - setDelegate: (id <IKIconObject, IKDependency>) theDelegate { id old = delegate; delegate = IKCheckConformance (theDelegate) ? theDelegate : nil; [delegate addUser: self]; [self setImage: [delegate image]]; [self setTitle: [delegate name]]; return [old removeUser: self]; } - willFree: who { if (who == delegate) { delegate = nil; [self setDelegate: nil]; [[self controlView] updateCell: self]; } return self; } /* ========================================================================== */ /* The methods below provide the most convenient way to access and change a cell's shelf behavior. */ - (int) shelfMode { if (!flags.container) return IK_NOSHELF; else if (flags.reallyLocked) return IK_REALLYLOCKED; else if (flags.locked) return IK_LOCKED; else return IK_UNLOCKED; } - setShelfMode: (int) mode { switch (mode) { case IK_NOSHELF: [self setContainer: NO]; break; case IK_UNLOCKED: [[[self setContainer: YES] setLocked: NO] setReallyLocked: NO]; break; case IK_LOCKED: [[[self setContainer: YES] setLocked: YES] setReallyLocked: NO]; break; case IK_REALLYLOCKED: [[[self setContainer: YES] setLocked: YES] setReallyLocked: YES]; break; } return self; } /* ========================================================================== */ /* The cell's title, icon, and attributes are automatically updated as needed. The setTitle: method already redraws the cell if needed, so we only need to explicitly redraw when chaning the image. */ - didChangeName: sender { if (sender == delegate) [self setTitle: [delegate name]]; return self; } - didChangeImage: sender { if (sender == delegate) { [self setImage: [delegate image]]; [[self controlView] updateCellInside: self]; } return self; } /* ========================================================================== */ /* Highlighting is more elaborate in an IKCell than a normal cell. When highlighted, the icon is displayed against an illuminated round corner rectangle. */ - highlight: (const NXRect *) cellFrame inView: view lit: (BOOL) flag { if ((image != NULL) && (cFlags1.highlighted != flag)) { cFlags1.highlighted = flag; [self drawInside: cellFrame inView: view]; } return self; } /* ========================================================================== */ /* The method below returns the image to use when drawing. In this implementation, the image is either the one being stored in the cell, or else a ghosted image calculated on the spot. */ - _imageToDraw { if (flags.ghosted && image != ghosting) { NXPoint origin = { 0.0, 0.0 }; NXSize size = { 0.0, 0.0 }; [image getSize: &size]; initGhostImages (size); [ghostImage lockFocus]; [image composite: NX_COPY toPoint: &origin]; [ghostHighlightMask composite: NX_SATOP toPoint: &origin]; [ghostImage unlockFocus]; ghosting = image; } return flags.ghosted ? ghostImage: image; } /* ========================================================================== */ /* An IKCell is drawn in three parts: the icon, the title, and a branch symbol if needed. There are two possible layouts. If the title is to occupy a single line, then the title and icon are centered in the cellFrame. If it should occupy multiple lines, then the icon is a fixed distance from the top of the cell, and the title occupies the remainder. The title is drawn using an auxilliary cell shared by the entire IKCell class. One could get rid of it by just calling super, but it's easier this way since the actual title may be truncated from the cell contents. We use a static variable to keep track of the current view for drawTitle:, instead of calling [self controlView], since InterfaceBuilder never bothers to set the control view when alt-dragging out a matrix. */ static id controlView = nil; float background; - drawInside: (const NXRect *) cellFrame inView: (View *) view { IKdprintf ("\tdrawing cell: %s\n", contents); controlView = view; if (image != NULL) [self drawIcon: * cellFrame]; if (contents != NULL) [self drawTitle: * cellFrame]; if (flags.showBranch) [self drawBranch: * cellFrame]; return self; } - drawIcon: (NXRect) iconRect { [self getIconRect: &iconRect]; PSsetgray ((cFlags1.state | cFlags1.highlighted) ? NX_WHITE: NX_LTGRAY); PSiconBackdrop( iconRect.origin.x - DX, iconRect.origin.y - DY, iconRect.size.width + 2.0 * DX, iconRect.size.height + 2.0 * DY ); iconRect.origin.y += iconRect.size.height; [[self _imageToDraw] composite: NX_SOVER toPoint: &iconRect.origin]; return self; } - drawTitle: (NXRect) titleRect { if ([self editor] == nil) { [self getTitleRect: &titleRect]; strcpy (textBuffer, contents); [[[[[[text setAlignment: cFlags1.alignment] setParameter: NX_CELLHIGHLIGHTED to: 0] setFont: support] setTextGray: flags.ghosted ? NX_DKGRAY : NX_BLACK] setBackgroundGray: NX_LTGRAY] setWrap: !cFlags2.noWrap]; if (!flags.multipleLines) IKShortenTitle (text, titleRect.size.width); [text drawInside: &titleRect inView: controlView]; } return self; } - drawBranch: (NXRect) cellFrame { id branchIcon = [NXBrowserCell branchIcon]; NXPoint origin = cellFrame.origin; NXSize size; [branchIcon getSize: &size]; origin.x += cellFrame.size.width - size.width; origin.y += (cellFrame.size.height + size.height) / 2.0; [branchIcon composite: NX_SOVER toPoint: &origin]; return self; } /* ========================================================================== */ /* The calcCellSize: method needs to take into account both the icon and title of the cell. The input frame may be huge, so we just look at the actual sizes of the text and icon. */ - calcCellSize: (NXSize *) size inRect: (const NXRect *) frame { NXSize imageSize; [super calcCellSize: size inRect: frame]; [image getSize: &imageSize]; if (contents[0] == '\0') size->height = 0.0; size->width = MAX (size->width, imageSize.width) + 2.0 * DX + 1.0; size->height = size->height + imageSize.height + GAP + OFFSET + 2.0 * DY + 1.0; return self; } /* ========================================================================== */ /* Here's the method used to compute the layout of the cell. Several compile time defaults control the spacing: OFFSET vertical offset of the cell contents away from true center GAP distance between the title and highlighting INSET gap from top of cell to icon in a multiline cell A mutliline cell is drawn with the icon rectangle a fixed distance from the top of the cell, and with the title rectangle occupying the remainder. In a single line cell, the title and icon rectangles are centered vertically. */ - _getIconRect: (NXRect *) iconRect titleRect: (NXRect *) titleRect { NXRect bigRect = {{ 0.0, 0.0 }, { 10000.0, 10000.0 }}; NXSize title = { 0.0, 0.0 }, icon = { 0.0, 0.0 }; float h = titleRect->origin.y; [image getSize: &icon]; iconRect->origin.x += (iconRect->size.width - icon.width) / 2.0; iconRect->origin.y += flags.multipleLines ? INSET: (iconRect->size.height - icon.height) / 2.0; if (contents[0] != '\0') { if (flags.multipleLines) { titleRect->origin.y = iconRect->origin.y + icon.height + GAP + DY; title.height = titleRect->size.height - titleRect->origin.y + h; title.width = titleRect->size.width; } else { [super calcCellSize: &title inRect: &bigRect]; title.width = MIN (titleRect->size.width, title.width); iconRect->origin.y += OFFSET - (title.height + GAP) / 2.0; titleRect->origin.y = iconRect->origin.y + icon.height + GAP + DY; titleRect->origin.x += (int) (titleRect->size.width - title.width) / 2; } } iconRect->size = icon; titleRect->size = title; return self; } /* ========================================================================== */ /* Here are the methods to calculate the icon and title rectangles. It's proved simpler to always just calculate both simulatenously. */ - getIconRect: (NXRect *) iconRect { NXRect titleRect = *iconRect; [self _getIconRect: iconRect titleRect: &titleRect]; return self; } - getTitleRect: (NXRect *) titleRect { NXRect iconRect = *titleRect; [self _getIconRect: &iconRect titleRect: titleRect]; return self; } /* ========================================================================== */ /* This method determines the part of an IKCell that contains a particular point. An IKIconPath uses it to find out where the user has clicked to decide if it should initiate dragging or editing. */ - (int) hitPart: (NXPoint *) where inRect: (const NXRect *) cellFrame { NXRect iconRect = * cellFrame, titleRect = * cellFrame; [self getIconRect: &iconRect]; [self getTitleRect: &titleRect]; return NXMouseInRect (where, &titleRect, YES) ? IK_TITLEPART: NXMouseInRect (where, &iconRect, NO) ? IK_ICONPART: IK_NOPART; } /* ========================================================================== */ /* This method lets the user drag the cell's delegate and drop it elsewhere on the screen. Dragging will work without a delegate, but the drag pasteboard will be empty. The event mask is temporarily reset to enable dragging events. A locked cell retains its contents despite the drag. Unless the cell is reallyLocked, the user can temporarily unlock the cell by holding down the command key. Notice that the cell keeps itself registered as a user of its current delegate until after the drag completes. The pasteboard is not presently smart enough to do reference counting itself. This code sidesteps [self setDelegate: nil] to avoid a clumsy looking erase followed by an immediate redraw. It's not 100% semantically correct, though, so if a user manages to drag really, really, really fast, there might be trouble. */ - dragIcon: (NXEvent *) event inRect: (const NXRect *) cellFrame ofView: view { Pasteboard * pboard = [Pasteboard newName: NXDragPboard]; NXEvent mouseDown = * event; NXRect iconRect = * cellFrame; NXPoint offset; id old = delegate, theImage = image; [[view window] addToEventMask: NX_MOUSEDRAGGEDMASK]; while ((event = [NXApp getNextEvent: NX_ALLEVENTS])->type == NX_MOUSEDRAGGED) { offset.x = event->location.x - mouseDown.location.x; offset.y = event->location.y - mouseDown.location.y; if (abs(offset.x) + abs(offset.y) > HYSTERESIS) { [old addUser: self]; if (delegate != nil) [delegate copyToPasteboard: pboard]; else [pboard declareTypes: NULL num: 0 owner: self]; [self getIconRect: &iconRect]; iconRect.origin.y += iconRect.size.height; if (flags.container && !flags.reallyLocked && (!flags.locked || (event->flags & NX_COMMANDMASK))) { [delegate removeUser: self]; [self setState: 0]; delegate = nil; cFlags1.highlighted = NO; } [view dragImage: theImage at: &iconRect.origin offset: &offset event: &mouseDown pasteboard: pboard source: view slideBack: YES]; if (delegate == nil && old != nil) [self setDelegate: nil]; [old removeUser: self]; break; } } [[view window] removeFromEventMask: NX_MOUSEDRAGGEDMASK]; return self; } /* ========================================================================== */ /* The method below edits the cell's title. The cell installs itself as the text delegate so that it can make appropriate changes when the editing finishes. The appkit seems too slow to have the editor intercept an initial double click, hence the otherwise unnecessary check to make sure we're not already editing. */ - editTitle: (NXEvent *) event inRect:(const NXRect *) cellFrame ofView: view { NXRect titleRect = * cellFrame; float background; id editor = [[view window] getFieldEditor: YES for: self]; BOOL canEdit = [self isEditable], canSelect = canEdit; if ([editor delegate] != self) { if (flags.multipleLines) { background = NX_LTGRAY; [self getTitleRect: &titleRect]; } else { background = NX_WHITE; titleRect.size.width += 10000; titleRect.origin.x -= 5000; [self getTitleRect: &titleRect]; [view setClipping: YES]; } { NXSize maxSize = { 10000.0, titleRect.size.height }, minSize = { 0.0, titleRect.size.height }; [view endEditing]; [[[[[[[[[[[[[[editor setFrame: &titleRect] setHorizResizable: !flags.multipleLines] setVertResizable: NO] setMaxSize: &maxSize] setMinSize: &minSize] setAlignment: NX_CENTERED] setBackgroundGray: background] setTextGray: NX_BLACK] setOpaque: background != -1.0] setFont: support] setEditable: canEdit] setSelectable: canSelect] setText: contents] setDelegate: self]; } [view addSubview: editor]; [editor display]; } if (event != NULL) { [[view window] makeFirstResponder: editor]; [editor mouseDown: event]; } return self; } /* ========================================================================== */ /* The cell updates its title and delegate's name whenever its editor resigns first responder status. */ - (BOOL) textWillEnd: sender { char name [1000]; if ([self isEditable]) { [sender getSubstring: name start: 0 length: 1000]; if (name[0] == '\0') strcat (name, " "); if (strcmp (name, contents)) { [self setStringValue: name]; [delegate setName: name]; } } return NO; } - textDidResize: sender oldBounds: (const NXRect *) oldBounds invalid: (NXRect *) invalidRect { [[sender window] invalidateCursorRectsForView: [self controlView]]; return self; } - editor { id editor = [[[self controlView] window] getFieldEditor: NO for: self]; return ([editor delegate] == self) ? editor: nil; } @end /* ========================================================================== */ /* Here is the routine used to initialize the ghost image and highlight mask. It's called anew every time a ghost image needs to be drawn so that it can resize everything to be large enough for the current image. */ static void initGhostImages (NXSize size) { NXSize imageSize = { 0.0, 0.0 }, maskSize = { 0.0, 0.0 }; [ghostImage getSize: &imageSize]; [ghostHighlightMask getSize: &maskSize]; if (size.width > imageSize.width || size.height > imageSize.height) { [ghostImage free]; ghostImage = [[NXImage alloc] initSize: &size]; } if (size.width > maskSize.width || size.height > maskSize.height) { [ghostHighlightMask free]; ghostHighlightMask = [[NXImage alloc] initSize: &size]; [ghostHighlightMask lockFocus]; PSsetalpha (1.0 / 3.0); PSsetgray (1.0); PSrectfill (0.0, 0.0, size.width, size.height); [ghostHighlightMask unlockFocus]; } }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.