Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
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; |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-30