Annotation of Examples/AppKit/ScrollDoodScroll/NiftyMatrix.m, revision 1.1.1.1

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

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.