// 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 -----------------------------
