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.