|
|
1.1 ! root 1: // NiftyMatrix.m ! 2: // By Jayson Adams, NeXT Developer Support Team ! 3: // You may freely copy, distribute and reuse the code in this example. ! 4: // NeXT disclaims any warranty of any kind, expressed or implied, as to its ! 5: // fitness for any particular use. ! 6: ! 7: #import <dpsclient/psops.h> ! 8: #import <dpsclient/wraps.h> ! 9: #import <appkit/timer.h> ! 10: #import <appkit/Cell.h> ! 11: #import <appkit/Window.h> ! 12: #import <appkit/Application.h> ! 13: ! 14: #import "NiftyMatrix.h" ! 15: ! 16: ! 17: @implementation NiftyMatrix ! 18: ! 19: ! 20: /* #defines stolen from Draw */ ! 21: ! 22: #define startTimer(timer) if (!timer) timer = NXBeginTimer(NULL, 0.1, 0.01); ! 23: ! 24: #define stopTimer(timer) if (timer) { \ ! 25: NXEndTimer(timer); \ ! 26: timer = NULL; \ ! 27: } ! 28: ! 29: #define MOVE_MASK NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK ! 30: ! 31: ! 32: /* instance methods */ ! 33: ! 34: - free ! 35: { ! 36: [matrixCache free]; ! 37: [cellCache free]; ! 38: ! 39: return [super free]; ! 40: } ! 41: ! 42: - mouseDown:(NXEvent *)theEvent ! 43: { ! 44: NXPoint mouseDownLocation, mouseUpLocation, mouseLocation; ! 45: int eventMask, row, column, newRow; ! 46: NXRect visibleRect, cellCacheBounds, cellFrame; ! 47: id matrixCacheContentView, cellCacheContentView; ! 48: float dy; ! 49: NXEvent *event, peek; ! 50: NXTrackingTimer *timer = NULL; ! 51: BOOL scrolled = NO; ! 52: ! 53: /* if the Control key isn't down, show normal behavior */ ! 54: if (!(theEvent->flags & NX_CONTROLMASK)) { ! 55: return [super mouseDown:theEvent]; ! 56: } ! 57: ! 58: /* prepare the cell and matrix cache windows */ ! 59: [self setupCacheWindows]; ! 60: ! 61: /* we're now interested in mouse dragged events */ ! 62: eventMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK]; ! 63: ! 64: /* find the cell that got clicked on and select it */ ! 65: mouseDownLocation = theEvent->location; ! 66: [self convertPoint:&mouseDownLocation fromView:nil]; ! 67: [self getRow:&row andCol:&column forPoint:&mouseDownLocation]; ! 68: activeCell = [self cellAt:row :column]; ! 69: [self selectCell:activeCell]; ! 70: [self getCellFrame:&cellFrame at:row :column]; ! 71: ! 72: /* do whatever's required for a single-click */ ! 73: [self sendAction]; ! 74: ! 75: /* draw a "well" in place of the selected cell (see drawSelf::) */ ! 76: [self display:&cellFrame :1]; ! 77: ! 78: /* copy what's currently visible into the matrix cache */ ! 79: matrixCacheContentView = [matrixCache contentView]; ! 80: [matrixCacheContentView lockFocus]; ! 81: [self getVisibleRect:&visibleRect]; ! 82: [self convertRect:&visibleRect toView:nil]; ! 83: PScomposite(NX_X(&visibleRect), NX_Y(&visibleRect), ! 84: NX_WIDTH(&visibleRect), NX_HEIGHT(&visibleRect), ! 85: [window gState], 0.0, NX_HEIGHT(&visibleRect), NX_COPY); ! 86: [matrixCacheContentView unlockFocus]; ! 87: ! 88: /* image the cell into its cache */ ! 89: cellCacheContentView = [cellCache contentView]; ! 90: [cellCacheContentView lockFocus]; ! 91: [cellCacheContentView getBounds:&cellCacheBounds]; ! 92: [activeCell drawSelf:&cellCacheBounds inView:cellCacheContentView]; ! 93: [cellCacheContentView unlockFocus]; ! 94: ! 95: /* save the mouse's location relative to the cell's origin */ ! 96: dy = mouseDownLocation.y - cellFrame.origin.y; ! 97: ! 98: /* from now on we'll be drawing into ourself */ ! 99: [self lockFocus]; ! 100: ! 101: event = theEvent; ! 102: while (event->type != NX_MOUSEUP) { ! 103: ! 104: /* erase the active cell using the image in the matrix cache */ ! 105: [self getVisibleRect:&visibleRect]; ! 106: PScomposite(NX_X(&cellFrame), NX_HEIGHT(&visibleRect) - ! 107: NX_Y(&cellFrame) + NX_Y(&visibleRect) - ! 108: NX_HEIGHT(&cellFrame), NX_WIDTH(&cellFrame), ! 109: NX_HEIGHT(&cellFrame), [matrixCache gState], ! 110: NX_X(&cellFrame), NX_Y(&cellFrame) + NX_HEIGHT(&cellFrame), ! 111: NX_COPY); ! 112: ! 113: /* move the active cell */ ! 114: mouseLocation = event->location; ! 115: [self convertPoint:&mouseLocation fromView:nil]; ! 116: cellFrame.origin.y = mouseLocation.y - dy; ! 117: ! 118: /* constrain the cell's location to our bounds */ ! 119: if (NX_Y(&cellFrame) < NX_X(&bounds) ) { ! 120: cellFrame.origin.y = NX_X(&bounds); ! 121: } else if (NX_MAXY(&cellFrame) > NX_MAXY(&bounds)) { ! 122: cellFrame.origin.y = NX_HEIGHT(&bounds) - NX_HEIGHT(&cellFrame); ! 123: } ! 124: ! 125: /* ! 126: * make sure the cell will be entirely visible in its new location (if ! 127: * we're in a scrollView, it may not be) ! 128: */ ! 129: if (!NXContainsRect(&visibleRect, &cellFrame) && mFlags.autoscroll) { ! 130: /* ! 131: * the cell won't be entirely visible, so scroll, dood, scroll, but ! 132: * don't display on-screen yet ! 133: */ ! 134: [window disableFlushWindow]; ! 135: [self scrollRectToVisible:&cellFrame]; ! 136: [window reenableFlushWindow]; ! 137: ! 138: /* copy the new image to the matrix cache */ ! 139: [matrixCacheContentView lockFocus]; ! 140: [self getVisibleRect:&visibleRect]; ! 141: [self convertRectFromSuperview:&visibleRect]; ! 142: [self convertRect:&visibleRect toView:nil]; ! 143: PScomposite(NX_X(&visibleRect), NX_Y(&visibleRect), ! 144: NX_WIDTH(&visibleRect), NX_HEIGHT(&visibleRect), ! 145: [window gState], 0.0, NX_HEIGHT(&visibleRect), ! 146: NX_COPY); ! 147: [matrixCacheContentView unlockFocus]; ! 148: ! 149: /* ! 150: * note that we scrolled and start generating timer events for ! 151: * autoscrolling ! 152: */ ! 153: scrolled = YES; ! 154: startTimer(timer); ! 155: } else { ! 156: /* no scrolling, so stop any timer */ ! 157: stopTimer(timer); ! 158: } ! 159: ! 160: /* composite the active cell's image on top of ourself */ ! 161: PScomposite(0.0, 0.0, NX_WIDTH(&cellFrame), NX_HEIGHT(&cellFrame), ! 162: [cellCache gState], NX_X(&cellFrame), ! 163: NX_Y(&cellFrame) + NX_HEIGHT(&cellFrame), NX_COPY); ! 164: ! 165: /* now show what we've done */ ! 166: [window flushWindow]; ! 167: ! 168: /* ! 169: * if we autoscrolled, flush any lingering window server events to make ! 170: * the scrolling smooth ! 171: */ ! 172: if (scrolled) { ! 173: NXPing(); ! 174: scrolled = NO; ! 175: } ! 176: ! 177: /* save the current mouse location, just in case we need it again */ ! 178: mouseLocation = event->location; ! 179: ! 180: if (![NXApp peekNextEvent:MOVE_MASK into:&peek]) { ! 181: /* ! 182: * no mouseMoved or mouseUp event immediately avaiable, so take ! 183: * mouseMoved, mouseUp, or timer ! 184: */ ! 185: event = [NXApp getNextEvent:MOVE_MASK|NX_TIMERMASK]; ! 186: } else { ! 187: /* get the mouseMoved or mouseUp event in the queue */ ! 188: event = [NXApp getNextEvent:MOVE_MASK]; ! 189: } ! 190: ! 191: /* if a timer event, mouse location isn't valid, so we'll set it */ ! 192: if (event->type == NX_TIMER) { ! 193: event->location = mouseLocation; ! 194: } ! 195: } ! 196: ! 197: /* mouseUp, so stop any timer and unlock focus */ ! 198: stopTimer(timer); ! 199: [self unlockFocus]; ! 200: ! 201: /* find the cell under the mouse's location */ ! 202: mouseUpLocation = event->location; ! 203: [self convertPoint:&mouseUpLocation fromView:nil]; ! 204: if (![self getRow:&newRow andCol:&column forPoint:&mouseUpLocation]) { ! 205: /* mouse is out of bounds, so find the cell the active cell covers */ ! 206: [self getRow:&newRow andCol:&column forPoint:&(cellFrame.origin)]; ! 207: } ! 208: ! 209: /* we need to shuffle cells if the active cell's going to a new location */ ! 210: if (newRow != row) { ! 211: /* no autodisplay while we move cells around */ ! 212: [self setAutodisplay:NO]; ! 213: if (newRow > row) { ! 214: /* adjust selected row if before new active cell location */ ! 215: if (selectedRow <= newRow) { ! 216: selectedRow--; ! 217: } ! 218: ! 219: /* ! 220: * push all cells above the active cell's new location up one row so ! 221: * that we fill the vacant spot ! 222: */ ! 223: while (row++ < newRow) { ! 224: cell = [self cellAt:row :0]; ! 225: [self putCell:cell at:(row - 1) :0]; ! 226: } ! 227: /* now place the active cell in its new home */ ! 228: [self putCell:activeCell at:newRow :0]; ! 229: } else if (newRow < row) { ! 230: /* adjust selected row if after new active cell location */ ! 231: if (selectedRow >= newRow) { ! 232: selectedRow++; ! 233: } ! 234: ! 235: /* ! 236: * push all cells below the active cell's new location down one row ! 237: * so that we fill the vacant spot ! 238: */ ! 239: while (row-- > newRow) { ! 240: cell = [self cellAt:row :0]; ! 241: [self putCell:cell at:(row + 1) :0]; ! 242: } ! 243: /* now place the active cell in its new home */ ! 244: [self putCell:activeCell at:newRow :0]; ! 245: } ! 246: ! 247: /* if the active cell is selected, note its new row */ ! 248: if ([activeCell state]) { ! 249: selectedRow = newRow; ! 250: } ! 251: ! 252: /* make sure the active cell's visible if we're autoscrolling */ ! 253: if (mFlags.autoscroll) { ! 254: [self scrollCellToVisible:newRow :0]; ! 255: } ! 256: ! 257: /* no longer dragging the cell */ ! 258: activeCell = 0; ! 259: ! 260: /* size to cells after all this shuffling and turn autodisplay back on */ ! 261: [[self sizeToCells] setAutodisplay:YES]; ! 262: } else { ! 263: /* no longer dragging the cell */ ! 264: activeCell = 0; ! 265: } ! 266: ! 267: /* now redraw ourself */ ! 268: [self display]; ! 269: ! 270: /* set the event mask to normal */ ! 271: [window setEventMask:eventMask]; ! 272: ! 273: return self; ! 274: } ! 275: ! 276: - drawSelf:(NXRect *)rects :(int)count ! 277: { ! 278: int row, col; ! 279: NXRect cellBorder; ! 280: int sides[] = {NX_XMIN, NX_YMIN, NX_XMAX, NX_YMAX, NX_XMIN, ! 281: NX_YMIN}; ! 282: float grays[] = {NX_DKGRAY, NX_DKGRAY, NX_WHITE, NX_WHITE, NX_BLACK, ! 283: NX_BLACK}; ! 284: ! 285: /* do the regular drawing */ ! 286: [super drawSelf:rects :count]; ! 287: ! 288: /* draw a "well" if the user's dragging a cell */ ! 289: if (activeCell) { ! 290: /* get the cell's frame */ ! 291: [self getRow:&row andCol:&col ofCell:activeCell]; ! 292: [self getCellFrame:&cellBorder at:row :col]; ! 293: ! 294: /* draw the well */ ! 295: if (NXIntersectsRect(&cellBorder, &(rects[0]))) { ! 296: NXDrawTiledRects(&cellBorder, (NXRect *)0, sides, grays, 6); ! 297: PSsetgray(0.17); ! 298: NXRectFill(&cellBorder); ! 299: } ! 300: } ! 301: ! 302: return self; ! 303: } ! 304: ! 305: - setupCacheWindows ! 306: { ! 307: NXRect visibleRect; ! 308: ! 309: /* create the matrix cache window */ ! 310: [self getVisibleRect:&visibleRect]; ! 311: matrixCache = [self sizeCacheWindow:matrixCache to:&(visibleRect.size)]; ! 312: ! 313: /* create the cell cache window */ ! 314: cellCache = [self sizeCacheWindow:cellCache to:&cellSize]; ! 315: ! 316: return self; ! 317: } ! 318: ! 319: - sizeCacheWindow:cacheWindow to:(NXSize *)windowSize ! 320: { ! 321: NXRect cacheFrame; ! 322: ! 323: if (!cacheWindow) { ! 324: /* create the cache window if it doesn't exist */ ! 325: cacheFrame.origin.x = cacheFrame.origin.y = 0.0; ! 326: cacheFrame.size = *windowSize; ! 327: cacheWindow = [[[Window alloc] initContent:&cacheFrame ! 328: style:NX_PLAINSTYLE ! 329: backing:NX_RETAINED ! 330: buttonMask:0 ! 331: defer:NO] reenableDisplay]; ! 332: /* flip the contentView since we are flipped */ ! 333: [[cacheWindow contentView] setFlipped:YES]; ! 334: } else { ! 335: /* make sure the cache window's the right size */ ! 336: [cacheWindow getFrame:&cacheFrame]; ! 337: if (cacheFrame.size.width != windowSize->width || ! 338: cacheFrame.size.height != windowSize->height) { ! 339: [cacheWindow sizeWindow:windowSize->width ! 340: :windowSize->height]; ! 341: } ! 342: } ! 343: ! 344: return cacheWindow; ! 345: } ! 346: ! 347: @end
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.