This is IKIconPath.m in view mode; [Download] [Up]
/* File IKIconPath.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 provides a matrix of IKCells. It extends the event handling of an ordinary matrix to allow dragging and editing within individual cells. */ #import <appkit/appkit.h> #import <apps/InterfaceBuilder.h> #import "iconkit.h" @implementation IKIconPath /* ========================================================================== */ /* Defaults for an IKIconPath are a little different than a standard matrix. The designated initializers are the same as in the Matrix class. */ - initFrame: (const NXRect *) frameRect { if ((self = [self initFrame: frameRect mode: NX_RADIOMODE cellClass: [IKCell class] numRows: 1 numCols: 1]) != nil) { [self setCellSize: &frameRect->size]; [self sizeToCells]; } return self; } - initFrame: (const NXRect *) frameRect mode: (int) aMode prototype: aCell numRows: (int) rows numCols: (int) cols { if ((self = [self initFrame: frameRect mode: aMode cellClass: [IKCell class] numRows: rows numCols: cols]) != nil) [self setPrototype: aCell]; return self; } - initFrame: (const NXRect *) frameRect mode: (int) aMode cellClass: class numRows: (int) rows numCols: (int) cols { NXSize zero = { 0, 0 }; if ((self = [super initFrame: frameRect mode: aMode cellClass: class numRows: rows numCols: cols]) != nil) [[[[[[[self setBackgroundGray: NX_LTGRAY] setCellSize: &zero] setIntercell: &zero] setAutodisplay: YES] setClipping: NO] setOpaque: YES] setEmptySelectionEnabled: NO]; return self; } /* ========================================================================== */ /* Drawing aspects are set upon awakening to cover the current way that an IKIconPath redraws itself. Clipping is kept off until a cell editor is actually invoked. */ - awake { [super awake]; [[[self setAutodisplay: YES] setClipping: NO] setCellBackgroundGray: NX_LTGRAY]; return self; } /* ========================================================================== */ /* Mouse events are handled specially to check for editing and dragging. A click in the title rectangle of an alrady selected and editable cell initiates editing. A click in a draggable cell initiates dragging. */ - mouseDown: (NXEvent *) event { NXPoint where = event->location; NXRect cellFrame; int hit, row, col; [self convertPoint: &where fromView: nil]; cell = [self getRow: &row andCol: &col forPoint: &where]; [self getCellFrame: &cellFrame at: row : col]; hit = [cell hitPart: &where inRect: &cellFrame]; if ((hit == IK_ICONPART) && [cell isDraggable] && !(event->flags & (NX_SHIFTMASK | NX_ALTERNATEMASK))) { if (cell == selectedCell) [window makeFirstResponder: self]; else [self endEditing]; [self selectCell: cell]; [cell dragIcon: event inRect: &cellFrame ofView: self]; if (event->data.mouse.click > 1) [self sendDoubleAction]; else [self sendAction]; } else if ((hit == IK_TITLEPART) && [cell isEditable] && [cell state]) [cell editTitle: event inRect: &cellFrame ofView: self]; else { if (cell == selectedCell) [window makeFirstResponder: self]; else [self endEditing]; [super mouseDown: event]; } [window invalidateCursorRectsForView: self]; return self; } /* ========================================================================== */ /* An IKIconPath contains draggable objects, so we want to delay bringing the containing window forward until mouse-ups. */ - (BOOL) shouldDelayWindowOrderingForEvent: (NXEvent *) theEvent; { return YES; } /* ========================================================================== */ /* The methods below let one programmatically invoke editing of a cell. IKBrowser uses them to automatically edit titles in the browser. */ - editCell: theCell { int row, col; if ([self getRow: &row andCol: &col ofCell: theCell]) [self editCellAt: row : col]; return self; } - editCellAt: (int) row : (int) col { NXRect cellFrame; [window disableFlushWindow]; [self getCellFrame: &cellFrame at: row : col]; [[self cellAt: row : col] editTitle: NULL inRect: &cellFrame ofView: self]; [[window reenableFlushWindow] flushWindow]; return self; } - endEditing { NXRect editorFrame; id editor = [window getFieldEditor: NO for: self]; if ([cellList indexOf: [editor delegate]] != NX_NOT_IN_LIST) { [editor getFrame: &editorFrame]; [window endEditingFor: self]; [self displayFromOpaqueAncestor: &editorFrame : 1 : NO]; } return self; } /* ========================================================================== */ /* The abortEditing method gets called whenever any cell in the matrix changes its title. That's not really what we want, so instead an IKIconPath ignores abortEditing--effectively refusing to abort. Editing should be terminated with endEditing. */ - abortEditing { return nil; } /* ========================================================================== */ /* The cursor is an I-beam whenever it's over the title area of a selected and editable cell. */ - resetCursorRects { NXRect title; id editor = [window getFieldEditor: NO for: self], editingCell = [editor delegate], selection = [self getSelectedCells: nil], theCell; int row, col, i; i = [selection count]; while (i--) if ([theCell = [selection objectAt: i] isEditable] && theCell != editingCell) { [self getRow: &row andCol: &col ofCell: theCell]; [self getCellFrame: &title at: row : col]; [theCell getTitleRect: &title]; [self addCursorRect: &title cursor: NXIBeam]; } if ([cellList indexOf: editingCell] != NX_NOT_IN_LIST) { [editor getFrame: &title]; [self addCursorRect: &title cursor: NXIBeam]; } [selection free]; return self; } /* ========================================================================== */ /* The default Matrix implementation of drawCellAt:: is no good, since it potentially clobers any subviews--in our case the editor. The implementation here relies on Matrix being smart enough to avoid drawing anything outside the rectangle passed to display::. Try clicking on a cell that's being edited without these methods and see what happens. */ - drawCellAt: (int) row : (int) col { NXRect cellFrame; [window disableFlushWindow]; [self getCellFrame: &cellFrame at: row : col]; [self displayFromOpaqueAncestor: &cellFrame : 1 : NO]; [[window reenableFlushWindow] flushWindow]; return self; } - drawCellInside: theCell { int row, col; [self getRow: &row andCol: &col ofCell: theCell]; [self drawCellAt: row : col]; return self; } /* ========================================================================== */ /* The getSelectedCells: method is broken in two ways: it crashes InterfaceBuilder in test mode, and it only returns the cells when the list is in NX_LISTMODE. Hence, IKIconPath provides its own implementation to give correct behavior. */ - getSelectedCells: selection { int i, j; id aCell; if (selection == nil) selection = [[List alloc] init]; for (i = 0; i < numRows; i++) for (j = 0; j < numCols; j++) { aCell = [self cellAt: i : j]; if ([aCell state] != 0) [selection addObject: aCell]; } return selection; } /* ========================================================================== */ /* Resizing messes up editing since we no longer allow editing to abort. So we shut down any editing before resizing, then restart it afterwards. */ - setCellSize: (const NXSize *) size { id editingCell = [[window getFieldEditor: NO for: self] delegate]; [self endEditing]; [super setCellSize: size]; if ([cellList indexOf: editingCell] != NX_NOT_IN_LIST) [self editCell: editingCell]; return self; } - setIntercell: (const NXSize *) size { id editingCell = [[window getFieldEditor: NO for: self] delegate]; [self endEditing]; [super setIntercell: size]; if ([cellList indexOf: editingCell] != NX_NOT_IN_LIST) [self editCell: editingCell]; return self; } - sizeTo: (float) width : (float) height { id editingCell = [[window getFieldEditor: NO for: self] delegate]; [self endEditing]; [super sizeTo: width : height]; if ([cellList indexOf: editingCell] != NX_NOT_IN_LIST) [self editCell: editingCell]; return self; } /* ========================================================================== */ /* The methods below help to manipulate cells by delegate. The first finds the cell with a given delegate. The second extracts the delegate in the indicated column. */ - cellWithDelegate: delegate { id theCell; int i; for (i = 0; i < [cellList count]; i++) if ([theCell = [cellList objectAt: i] delegate] == delegate) return theCell; return nil; } - objectInColumn: (int) n { return [[self cellAt: 0 : n] delegate]; } /* ========================================================================== */ /* The stringValue of a cell matrix is the path of the active cell delegate. */ - (const char *) stringValue { id delegate = [selectedCell delegate]; return [delegate respondsTo: @selector(path)] ? [delegate path]: [super stringValue]; } /* ========================================================================== */ /* When acting as a dragging source, the standard IKIconPath allows all dragging operations. The programmer should override this method for different behavior. */ - (NXDragOperation) draggingSourceOperationMaskForLocal: (BOOL) isLocal { return NX_DragOperationAll; } /* ========================================================================== */ /* When acting as a dragging destination, the standard IKIconPath needs to account for mouse movements between cell boundaries. The variable 'current' is used to keep track of the cell under the mouse. */ - (NXDragOperation) draggingEntered: (id <NXDraggingInfo>) sender { last = current = nil; if ([self enteredNewCell: [sender draggingLocation]]) return [self cellEntered: sender]; else return NX_DragOperationNone; } - (NXDragOperation) draggingUpdated: (id <NXDraggingInfo>) sender { if ([self enteredNewCell: [sender draggingLocation]]) { if (last) [self cellExited: sender]; return [self cellEntered: sender]; } else return current ? [self cellUpdated: sender]: [super draggingUpdated: sender]; } - draggingExited: (id <NXDraggingInfo>) sender { last = current; [self cellExited: sender]; return nil; } /* ========================================================================== */ /* This method determines whether the mouse has entered a new cell during a drag operation. */ - (BOOL) enteredNewCell: (NXPoint) where { int row, col; id new; [self convertPoint: &where fromView: nil]; new = [self getRow: &row andCol: &col forPoint: &where]; if (new != current) { last = current; current = new; return YES; } else return NO; } /* ========================================================================== */ /* The routines below coordinate dragging with the appropriate cell delegate. If the delegate indicates that it will accept the drag, the cell is updated to reflect the drag operation. It reverts to its normal state when the cell is exited. */ - (NXDragOperation) cellEntered: (id <NXDraggingInfo>) sender { id delegate = [current delegate]; NXDragOperation op = NX_DragOperationNone; if (delegate && [current isDragAccepting] && [delegate respondsTo: @selector(draggingEntered:)] && ((op = [delegate draggingEntered: sender]) != NX_DragOperationNone)) { [current setImage: [delegate acceptingDragImage]]; [self drawCell: current]; } else current = nil; return op; } - (NXDragOperation) cellUpdated: (id <NXDraggingInfo>) sender { id delegate = [current delegate]; return [delegate respondsTo: @selector(draggingUpdated:)] ? [delegate draggingUpdated: sender]: [super draggingUpdated: sender]; } - cellExited: (id <NXDraggingInfo>) sender { id delegate; if ([delegate = [last delegate] respondsTo: @selector(draggingExited)]) [delegate draggingExited: sender]; [last setImage: [delegate image]]; [self drawCell: last]; return self; } /* ========================================================================== */ /* The standard drag protocol messages are simply passed on to the current cell delegate. The cell image is returned to its normal state. */ - (BOOL) prepareForDragOperation: (id <NXDraggingInfo>) sender { id delegate = [current delegate]; [current setImage: [delegate image]]; [self drawCell: current]; return [delegate respondsTo: @selector(prepareForDragOperation:)] ? [delegate prepareForDragOperation: sender]: YES; } - (BOOL) performDragOperation: (id <NXDraggingInfo>) sender { id delegate = [current delegate]; return [delegate respondsTo: @selector(performDragOperation:)] ? [delegate performDragOperation: sender]: NO; } - concludeDragOperation: (id <NXDraggingInfo>) sender { id delegate = [current delegate]; return [delegate respondsTo: @selector(concludeDragOperation:)] ? [delegate concludeDragOperation: sender]: nil; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.