Lists.c

/*
    File:       Lists.c
 
    Contains:   List Manager stuff and associated routines
 
    Written by: Chris White 
 
    Copyright:  Copyright © 1995-1999 by Apple Computer, Inc., All Rights Reserved.
 
                You may incorporate this Apple sample source code into your program(s) without
                restriction. This Apple sample source code has been provided "AS IS" and the
                responsibility for its operation is yours. You are not permitted to redistribute
                this Apple sample source code as "Apple sample source code" after having made
                changes. If you're going to re-distribute the source, we require that you make
                it clear in the source that the code was descended from Apple sample source
                code, but that you've made changes.
 
    Change History (most recent first):
                8/5/1999    Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
                
 
*/
 
#include <Drag.h>
#include <Errors.h>
#include <MixedMode.h>
 
 
#include "MenuStuff.h"
 
#include "Prototypes.h"
 
 
static Boolean UserWantsToDrag ( ListRef theList, Point globallPt );
static Boolean MouseHitSelection ( ListRef theList, Point localPt );
static Boolean EqualCells ( Cell c1, Cell c2 );
static void HandleClickInList ( WindowRef theWindow, Boolean newClick, 
                                Boolean doubleClick, Cell newCell );
static Boolean GetSelectionRect ( ListRef theList, Rect* theRect );
static OSErr StartADrag ( WindowRef theWindow, EventRecord* theEvent );
static OSErr AddFlavors ( DragReference theDrag, ItemReference theItem, WindowRef theWindow );
 
static void OutlineRegion ( RgnHandle theRgn );
static void LocalToGlobalRect ( Rect* theRect );
 
static Boolean MyClickLoop ( void );
 
 
 
 
 
// We'll limit its scope to this file
static ListRef gCurrentClickedList = nil;
 
 
//
// Gotcha: The List Manager doesn't actually check D0, but rather simply tests the Z-bit in
// the 68K processor's status register. Because this is lost during Mixed Mode, we need to
// use a 68K stub routine that calls the native clickLoop and then tests DO before returning
// to the List Manager. The emulator will set its Z-bit, and the List Manager will be happy.
// See the ReadMe file for a more complete explanation.
//
void InitListClickLoop ( void )
{
 
#ifdef powerc
 
    static RoutineDescriptor theListClickLoopRD =
                BUILD_ROUTINE_DESCRIPTOR ( uppListClickLoopProcInfo, MyClickLoop );
    
    
    #ifdef powerc
    #pragma options align=mac68k
    #endif
    
    static struct LClickLoopGlue
    {
       long     move;                   // MOVEA.L  ClickLoopUPP, A0
       short    jsr;                    // JSR  (A0)
       short    tst;                    // TST.B D0
       short    rts;                    // RTS
       UniversalProcPtr ClickLoopUPP;   // Storage for the UPP
    } LClickLoop68K = {
       0x207A0008,
       0x4E90,
       0x4A00,
       0x4E75,
       (UniversalProcPtr) &theListClickLoopRD
    };
    
    #ifdef powerc
    #pragma options align=reset
    #endif
    
    gClickLoopUPP = (ListClickLoopUPP) &LClickLoop68K;
    
#else
    gClickLoopUPP = NewListClickLoopProc ( MyClickLoop );
#endif
    
    return;
}
 
 
 
Boolean PtInList ( Point localPt, ListRef theList )
{
    return PtInRect ( localPt, &(*theList)->rView );
}
 
 
 
void GetListRect ( Rect* theRect, ListRef theList )
{
    *theRect = (*theList)->rView;
}
 
 
 
void AddToList ( ListRef theList, Str255 theString )
{
    Rect    dataRect;
    Cell    cell;
    short   nuRow;
    short   stringSize = sizeof(OSType);
    
    #if DEBUGGING
    if ( theList == nil ) DebugStr ( "\p theList == nil");
    #endif
    
    dataRect = (*theList)->dataBounds;
    nuRow = LAddRow ( 1, dataRect.bottom, theList );
    SetPt ( &cell, 0, nuRow );
    LSetCell ( &theString[1], theString[0], cell, theList );
    
    return;
}
 
 
 
