This is MiscDraggableCellMatrix.m in view mode; [Download] [Up]
// Suppress compiler warning about rcsid being unused, yet prevent assembler
// code for this function from being produced.
inline extern const char *suppressCompilerWarning(void)
{
static const char *rcsid = "$Id$ cc: "__FILE__" "__DATE__" "__TIME__;
return rcsid;
}
//
// ------------- MiscDraggableCellMatrix Class Implementation -----------------
//
// NSMatrix subclass that supports reordering cells be Control-dragging.
//
// Written by Art Isbell (adapted from NiftyMatrix by Jayson Adams, NeXT
// Developer Support Team)
// Copyright 1996 by Art Isbell.
// Version 1.0. All rights reserved.
//
// This notice may not be removed from this source code.
//
// This object is included in the MiscKit by permission from the author
// and its use is governed by the MiscKit license, found in the file
// "License.rtf" in the MiscKit distribution. Please refer to that file
// for a list of all applicable permissions and restrictions.
//
// ----------------------------------------------------------------------------
//
// ----------------------------- Header Files ---------------------------------
#import <AppKit/AppKit.h>
#ifndef NOMISC
#import <misckit/MiscDraggableCellMatrix.h>
#else NOMISC
#import "MiscDraggableCellMatrix.h"
#endif NOMISC
// ---------------- Typedef, Struct, and Union Declarations -------------------
// --------------------- Constant and Enum Definitions ------------------------
NSString *MiscMatrixRowDidMove = @"MatrixRowMoved";
// ------------------------- Function Declarations ----------------------------
@interface MiscDraggableCellMatrix(Private)
// ---------------------- Private Method Declarations -------------------------
- (void)_setupCacheWindows;
- (NSWindow *)_sizeCacheWindow:(NSWindow *)aCacheWindow to:(NSSize)aWindowSize;
- (void)_cacheCellWithFrame:(NSRect)aCellFrame;
- (void)_cacheMatrixWithVisibleRect:(NSRect)aVisibleRect;
- (NSEvent *)_nextEventWithLocation:(NSPoint)aLocation;
- (void)_moveActiveCell:(NSCell *)aCell fromRow:(int)aRow toRow:(int)aNewRow;
- (void)_postNotificationWithOldRow:(int)aRow newRow:(int)aNewRow;
- (void)_highlightNewRow:(int)newRow;
@end
@implementation MiscDraggableCellMatrix
// ---------------------- Factory Method Definitions --------------------------
// ---------------- Overridden Instance Method Definitions --------------------
- (void)dealloc
{
[_matrixCache release];
[_cellCache release];
[super dealloc];
}
// This is a terribly complex method that needs to be simplified, but it was
// converted from working NEXTSTEP code written by another, so simplifying it
// would run the high risk of breaking it.
- (void)mouseDown:(NSEvent *)theEvent
{
// If the Control key is down, show new behavior.
if (([theEvent modifierFlags] & NSControlKeyMask) == NSControlKeyMask)
{
int rowL;
int columnL;
int newRowL;
int cellCacheGStateL;
int matrixCacheGStateL;
float dyL;
float boundsMinYL;
float cellMinXL;
float cellMinYL;
float cellHeightL;
float cellWidthL;
BOOL acceptsMouseMovedEventsL;
BOOL arePeriodicEventsOccurringL;
NSPoint mouseDownLocationL;
NSRect boundsL;
NSRect cellFrameL;
NSWindow *windowL;
NSView *superviewL;
NSEvent *eventL = theEvent;
// Post bogus mouse up event so that message to super's mouseDown: will
// return.
[NSApp postEvent:[NSEvent mouseEventWithType:NSLeftMouseUp
location:[eventL locationInWindow]
modifierFlags:[eventL modifierFlags]
timestamp:[eventL timestamp]
windowNumber:[eventL windowNumber]
context:[eventL context]
eventNumber:[eventL eventNumber]
clickCount:1 pressure:0.0]
atStart:YES];
// Select and highlight cell.
[super mouseDown:eventL];
// Prepare the cell and matrix cache windows.
[self _setupCacheWindows];
matrixCacheGStateL = [_matrixCache gState];
cellCacheGStateL = [_cellCache gState];
windowL = [self window];
acceptsMouseMovedEventsL = [windowL acceptsMouseMovedEvents];
// We're now interested in mouse dragged events.
if (!acceptsMouseMovedEventsL)
{
[windowL setAcceptsMouseMovedEvents:YES];
}
mouseDownLocationL = [self convertPoint:[eventL locationInWindow]
fromView:nil];
// Find the cell that got clicked on and select it.
[self getRow:&rowL column:&columnL forPoint:mouseDownLocationL];
_activeCell = [self cellAtRow:rowL column:columnL];
cellFrameL = [self cellFrameAtRow:rowL column:columnL];
// Save the mouse's location relative to the cell's origin.
dyL = mouseDownLocationL.y - cellFrameL.origin.y;
// Image the cell into its cache.
[self _cacheCellWithFrame:[self convertRect:cellFrameL toView:nil]];
// Draw a "well" in place of the selected cell (see drawRect:).
[self displayRect:cellFrameL];
// Copy what's currently visible into the matrix cache.
[self _cacheMatrixWithVisibleRect:[self convertRect:[self visibleRect]
toView:nil]];
// Initialize some variables used in "do" loop.
arePeriodicEventsOccurringL = NO;
boundsL = [self bounds];
superviewL = [self superview];
boundsMinYL = NSMinY(boundsL);
cellMinXL = NSMinX(cellFrameL);
cellMinYL = NSMinY(cellFrameL);
cellHeightL = NSHeight(cellFrameL);
cellWidthL = NSWidth(cellFrameL);
// From now on we'll be drawing into ourself.
[self lockFocus];
do
{
BOOL didScrollL;
NSPoint eventLocationL;
NSPoint mouseLocationL;
NSRect visibleRectL;
// If this isn't the mouse down event, erase the active cell using
// the image in the matrix cache.
if (eventL != theEvent)
{
visibleRectL = [self visibleRect];
PScomposite(cellMinXL, NSHeight(visibleRectL) - cellMinYL +
NSMinY(visibleRectL) - cellHeightL, cellWidthL,
cellHeightL, matrixCacheGStateL, cellMinXL,
cellMinYL + cellHeightL, NSCompositeCopy);
}
eventLocationL = [eventL locationInWindow];
mouseLocationL = [self convertPoint:eventLocationL fromView:nil];
// Move the active cell.
cellMinYL = cellFrameL.origin.y = mouseLocationL.y - dyL;
// Constrain the cell's location to our bounds.
if (cellMinYL < boundsMinYL)
{
cellMinYL = cellFrameL.origin.y = boundsMinYL;
}
else if (NSMaxY(boundsL) < NSMaxY(cellFrameL))
{
cellMinYL = cellFrameL.origin.y = NSHeight(boundsL) -
cellHeightL;
}
// Ensure the cell will be entirely visible in its new location
// (if we're in a scrollView, it may not be).
didScrollL = (BOOL)([self isAutoscroll] &&
!NSContainsRect(visibleRectL, cellFrameL));
if (didScrollL)
{
// The cell won't be entirely visible, so scroll, dood, scroll.
[self scrollRectToVisible:cellFrameL];
// Copy the new image to the matrix cache.
visibleRectL = [self convertRect:[self visibleRect]
fromView:superviewL];
[self _cacheMatrixWithVisibleRect:[self
convertRect:visibleRectL toView:nil]];
// Start generating timer events for autoscrolling if this
// isn't the mouse down event to prevent continuous scrolling
// when a partially-visible cell is clicked.
if ((eventL != theEvent) && !arePeriodicEventsOccurringL)
{
[NSEvent startPeriodicEventsAfterDelay:0.1
withPeriod:0.03];
arePeriodicEventsOccurringL = YES;
}
}
else
{
if (arePeriodicEventsOccurringL)
{
// No scrolling, so stop any timer.
[NSEvent stopPeriodicEvents];
arePeriodicEventsOccurringL = NO;
}
}
// Composite the active cell's image on top of ourself.
PScomposite(0.0, 0.0, cellWidthL, cellHeightL, cellCacheGStateL,
cellMinXL, cellMinYL + cellHeightL, NSCompositeCopy);
// Now show what we've done.
[windowL flushWindow];
// If we autodidScroll, flush any lingering window server events to
// make the scrolling smooth.
if (didScrollL)
{
[[NSDPSContext currentContext] wait];
didScrollL = NO;
}
// If no mouse up or dragged event is available now, wait for and
// get next mouse up, mouse dragged, or periodic event.
eventL = [self _nextEventWithLocation:eventLocationL];
} while ([eventL type] != NSLeftMouseUp);
// MouseUp, so unlock focus and stop any timer.
[self unlockFocus];
if (arePeriodicEventsOccurringL)
{
[NSEvent stopPeriodicEvents];
arePeriodicEventsOccurringL = NO;
}
[windowL discardEventsMatchingMask:NSAnyEventMask beforeEvent:eventL];
// Do whatever's required for a single-click.
[self sendAction];
// If mouse is out of bounds, find the cell the active cell covers.
if (![self getRow:&newRowL column:&columnL
forPoint:[self convertPoint:[eventL locationInWindow]
fromView:nil]])
{
[self getRow:&newRowL column:&columnL forPoint:cellFrameL.origin];
}
// We need to shuffle cells if the active cell's going to a new
// location.
if (newRowL != rowL)
{
[self _moveActiveCell:_activeCell fromRow:rowL toRow:newRowL];
[self _postNotificationWithOldRow:rowL newRow:newRowL];
[self _highlightNewRow:newRowL];
}
// No longer dragging the cell.
_activeCell = nil;
// Now redraw ourself.
[self setNeedsDisplay:YES];
// Set the event mask to normal.
if (acceptsMouseMovedEventsL != [windowL acceptsMouseMovedEvents])
{
[windowL setAcceptsMouseMovedEvents:acceptsMouseMovedEventsL];
}
}
else
{
[super mouseDown:theEvent];
}
}
- (void)drawRect:(NSRect)aRect
{
// Do the regular drawing.
[super drawRect:aRect];
// Draw a "well" if the user's dragging a cell.
if (_activeCell != nil)
{
int rowL;
int columnL;
NSRect cellBorderL;
// Get the cell's frame.
[self getRow:&rowL column:&columnL ofCell:_activeCell];
cellBorderL = [self cellFrameAtRow:rowL column:columnL];
// Draw the well.
if (!NSIsEmptyRect(NSIntersectionRect(cellBorderL, aRect)))
{
NSRectEdge sides[] = {NSMinXEdge, NSMinYEdge, NSMaxXEdge,
NSMaxYEdge, NSMinXEdge, NSMinYEdge};
float grays[] = {NSDarkGray, NSDarkGray, NSWhite, NSWhite, NSBlack,
NSBlack};
cellBorderL = NSDrawTiledRects(cellBorderL, NSZeroRect, sides,
grays, 6);
[[NSColor colorWithCalibratedWhite:0.17 alpha:1.0] set];
NSRectFill(cellBorderL);
}
}
}
// -------------------- New Instance Method Definitions -----------------------
// ----------------- Delegate Instance Method Definitions ---------------------
@end
@implementation MiscDraggableCellMatrix(Private)
// ---------------------- Private Method Definitions --------------------------
- (void)_setupCacheWindows
{
NSRect visibleRectL = [self visibleRect];
// Create the matrix cache window.
_matrixCache = [self _sizeCacheWindow:_matrixCache to:visibleRectL.size];
// Create the cell cache window.
_cellCache = [self _sizeCacheWindow:_cellCache to:[self cellSize]];
}
- (NSWindow *)_sizeCacheWindow:(NSWindow *)aCacheWindow to:(NSSize)aWindowSize
{
NSRect cacheFrameL;
if (aCacheWindow == nil)
{
// Create the cache window if it doesn't exist.
cacheFrameL = NSMakeRect(0.0, 0.0, aWindowSize.width,
aWindowSize.height);
aCacheWindow = [[NSWindow allocWithZone:[self zone]]
initWithContentRect:cacheFrameL styleMask:NSBorderlessWindowMask
backing:NSBackingStoreRetained defer:NO];
// Display window so that it's available for drawing into.
[aCacheWindow display];
}
else
{
// Ensure the cache window's the right size.
cacheFrameL = [aCacheWindow frame];
if (NSWidth(cacheFrameL) != aWindowSize.width ||
NSHeight(cacheFrameL) != aWindowSize.height)
{
[aCacheWindow setContentSize:aWindowSize];
}
}
return aCacheWindow;
}
- (void)_cacheCellWithFrame:(NSRect)aCellFrame
{
NSView *cellCacheContentViewL = [_cellCache contentView];
[cellCacheContentViewL lockFocus];
PScomposite(NSMinX(aCellFrame), NSMinY(aCellFrame), NSWidth(aCellFrame),
NSHeight(aCellFrame), [[self window] gState], 0.0, 0.0,
NSCompositeCopy);
[cellCacheContentViewL unlockFocus];
}
- (void)_cacheMatrixWithVisibleRect:(NSRect)aVisibleRect
{
NSView *matrixCacheContentViewL = [_matrixCache contentView];
[matrixCacheContentViewL lockFocus];
PScomposite(NSMinX(aVisibleRect), NSMinY(aVisibleRect),
NSWidth(aVisibleRect), NSHeight(aVisibleRect),
[[self window] gState], 0.0, 0.0, NSCompositeCopy);
[matrixCacheContentViewL unlockFocus];
}
- (NSEvent *)_nextEventWithLocation:(NSPoint)aLocation
{
NSEvent *eventL = [NSApp nextEventMatchingMask:
NSLeftMouseUpMask | NSLeftMouseDraggedMask
untilDate:[NSDate date]
inMode:NSEventTrackingRunLoopMode
dequeue:YES];
if (eventL == nil)
{
eventL = [NSApp nextEventMatchingMask:
NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSPeriodicMask
untilDate:[NSDate distantFuture]
inMode:NSEventTrackingRunLoopMode
dequeue:YES];
}
if ([eventL type] == NSPeriodic)
{
eventL = [NSEvent otherEventWithType:NSPeriodic
location:aLocation
modifierFlags:[eventL modifierFlags]
timestamp:[eventL timestamp]
windowNumber:[eventL windowNumber]
context:[eventL context]
subtype:[eventL subtype]
data1:[eventL data1]
data2:[eventL data2]];
}
return eventL;
}
- (void)_moveActiveCell:(NSCell *)aCell fromRow:(int)aRow toRow:(int)aNewRow
{
// Prevent accidental release of aCell.
[aCell retain];
if (aRow < aNewRow)
{
// Push all cells above the active cell's new location up one row so
// that we fill the vacant spot.
for (; aRow < aNewRow; aRow++)
{
[self putCell:[self cellAtRow:aRow + 1 column:0] atRow:aRow
column:0];
}
}
else if (aNewRow < aRow)
{
// Push all cells below the active cell's new location down one row so
// that we fill the vacant spot.
for (; aNewRow < aRow; aRow--)
{
[self putCell:[self cellAtRow:aRow - 1 column:0] atRow:aRow
column:0];
}
}
// Now place the active cell in its new home.
[self putCell:aCell atRow:aNewRow column:0];
[aCell release];
}
- (void)_postNotificationWithOldRow:(int)aRow newRow:(int)aNewRow
{
NSDictionary *userInfoL = [NSDictionary
dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:aRow], @"OldRow",
[NSNumber numberWithInt:aNewRow], @"NewRow", nil];
[[NSNotificationCenter defaultCenter]
postNotificationName:MiscMatrixRowDidMove object:self
userInfo:userInfoL];
}
- (void)_highlightNewRow:(int)newRow
{
NSEvent *eventL = [NSApp currentEvent];
unsigned modifierFlagsL = [eventL modifierFlags];
NSTimeInterval timestampL = [eventL timestamp];
int windowNumberL = [eventL windowNumber];
NSDPSContext *contextL = [eventL context];
int eventNumberL = [eventL eventNumber];
NSRect cellFrameL = [self cellFrameAtRow:newRow column:0];
NSPoint locationL = [self convertPoint:cellFrameL.origin
toView:nil];
// Can't figure out how to highlight cell in newRow, so do it the hard way.
eventL = [NSEvent mouseEventWithType:NSLeftMouseUp
location:locationL
modifierFlags:modifierFlagsL
timestamp:timestampL
windowNumber:windowNumberL
context:contextL
eventNumber:eventNumberL
clickCount:1
pressure:0.0];
[NSApp postEvent:eventL atStart:YES];
// Select and highlight cell.
[super mouseDown:[NSEvent mouseEventWithType:NSLeftMouseDown
location:locationL
modifierFlags:modifierFlagsL
timestamp:timestampL
windowNumber:windowNumberL
context:contextL
eventNumber:eventNumberL
clickCount:1
pressure:0.0]];
}
@end
// ------------------------- Function Definitions -----------------------------
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.