home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.3 (Developer)…68k, x86, SPARC, PA-RISC] / NeXTSTEP 3.3 Dev Intel.iso / NextDeveloper / Examples / AppKit / ScrollDoodScroll / NiftyMatrix.m < prev    next >
Encoding:
Text File  |  1992-05-29  |  10.2 KB  |  347 lines

  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