void UpdateList ( ListRef theList, int16 theIndex, Str255 theString )
{
    Cell    theCell;
    
    SetPt ( &theCell, 0, theIndex );
    LSetCell ( &theString[1], theString[0], theCell, theList );
    
    return;
}
 
 
 
void DeleteFromList ( ListRef theList, int16 theIndex )
{
    LDelRow ( 1, theIndex, theList );
    
    return;
}
 
 
/*------------------------------------------------------------------*/
 
 
 
Boolean HandleListClick ( WindowRef theWindow, EventRecord* event )
{
    Boolean     bWasHandled = false;
    Point       localPt;
    Rect        listRect;
    ListRef theList;
    
    
    
    localPt = event->where;
    GlobalToLocal ( &localPt );
    theList = GetWListRef ( theWindow );
    
    
    if ( (*theList)->lActive == true )
    {
        listRect = (*theList)->rView;
        listRect.right += 15; listRect.bottom += 15;
        
        if ( PtInRect ( localPt, &listRect ) )
        {   
            Cell        oldCell, newCell;
            Boolean     oldSelection, 
                        validSelection = false, 
                        newClick = false, 
                        doubleClick;
            
            
            oldSelection = GetFirstSelection ( theList, &oldCell );
            
            
            
            // UserWantsToDrag takes a point in global coords. It uses the Drag Manager
            // routine WaitMouseMoved to make sure the user is dragging as opposed
            // to double clicking. If a drag is started when the user is really trying
            // to double click, it will end up being a very quick drag.
            
            if ( UserWantsToDrag ( theList, event->where ) )
            {
                
                // The click loop code needs to wait until the mouse is moved out
                // of the list rect before starting a drag. Because the default
                // behaviour of a a drag within a list is to change the selection
                // and, if required, scroll the list, attempting to drag a cell
                // by moving the mouse over another cell will result in that cell
                // being hilited. The job of this code is to detect those attempted
                // drags and call the necessary drag routines instead of making a
                // call to the LClick routine.
                
                
                // If we successfully drop the cell somewhere else,
                // clear the selection in the original list.
                
                if ( StartADrag ( theWindow, event ) == noErr )
                    LSetSelect ( false, oldCell, theList );
                
                return true;
                
            }
                        
            //
            // Save the current list for use in our click loop 
            //
            gCurrentClickedList = theList;
            doubleClick = LClick ( localPt, event->modifiers, theList );
            
            validSelection = GetFirstSelection ( theList, &newCell );
            if ( validSelection ) 
                if ( !oldSelection || !EqualCells ( newCell, oldCell ) )
                    newClick = true;
            
            HandleClickInList ( theWindow, newClick, doubleClick, newCell );
            bWasHandled = true;
        }
    }
    
    return bWasHandled;
}
 
 
 
Boolean GetSelection ( ListRef theList, int16* theIndex )
{
    Boolean     bFound;
    Cell        theCell;
    
    SetPt ( (Point*) &theCell, 0, *theIndex );
    bFound = LGetSelect ( true, &theCell, theList );
    if ( bFound )
        *theIndex = theCell.v;
    
    return bFound;
}
 
 
 
Boolean GetFirstSelection ( ListRef theList, Cell* theCell )
{
    SetPt ( (Point*) theCell, 0, 0 );
    return LGetSelect ( true, theCell, theList );
}
 
 
 
//
// Does the user want to drag something? First, checks the click could
// be drag, then waits to see if the user starts to drag the cell.
// 
static Boolean UserWantsToDrag ( ListRef theList, Point globalPt )
{
    Point   localPt;
    
    localPt = globalPt;
    GlobalToLocal ( &localPt );
    if ( MouseHitSelection ( theList, localPt ) )
        return WaitMouseMoved ( globalPt );
        
    return false;
}
 
 
 
//
// Has the user clicked on a select cell?
//
static Boolean MouseHitSelection ( ListRef theList, Point localPt )
{
    Cell        selectedCell;
    Rect        selectedCellRect;
    
    
    if ( !(GetFirstSelection ( theList, &selectedCell )) )
        return false;
 
    if ( !(PtInRect ( *(Point*) &selectedCell, (Rect*) &(*theList)->visible )) )
        return false;
 
    if ( !(GetSelectionRect ( theList, &selectedCellRect )) )
        return false;
    
    return PtInRect ( localPt, &selectedCellRect );
}
 
 
 
