home *** CD-ROM | disk | FTP | other *** search
- // NiftyMatrix.m
- // By Jayson Adams, NeXT Developer Support Team
- // You may freely copy, distribute and reuse the code in this example.
- // NeXT disclaims any warranty of any kind, expressed or implied, as to its
- // fitness for any particular use.
-
- #import <dpsclient/psops.h>
- #import <dpsclient/wraps.h>
- #import <appkit/timer.h>
- #import <appkit/Cell.h>
- #import <appkit/Window.h>
- #import <appkit/Application.h>
-
- #import "NiftyMatrix.h"
-
-
- @implementation NiftyMatrix
-
-
- /* #defines stolen from Draw */
-
- #define startTimer(timer) if (!timer) timer = NXBeginTimer(NULL, 0.1, 0.01);
-
- #define stopTimer(timer) if (timer) { \
- NXEndTimer(timer); \
- timer = NULL; \
- }
-
- #define MOVE_MASK NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK
-
-
- /* instance methods */
-
- - free
- {
- [matrixCache free];
- [cellCache free];
-
- return [super free];
- }
-
- - mouseDown:(NXEvent *)theEvent
- {
- NXPoint mouseDownLocation, mouseUpLocation, mouseLocation;
- int eventMask, row, column, newRow;
- NXRect visibleRect, cellCacheBounds, cellFrame;
- id matrixCacheContentView, cellCacheContentView;
- float dy;
- NXEvent *event, peek;
- NXTrackingTimer *timer = NULL;
- BOOL scrolled = NO;
-
- /* if the Control key isn't down, show normal behavior */
- if (!(theEvent->flags & NX_CONTROLMASK)) {
- return [super mouseDown:theEvent];
- }
-
- /* prepare the cell and matrix cache windows */
- [self setupCacheWindows];
-
- /* we're now interested in mouse dragged events */
- eventMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK];
-
- /* find the cell that got clicked on and select it */
- mouseDownLocation = theEvent->location;
- [self convertPoint:&mouseDownLocation fromView:nil];
- [self getRow:&row andCol:&column forPoint:&mouseDownLocation];
- activeCell = [self cellAt:row :column];
- [self selectCell:activeCell];
- [self getCellFrame:&cellFrame at:row :column];
-
- /* do whatever's required for a single-click */
- [self sendAction];
-
- /* draw a "well" in place of the selected cell (see drawSelf::) */
- [self display:&cellFrame :1];
-
- /* copy what's currently visible into the matrix cache */
- matrixCacheContentView = [matrixCache contentView];
- [matrixCacheContentView lockFocus];
- [self getVisibleRect:&visibleRect];
- [self convertRect:&visibleRect toView:nil];
- PScomposite(NX_X(&visibleRect), NX_Y(&visibleRect),
- NX_WIDTH(&visibleRect), NX_HEIGHT(&visibleRect),
- [window gState], 0.0, NX_HEIGHT(&visibleRect), NX_COPY);
- [matrixCacheContentView unlockFocus];
-
- /* image the cell into its cache */
- cellCacheContentView = [cellCache contentView];
- [cellCacheContentView lockFocus];
- [cellCacheContentView getBounds:&cellCacheBounds];
- [activeCell drawSelf:&cellCacheBounds inView:cellCacheContentView];
- [cellCacheContentView unlockFocus];
-
- /* save the mouse's location relative to the cell's origin */
- dy = mouseDownLocation.y - cellFrame.origin.y;
-
- /* from now on we'll be drawing into ourself */
- [self lockFocus];
-
- event = theEvent;
- while (event->type != NX_MOUSEUP) {
-
- /* erase the active cell using the image in the matrix cache */
- [self getVisibleRect:&visibleRect];
- PScomposite(NX_X(&cellFrame), NX_HEIGHT(&visibleRect) -
- NX_Y(&cellFrame) + NX_Y(&visibleRect) -
- NX_HEIGHT(&cellFrame), NX_WIDTH(&cellFrame),
- NX_HEIGHT(&cellFrame), [matrixCache gState],
- NX_X(&cellFrame), NX_Y(&cellFrame) + NX_HEIGHT(&cellFrame),
- NX_COPY);
-
- /* move the active cell */
- mouseLocation = event->location;
- [self convertPoint:&mouseLocation fromView:nil];
- cellFrame.origin.y = mouseLocation.y - dy;
-
- /* constrain the cell's location to our bounds */
- if (NX_Y(&cellFrame) < NX_X(&bounds) ) {
- cellFrame.origin.y = NX_X(&bounds);
- } else if (NX_MAXY(&cellFrame) > NX_MAXY(&bounds)) {
- cellFrame.origin.y = NX_HEIGHT(&bounds) - NX_HEIGHT(&cellFrame);
- }
-
- /*
- * make sure the cell will be entirely visible in its new location (if
- * we're in a scrollView, it may not be)
- */
- if (!NXContainsRect(&visibleRect, &cellFrame) && mFlags.autoscroll) {
- /*
- * the cell won't be entirely visible, so scroll, dood, scroll, but
- * don't display on-screen yet
- */
- [window disableFlushWindow];
- [self scrollRectToVisible:&cellFrame];
- [window reenableFlushWindow];
-
- /* copy the new image to the matrix cache */
- [matrixCacheContentView lockFocus];
- [self getVisibleRect:&visibleRect];
- [self convertRectFromSuperview:&visibleRect];
- [self convertRect:&visibleRect toView:nil];
- PScomposite(NX_X(&visibleRect), NX_Y(&visibleRect),
- NX_WIDTH(&visibleRect), NX_HEIGHT(&visibleRect),
- [window gState], 0.0, NX_HEIGHT(&visibleRect),
- NX_COPY);
- [matrixCacheContentView unlockFocus];
-
- /*
- * note that we scrolled and start generating timer events for
- * autoscrolling
- */
- scrolled = YES;
- startTimer(timer);
- } else {
- /* no scrolling, so stop any timer */
- stopTimer(timer);
- }
-
- /* composite the active cell's image on top of ourself */
- PScomposite(0.0, 0.0, NX_WIDTH(&cellFrame), NX_HEIGHT(&cellFrame),
- [cellCache gState], NX_X(&cellFrame),
- NX_Y(&cellFrame) + NX_HEIGHT(&cellFrame), NX_COPY);
-
- /* now show what we've done */
- [window flushWindow];
-
- /*
- * if we autoscrolled, flush any lingering window server events to make
- * the scrolling smooth
- */
- if (scrolled) {
- NXPing();
- scrolled = NO;
- }
-
- /* save the current mouse location, just in case we need it again */
- mouseLocation = event->location;
-
- if (![NXApp peekNextEvent:MOVE_MASK into:&peek]) {
- /*
- * no mouseMoved or mouseUp event immediately avaiable, so take
- * mouseMoved, mouseUp, or timer
- */
- event = [NXApp getNextEvent:MOVE_MASK|NX_TIMERMASK];
- } else {
- /* get the mouseMoved or mouseUp event in the queue */
- event = [NXApp getNextEvent:MOVE_MASK];
- }
-
- /* if a timer event, mouse location isn't valid, so we'll set it */
- if (event->type == NX_TIMER) {
- event->location = mouseLocation;
- }
- }
-
- /* mouseUp, so stop any timer and unlock focus */
- stopTimer(timer);
- [self unlockFocus];
-
- /* find the cell under the mouse's location */
- mouseUpLocation = event->location;
- [self convertPoint:&mouseUpLocation fromView:nil];
- if (![self getRow:&newRow andCol:&column forPoint:&mouseUpLocation]) {
- /* mouse is out of bounds, so find the cell the active cell covers */
- [self getRow:&newRow andCol:&column forPoint:&(cellFrame.origin)];
- }
-
- /* we need to shuffle cells if the active cell's going to a new location */
- if (newRow != row) {
- /* no autodisplay while we move cells around */
- [self setAutodisplay:NO];
- if (newRow > row) {
- /* adjust selected row if before new active cell location */
- if (selectedRow <= newRow) {
- selectedRow--;
- }
-
- /*
- * push all cells above the active cell's new location up one row so
- * that we fill the vacant spot
- */
- while (row++ < newRow) {
- cell = [self cellAt:row :0];
- [self putCell:cell at:(row - 1) :0];
- }
- /* now place the active cell in its new home */
- [self putCell:activeCell at:newRow :0];
- } else if (newRow < row) {
- /* adjust selected row if after new active cell location */
- if (selectedRow >= newRow) {
- selectedRow++;
- }
-
- /*
- * push all cells below the active cell's new location down one row
- * so that we fill the vacant spot
- */
- while (row-- > newRow) {
- cell = [self cellAt:row :0];
- [self putCell:cell at:(row + 1) :0];
- }
- /* now place the active cell in its new home */
- [self putCell:activeCell at:newRow :0];
- }
-
- /* if the active cell is selected, note its new row */
- if ([activeCell state]) {
- selectedRow = newRow;
- }
-
- /* make sure the active cell's visible if we're autoscrolling */
- if (mFlags.autoscroll) {
- [self scrollCellToVisible:newRow :0];
- }
-
- /* no longer dragging the cell */
- activeCell = 0;
-
- /* size to cells after all this shuffling and turn autodisplay back on */
- [[self sizeToCells] setAutodisplay:YES];
- } else {
- /* no longer dragging the cell */
- activeCell = 0;
- }
-
- /* now redraw ourself */
- [self display];
-
- /* set the event mask to normal */
- [window setEventMask:eventMask];
-
- return self;
- }
-
- - drawSelf:(NXRect *)rects :(int)count
- {
- int row, col;
- NXRect cellBorder;
- int sides[] = {NX_XMIN, NX_YMIN, NX_XMAX, NX_YMAX, NX_XMIN,
- NX_YMIN};
- float grays[] = {NX_DKGRAY, NX_DKGRAY, NX_WHITE, NX_WHITE, NX_BLACK,
- NX_BLACK};
-
- /* do the regular drawing */
- [super drawSelf:rects :count];
-
- /* draw a "well" if the user's dragging a cell */
- if (activeCell) {
- /* get the cell's frame */
- [self getRow:&row andCol:&col ofCell:activeCell];
- [self getCellFrame:&cellBorder at:row :col];
-
- /* draw the well */
- if (NXIntersectsRect(&cellBorder, &(rects[0]))) {
- NXDrawTiledRects(&cellBorder, (NXRect *)0, sides, grays, 6);
- PSsetgray(0.17);
- NXRectFill(&cellBorder);
- }
- }
-
- return self;
- }
-
- - setupCacheWindows
- {
- NXRect visibleRect;
-
- /* create the matrix cache window */
- [self getVisibleRect:&visibleRect];
- matrixCache = [self sizeCacheWindow:matrixCache to:&(visibleRect.size)];
-
- /* create the cell cache window */
- cellCache = [self sizeCacheWindow:cellCache to:&cellSize];
-
- return self;
- }
-
- - sizeCacheWindow:cacheWindow to:(NXSize *)windowSize
- {
- NXRect cacheFrame;
-
- if (!cacheWindow) {
- /* create the cache window if it doesn't exist */
- cacheFrame.origin.x = cacheFrame.origin.y = 0.0;
- cacheFrame.size = *windowSize;
- cacheWindow = [[[Window alloc] initContent:&cacheFrame
- style:NX_PLAINSTYLE
- backing:NX_RETAINED
- buttonMask:0
- defer:NO] reenableDisplay];
- /* flip the contentView since we are flipped */
- [[cacheWindow contentView] setFlipped:YES];
- } else {
- /* make sure the cache window's the right size */
- [cacheWindow getFrame:&cacheFrame];
- if (cacheFrame.size.width != windowSize->width ||
- cacheFrame.size.height != windowSize->height) {
- [cacheWindow sizeWindow:windowSize->width
- :windowSize->height];
- }
- }
-
- return cacheWindow;
- }
-
- @end