This is GroupBrowserMatrix.m in view mode; [Download] [Up]
/* Copyright 1993 Jeremy Slade. You are free to use all or any parts of the Locus project however you wish, just give credit where credit is due. The author (Jeremy Slade) shall not be held responsible for any damages that result out of use or misuse of any part of this project. */ /* Project: Locus Class: GroupBrowserMatrix Description: See GroupBrowserMatrix.h Original Author: Jeremy Slade Revision History: Created V.101 JGS Wed Feb 3 23:47:02 GMT-0700 1993 */ #import "GroupBrowserMatrix.h" #import "Dragging.h" #import "FolderController.h" #import "Globals.h" #import "Group.h" #import "ItemCell.h" #import "ItemList.h" #import <dpsclient/psops.h> #import <dpsclient/wraps.h> @implementation GroupBrowserMatrix // ------------------------------------------------------------------------- // Stuff used by mouseDown for control-dragging // ------------------------------------------------------------------------- // Cache windows shared by all GroupBrowserMatrices static id matrixCache, cellCache; #define MOVE_MASK NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK #define startTimer(timer) if (!timer) timer = NXBeginTimer(NULL, 0.1, 0.01); #define stopTimer(timer) if (timer) { \ NXEndTimer(timer); \ timer = NULL; \ } // ------------------------------------------------------------------------- // Creating, initializing methods // ------------------------------------------------------------------------- + initialize { [self setVersion:GroupBrowserMatrix_VERSION]; return ( self ); } // ------------------------------------------------------------------------- // Loading the Group // ------------------------------------------------------------------------- - setGroup:group /* 'Loads' the Group (a subclass of List) by replacing our cellList with it. Also resets selectedCell, etc. Returns the old cellList. */ { id oldList = cellList; NXSize size; DrawInfo drawInfo; cellList = group; selectedCell = nil; selectedRow = -1; selectedCol = -1; numRows = [cellList count]; numCols = 1; [group getDrawInfo:&drawInfo]; [ItemCell setDrawInfo:&drawInfo]; [[group objectAt:0] calcCellSize:&size]; size.width = bounds.size.width; [[self setCellSize:&size] sizeToCells]; return ( oldList ); } // ------------------------------------------------------------------------- // Responding to mouseDown events // ------------------------------------------------------------------------- - mouseDown:(NXEvent *)theEvent /* On the first click, and if no meta keys are used, it determines which cell was clicked and sends it a startTrackingAt:inView: message. This makes it possible for the cells to track the mouse even when in NX_LISTMODE. If the user control-clicks a cell, it allows them to drag the cell to a new location. */ { NXPoint pt; int row, col; NXRect cellFrame; id clickedCell; // Allow cells to track mouseDown events if ( !theEvent->flags ) { pt = theEvent->location; [self convertPoint:&pt fromView:nil]; [self getRow:&row andCol:&col forPoint:&pt]; clickedCell = [self cellAt:row :0]; [self selectCell:clickedCell]; [self getCellFrame:&cellFrame at:row :0]; if ( [clickedCell mouseDownAt:&pt frame:&cellFrame inView:self] ) { [self drawCellInside:clickedCell]; return ( self ); } } // Check for control-click if ( (theEvent->flags & NX_CONTROLMASK) && !(theEvent->flags & NX_SHIFTMASK) ) { [self controlDragMouse:theEvent]; return ( self ); } // Check for alternate-click if ( (theEvent->flags & NX_ALTERNATEMASK) ) { [self alternateDragMouse:theEvent]; return ( self ); } // Just act like normal return ( [super mouseDown:theEvent] ); } - controlDragMouse:(NXEvent *)theEvent /* Called when the user Control-clicks on a cell. This allows the cells to be reordered by dragging them to a new position. */ { NXPoint pt; int row, col, newRow; NXRect cellFrame, visibleRect, cellCacheBounds; int eventMask; float dy; NXEvent *event = NULL, peek; NXTrackingTimer *timer = NULL; BOOL scrolled = NO; id group; // Prepare the cell and matrix cache windows [self setupCacheWindows]; // Tell the window to receive mouse-dragged events eventMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK]; // Find the cell that got clicked and select it pt = theEvent->location; [self convertPoint:&pt fromView:nil]; //Convert from window's base coords [self getRow:&row andCol:&col forPoint:&pt]; activeCell = [self cellAt:row :0]; [self selectCell:activeCell]; [self getCellFrame:&cellFrame at:row :0]; // Do whatever's required for a single-click [self sendAction]; // draw a 'well' in place of the selected cell (see drawSelf::) [window disableFlushWindow]; [self lockFocus]; [[self drawSelf:&cellFrame :1] unlockFocus]; [window reenableFlushWindow]; // Copy what's currently visible into the matrix cache [[matrixCache contentView] 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 ); [[matrixCache contentView] unlockFocus]; // Image the cell into its cache [[cellCache contentView] lockFocus]; [[cellCache contentView] getBounds:&cellCacheBounds]; [activeCell drawSelf:&cellCacheBounds inView:[cellCache contentView]]; [[cellCache contentView] unlockFocus]; // save the mouse's location relative to the cell's origin dy = pt.y - cellFrame.origin.y; // All further drawing will be done in self [self lockFocus]; event = theEvent; while ( event->type != NX_MOUSEUP ) { // Loop until mouse-up event // Erase the active cell using inmage in 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 pt = event->location; [self convertPoint:&pt fromView:nil]; cellFrame.origin.y = pt.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 ( !NXContainsRect(&visibleRect,&cellFrame) && mFlags.autoscroll ) { [window disableFlushWindow]; [self scrollRectToVisible:&cellFrame]; [window reenableFlushWindow]; // Copy the new image to the matrix cache [[matrixCache contentView] 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 ); [[matrixCache contentView] 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 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 flush the display [window flushWindow]; // If we autoscrolled, flush any lingering window server events to // make the scrolling smooth if ( scrolled ) { NXPing(); scrolled = NO; } // Save the location, just in case we need it again pt = event->location; if ( ![NXApp peekNextEvent:MOVE_MASK into:&peek] ) { // No mouseMoved, mouseUp event avail, 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 set it if ( event->type == NX_TIMER ) event->location = pt; } // mouseUp, so stop any timer and unlock focus stopTimer ( timer ); [self unlockFocus]; // Find the cell under under the mouse's location pt = event->location; [self convertPoint:&pt fromView:nil]; if ( ![self getRow:&newRow andCol:&col forPoint:&pt] ) { // Mouse is out of bounds, so find the cell the active cell covers [self getRow:&newRow andCol:&col 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]; // Move the item to it's new location within the group group = cellList; [group setChanged:YES]; [group removeObject:activeCell]; [group insertObject:activeCell at:newRow]; // 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]; } // size to cells after all this shuffling and turn autodisplay back on [[self sizeToCells] setAutodisplay:YES]; } // No longer dragging the cell activeCell = nil; // Now redraw ourself [self display]; // set the event mask mask to normal [window setEventMask:eventMask]; return ( self ); } - alternateDragMouse:(NXEvent *)theEvent /* Sent when the user alternate-clicks on a cell. This allows the selected items to be dragged out of this window to another folder or another application. */ { id clickedCell; char *files = NULL, *buf; NXPoint pt, offs = { 24,24 }; int row, col; NXStream *stream; int len, mlen; int i, count, k; NXImage *img; id pboard; // Determine which cell the click happened in pt = theEvent->location; [self convertPoint:&pt fromView:nil]; [self getRow:&row andCol:&col forPoint:&pt]; clickedCell = [self cellAt:row :0]; // Make sure this cell is selected, then redraw [clickedCell setState:1]; [self drawCellInside:clickedCell]; // behave as a single click if ( !(theEvent->flags & NX_SHIFTMASK ) ) [self sendAction]; // Get list of all selected items, and put them into // tab-separated string stream = NXOpenMemory ( NULL, 0, NX_WRITEONLY ); if ( !stream ) { // Couldn't open stream NXBeep(); return ( self ); } k = 0; // This is the first item in the list for ( i=0, count=[cellList count]; i<count; i++ ) { if ( [[cellList objectAt:i] state] ) { if ( k>0 ) NXPutc ( stream, '\t' ); NXPrintf ( stream, "%s", [[cellList objectAt:i] path] ); k++; } } // Get pointer to list NXGetMemoryBuffer ( stream, &buf, &len, &mlen ); if ( buf ) files = NXCopyStringBuffer ( buf ); // Close the stream NXCloseMemory ( stream, NX_FREEBUFFER ); // Now drag the files if ( files ) { // Initiate a dragging sesssion pt.x -= 24.0; // Set pt to be center of icon pt.y += 24.0; if ( k > 1 ) img = [NXImage findImageNamed:"multiple"]; // Multiple Items else img = [[self selectedCell] image]; // Single Item pboard = [Pasteboard newName:NXDragPboard]; [pboard declareTypes:dragInTypes num:DRAGINTYPES owner:self]; [pboard writeType:NXFilenamePboardType data:files length:len]; [self dragImage:img at:&pt offset:&offs event:theEvent pasteboard:pboard source:self slideBack:YES]; } if ( files ) NX_FREE ( files ); return ( self ); } // ------------------------------------------------------------------------- // Making selections // ------------------------------------------------------------------------- - selectAll:sender { id ret = [super selectAll:sender]; [self determineSelectionCount]; return ( ret ); } - selectCellAt:(int)row :(int)col { id ret = [super selectCellAt:row :col]; [self determineSelectionCount]; return ( ret ); } - selectCell:aCell { id ret = [super selectCell:aCell]; [self determineSelectionCount]; return ( ret ); } - selectedCell /* Returns the currently selected cell. If there isn't one selected, or there is more than one, returns nil. Updates selectionCount to hold the number of cells selected */ { // Returns selectedCell only if a single cell is selected if ( selectionCount == 1 ) return ( selectedCell ); else return ( nil ); } - determineSelectionCount { register int i, count; // Reset selectionCount selectionCount = 0; // Get the number of selected cells for ( i=0, count=[self cellCount]; i<count; i++ ) if ( [[self cellAt:i :0] state] ) selectionCount++; return ( self ); } - selection /* Acts like selectedCell, but if multiple items are selected, it returns the selection list ( see -selectionList) */ { static id list = nil; if ( list ) { [list free]; list = nil; } if ( selectionCount == 1 ) return ( selectedCell ); else return ( [(list = [self selectionList]) count] ? list : nil ); } - selectionList /* Create an ItemList object and fill it with the items that are selected. Caller must free. */ { id theList = [[ItemList alloc] initCount:selectionCount], item; int i, count; for ( i=0, count=[self cellCount]; i<count; i++ ) if ( [(item = [self cellAt:i :0]) state] ) [theList addObject:item]; return ( theList ); } - (int)selectionCount { return ( selectionCount ); } // ------------------------------------------------------------------------- // Drawing the matrix // ------------------------------------------------------------------------- - drawSelf:(NXRect *)rects :(int)count /* Draws a 'well' in place of the cell being dragged */ { 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}; [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 ); } } // Flush windowserver events to smooth scrolling NXPing(); return ( self ); } - scrollCellToVisible:(int)row :(int)col /* Override default scrolling behavior to make it scroll the cell to the middle of the visible rectangle */ { NXRect visibleRect, cellFrame; [self getVisibleRect:&visibleRect]; [self getCellFrame:&cellFrame at:row :col]; if ( !NXContainsRect ( &visibleRect, &cellFrame ) ) { cellFrame.origin.y -= NX_HEIGHT(&visibleRect) / 2; cellFrame.size.height += NX_HEIGHT(&visibleRect); } return ( [self scrollRectToVisible:&cellFrame] ); } - sizeToCells { NXRect newFrame; [super sizeToCells]; // If in a ClipView, don't size any smaller than ClipView's width if ( [superview isKindOf:[ClipView class]] ) { [superview getDocVisibleRect:&newFrame]; newFrame.origin = frame.origin; newFrame.size.height = frame.size.height; newFrame.size.width = (newFrame.size.width > frame.size.width) ? newFrame.size.width : frame.size.width; [self setFrame:&newFrame]; } return ( self ); } // ------------------------------------------------------------------------- // Cache windows used to speed drawing // ------------------------------------------------------------------------- - setupCacheWindows /* Create and size the cache windows */ { 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 /* Create ( if cahceWindow = nil ) and size the specified cache window */ { 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 ( NX_WIDTH(&cacheFrame) != windowSize->width || NX_HEIGHT(&cacheFrame) != windowSize->height ) [cacheWindow sizeWindow:windowSize->width :windowSize->height]; } return ( cacheWindow ); } // ------------------------------------------------------------------------- // NXDraggingSource Protocol // ------------------------------------------------------------------------- - (NXDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal { return ( NX_DragOperationAll ); } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.