static Boolean EqualCells ( Cell c1, Cell c2 )
{
    return (c1.h == c2.h && c1.v == c2.v);
}
 
 
 
//
// This routine is called in response to a click in a list. We'll
// turn a double click into a ÒGet InfoÓ command.
//  
static void HandleClickInList ( WindowRef theWindow, Boolean newClick, 
                                Boolean doubleClick, Cell newCell )
{
    #pragma unused(theWindow,newClick,newCell)
    if ( doubleClick )
    {
        int32 menuSelection;
        
        menuSelection = kFragmentMenu << 16;
        menuSelection += cGetInfo;
        MenuDispatch ( menuSelection );
    }
    
    return;
}
 
 
 
//
// This gets a rectangle for the selection in a list. It's used to
// pass to the Drag Manager for user feedback.
//
static Boolean GetSelectionRect ( ListRef theList, Rect* theRect )
{
    Boolean validSelection;
    Cell    selectedCell, adjustedCell;
    Rect    selectedCellRect, emptyRect = {0, 0, 0, 0};
    
    *theRect = emptyRect;
    validSelection = GetFirstSelection ( theList, &selectedCell );
    
    if ( validSelection )
    {
        
        //
        // adjustedCell is needed because the current view of the list may not
        // have cell (0, 0) at the top left corner. This takes the top and left
        // values of the visible field and subtracts them from the selected cell.
        // We end up with this cell's location in the current view honoring scroll
        // bars.
        //
        
        adjustedCell.v = selectedCell.v - (*theList)->visible.top;
        adjustedCell.h = selectedCell.h - (*theList)->visible.left;
        
        selectedCellRect.top = (*theList)->rView.top + (adjustedCell.v * (*theList)->cellSize.v);
        selectedCellRect.bottom = selectedCellRect.top + (*theList)->cellSize.v;
        
        
        //
        // Because the cell width is 32767 (see comments in CreateContentList routine),
        // we need to use the view width instead of the cell width. If we don't, the Drag
        // Manager will not draw anything as the user drags the cell around (because it's
        // too big). Even if the Drag Manager did do any drawing, it wouldn't reflect the
        // size of the cell as the user sees it.
        //
        
        selectedCellRect.left = (*theList)->rView.left + (adjustedCell.h * (*theList)->rView.right);
        selectedCellRect.right = selectedCellRect.left + (*theList)->rView.right;
        
        *theRect = selectedCellRect;
    }
    
    return validSelection;
}
 
 
 
//
// This creates a new drag for the Drag Manager and tracks it until
// the user is done. If a successful drop is made, the Drag Manager
// will call our DragReceiver routine.
//
static OSErr StartADrag ( WindowRef theWindow, EventRecord* theEvent )
{
    OSErr           theErr = noErr;
    DragReference   theDrag;
    Rect            dragBounds;
    RgnHandle       dragRgn;
    ListRef     theList;
    ItemReference   theItem = 1;
    
    
    if ( !gHasDragManager )
        return kDragManagerNotPresent;
    
    
    theList = GetWListRef ( theWindow );
    
    theErr = NewDrag ( &theDrag );
    if ( theErr == noErr )
    {       
        theErr = AddFlavors ( theDrag, theItem, theWindow );
 
        if ( theErr == noErr )
        {
            
            if ( GetSelectionRect ( theList, &dragBounds ) )
            {
                LocalToGlobalRect ( &dragBounds );              
                theErr = SetDragItemBounds ( theDrag, theItem, &dragBounds );
 
                if ( theErr == noErr )
                {   
                    dragRgn = NewRgn ( );
                    RectRgn ( dragRgn, &dragBounds );
                    OutlineRegion ( dragRgn );
                    theErr = TrackDrag ( theDrag, theEvent, dragRgn );
                    
                    DisposeRgn ( dragRgn );
                }
            }
        }
        
        DisposeDrag ( theDrag );
    }
 
    return theErr;
}
 
 
 
static Boolean MyClickLoop ( void )
{
    Point       localPt;
    Cell        selectedCell;
    WindowRef   theWindow;
    ListRef list;
    
    
    
    //
    // This code starts a drag for those cases where there isn't already
    // a selection, and the mouse is dragged out in such a way as to not
    // cause another cell to be hilited. We need to wait until the mouse
    // is dragged out of the list rect for two reasons. First, to allow
    // the default behaviour to occur. Second, to stop a single click from
    // acting like the current selection was dragged and release where the
    // click was made. Note that the default behaviour is to change the
    // selection as the mouse is dragged over the cells, and scroll the
    // list if required.
    // 
    
    
    theWindow = FrontWindow ( );
    list = GetWListRef ( theWindow );
    
    GetMouse ( &localPt );  
    if ( GetFirstSelection ( list, &selectedCell ) )
    {
        if ( !(PtInRect ( localPt, &(*list)->rView)) )
        {
            EventRecord dummyEvent;
            long        tmpLong;
            Rect        tmpRect;
            
            OSEventAvail(0, &dummyEvent);
            dummyEvent.what = mouseDown;
            
            //
            // We go through the trouble of making sure the mouse doesn't
            // appear outside of the list cell region when the drag starts.
            //
            
            GetSelectionRect(list, &tmpRect);
            InsetRect(&tmpRect, 2, 2);
            tmpLong = PinRect(&tmpRect, localPt);
            dummyEvent.where = *(Point *) &tmpLong;
            LocalToGlobal((Point *) &dummyEvent.where);
            
            //
            // If we successfully drop the cell somewhere else,
            // clear the selection in the original list.
            //
            
            if ( StartADrag ( theWindow, &dummyEvent ) == noErr )
                LSetSelect ( false, selectedCell, list );
            
            return false;
            
        }
    }
 
    return true;
}
 
 
 
//
// This tells the Drag Manager what data to send when the user drags something
//
static OSErr AddFlavors ( DragReference theDrag, ItemReference theItem, WindowRef theWindow )
{
    OSErr       theErr = noErr;
    int16       theIndex = 0;
    Size        theSize;
    ListRef     theList;
    tDragData   theData;
    
    
    // First, we'll promise to create a file if the user drags
    // something out to the Finder.
    theErr = AddHFSPromise ( theDrag, theItem );
    if ( theErr )
        return theErr;
    
    // Set the send procedure that will deliver any promised data.
    SetDragSendProc ( theDrag, gDragSendDataProcUPP, theWindow );
    
    
    
    //
    // I'm being lazy here. There's no real advantage to including
    // the window reference with each item (well, apart from the effort
    // saved on my part).
    //
    
    theList = GetWListRef ( theWindow );
    theData.theWindow = theWindow;
    theSize = sizeof ( tDragData );
    
    while ( GetSelection ( theList, &theIndex ) )
    {
        OSErr       theErr;
        
        //
        // The flavorSenderOnly is used to tell the Drag Manager that this data will not
        // be valid in another client. Eg. If this application was launched twice, one
        // could accept a drag from the other. However, since the data consists of ptrs
        // rather than the actual data, it wouldn't make sense. Note, this doesn't affect
        // dragging to the finder, which is a special case.
        //
        theData.theIndex = theIndex;
        theErr = AddDragItemFlavor ( theDrag, theItem, kCreatorCode, 
                                    (Ptr) &theData, theSize, flavorSenderOnly );
        if ( theErr )
            return theErr;
            
        theIndex++;
    }
    
    
    return noErr;
}
 
 
 
static void OutlineRegion ( RgnHandle theRgn )
{
    RgnHandle tempRgn;
    
    tempRgn = NewRgn ( );
    CopyRgn ( theRgn, tempRgn );
    InsetRgn ( tempRgn, 1, 1 );
    DiffRgn ( theRgn, tempRgn, theRgn );
    DisposeRgn ( tempRgn );
    
    return;
}
 
 
 
static void LocalToGlobalRect ( Rect* theRect )
{
    LocalToGlobal ( (Point*) &theRect->top );
    LocalToGlobal ( (Point*) &theRect->bottom );
    
    return;
}