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.
TextDrag.c
/* |
** File: TextDrag.c |
** |
** Contains: Text document dragging support for SimpleText |
** |
** Version: SimpleText 1.4 or later |
** |
** Copyright 1993-1999 Apple Computer. All rights reserved. |
** |
** You may incorporate this sample code into your applications without |
** restriction, though the sample code has been provided "AS IS" and the |
** responsibility for its operation is 100% yours. However, what you are |
** not permitted to do is to redistribute the source as "DSC Sample 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 Code, but that you've made changes. |
*/ |
#include "MacIncludes.h" |
#include "TextFile.h" |
#pragma segment Text |
// -------------------------------------------------------------------------------------------------------------- |
// FORWARD EXTERNAL DECLARES |
// -------------------------------------------------------------------------------------------------------------- |
extern Boolean IsOnlyThisFlavor(DragReference theDragRef, FlavorType theType); |
extern Boolean IsDropInFinderTrash(AEDesc *dropLocation); |
extern OSErr SaveCurrentUndoState(WindowDataPtr pData, short newCommandID); |
// -------------------------------------------------------------------------------------------------------------- |
// INTERNAL DEFINES |
// -------------------------------------------------------------------------------------------------------------- |
// Dragging private globals |
extern Boolean gCanAccept; |
// -------------------------------------------------------------------------------------------------------------- |
// GLOBALS USED ONLY BY THESE ROUTINES |
// -------------------------------------------------------------------------------------------------------------- |
static Boolean gCaretShow; |
static long gCaretTime; |
static short gCaretOffset, gLastOffset, gInsertPosition; |
static Boolean gCursorInContent; |
static unsigned long gAutoScrollTicks; |
// -------------------------------------------------------------------------------------------------------------- |
// INTERNAL ROUTINES |
// -------------------------------------------------------------------------------------------------------------- |
extern void AdjustTE(WindowDataPtr pData, Boolean doScroll); |
// -------------------------------------------------------------------------------------------------------------- |
// |
// GetSelectionSize - |
// |
static short GetSelectionSize(TEHandle hTE) |
{ |
return((**(hTE)).selEnd - (**(hTE)).selStart); |
} // GetSelectionSize |
// -------------------------------------------------------------------------------------------------------------- |
// |
// GetSelectedTextPtr |
// |
static Ptr GetSelectedTextPtr(TEHandle hTE) |
{ |
return((*(**(hTE)).hText) + (**(hTE)).selStart); |
} // GetSelectedTextPtr |
// -------------------------------------------------------------------------------------------------------------- |
// |
// TEIsFrontOfLine - Given a text offset and a TextEdit handle, returns true if the given |
// offset is at the beginning of a line start. |
// |
static short TEIsFrontOfLine(short textOffset, TEHandle hTE) |
{ |
short theLine = 0; |
if ((**hTE).teLength == 0) |
return true; |
if (textOffset >= (**hTE).teLength) |
return( (*((**hTE).hText))[(**hTE).teLength - 1] == 0x0D ); |
while ((**hTE).lineStarts[theLine] < textOffset) |
theLine++; |
return( (**hTE).lineStarts[theLine] == textOffset ); |
} // TEIsFrontOfLine |
// -------------------------------------------------------------------------------------------------------------- |
// |
// TEGetLine - Given an offset and a TextEdit handle, returns the line number that contains the offset. |
// |
static short TEGetLine(short textOffset, TEHandle hTE) |
{ |
short theLine = 0; |
if (textOffset > (**hTE).teLength) |
return((**hTE).nLines); |
else |
{ |
while ((**hTE).lineStarts[theLine] < textOffset) |
++theLine; |
} |
return theLine; |
} // TEGetLine |
// -------------------------------------------------------------------------------------------------------------- |
// |
// DrawCaret - Draws a caret in a TextEdit field at the given offset by inverting the image of the |
// caret onto the screen. DrawCaret expects the port to be set to the port that the |
// TextEdit record is in. |
// |
static void DrawCaret(short textOffset, TEHandle hTE) |
{ |
Point theLoc; |
short lineHeight, theLine; |
// |
// Get the coordinates and the line of the offset to draw the caret. |
// |
theLoc = TEGetPoint(textOffset, hTE); |
theLine = TEGetLine(textOffset, hTE); |
// %%% Most heinously bogus - 21-Dec-93 JM3 |
// |
// For some reason, TextEdit dosen't return the proper coordinates |
// of the last offset in the field if the last character in the record |
// is a carriage return. TEGetPoint returns a point that is one line |
// higher than expected. The following code fixes this problem. |
if ((textOffset == (**hTE).teLength) && (*((**hTE).hText))[(**hTE).teLength - 1] == 0x0D) |
theLoc.v += TEGetHeight(theLine, theLine, hTE); |
// |
// Always invert the caret when drawing. |
// |
PenMode(patXor); |
// |
// Get the height of the line that the offset points to. |
// |
lineHeight = TEGetHeight(theLine, theLine, hTE); |
// |
// Draw the appropriate caret image. |
// |
MoveTo(theLoc.h - 1, theLoc.v - 1); |
Line(0, 1 - lineHeight); |
PenNormal(); |
} // DrawCaret |
// -------------------------------------------------------------------------------------------------------------- |
// |
// HitTest - Given a point in global coordinates, HitTest returns an offset into the text if the |
// point is inside the given TERecord. If the point is not in the text, HitTest returns |
// -1. |
// |
static short HitTest(Point theLoc, TEHandle hTE) |
{ |
WindowRef theWindow; |
short textOffset = -1; |
if (FindWindow(theLoc, &theWindow) == inContent) |
{ |
SetPort((GrafPtr) GetWindowPort(theWindow)); |
GlobalToLocal(&theLoc); |
if (PtInRect(theLoc, &((** hTE).viewRect)) && PtInRect(theLoc, &((** hTE).viewRect))) |
{ |
textOffset = TEGetOffset(theLoc, hTE); |
if ((TEIsFrontOfLine(textOffset, hTE)) && (textOffset) && |
((*((** hTE).hText))[textOffset - 1] != 0x0D) && |
(TEGetPoint(textOffset - 1, hTE).h < theLoc.h)) |
{ |
--textOffset; |
} |
} |
} |
return textOffset; |
} // HitTest |
// -------------------------------------------------------------------------------------------------------------- |
// |
// GetCharAtOffset - Given a text offset and a TEHandle, returns the character located at that offset in |
// the TERecord. |
// |
static char GetCharAtOffset(short offset, TEHandle hTE) |
{ |
if (offset < 0) |
return 0x0D; |
return(((char *) *((**hTE).hText))[offset]); |
} // GetCharAtOffset |
// -------------------------------------------------------------------------------------------------------------- |
// |
// WhiteSpace - Determines if the input character is white space. |
// |
static Boolean WhiteSpace(char theChar) |
{ |
return((theChar == ' ') || (theChar == 0x0D)); |
} // WhiteSpace |
// -------------------------------------------------------------------------------------------------------------- |
// |
// WhiteSpaceAtOffset - Given a text offset into a TERecord, determines if the character at that location is |
// whitespace. |
// |
static Boolean WhiteSpaceAtOffset(short offset, TEHandle hTE) |
{ |
char theChar; |
if ((offset < 0) || (offset > (**hTE).teLength - 1)) |
return true; |
theChar = ((char *) *((**hTE).hText))[offset]; |
return(WhiteSpace(theChar)); |
} // WhiteSpaceAtOffset |
// -------------------------------------------------------------------------------------------------------------- |
// |
// InsertTextAtOffset - |
// |
static short InsertTextAtOffset(short textOffset, char *theBuf, long textSize, StScrpHandle styleHand, TEHandle hTE) |
{ |
short charactersAdded = 0; |
if (textSize == 0) |
return charactersAdded; |
// If we're inserting at the end of a word and the selection does not begin with |
// a space, insert a space before the insertion. |
if (!WhiteSpaceAtOffset(textOffset - 1, hTE) && |
WhiteSpaceAtOffset(textOffset, hTE) && |
!WhiteSpace(theBuf[0])) |
{ |
TESetSelect(textOffset, textOffset, hTE); |
TEKey(' ', hTE); |
++textOffset; |
++charactersAdded; |
} |
// If we're inserting at the beginning of a word and the selection does not end |
// with a space, insert a space after the insertion. |
if ( WhiteSpaceAtOffset(textOffset - 1, hTE) && |
!WhiteSpaceAtOffset(textOffset, hTE) && |
!WhiteSpace(theBuf[textSize - 1])) |
{ |
TESetSelect(textOffset, textOffset, hTE); |
TEKey(' ', hTE); |
++charactersAdded; |
} |
// Before we insert this text, make sure we set the selection range to a single character. |
// This assures that we won't overwrite the text in the previous selection. |
TESetSelect(textOffset, textOffset, hTE); |
TEStyleInsert(theBuf, textSize, styleHand, hTE); |
return charactersAdded; |
} // InsertTextAtOffset |
// -------------------------------------------------------------------------------------------------------------- |
// OOP INTERFACE ROUTINES |
// -------------------------------------------------------------------------------------------------------------- |
OSErr TextDragTracking(WindowRef pWindow, void *pData, DragReference theDragRef, short message) |
{ |
#pragma unused(pWindow) |
unsigned long attributes; |
RgnHandle hilightRgn; |
Point localMouseLoc, dragMouseLoc; |
short textOffset; |
long theTime = TickCount(); |
WindowDataPtr theData = (WindowDataPtr) pData; |
GetDragAttributes(theDragRef, &attributes); |
switch(message) |
{ |
case kDragTrackingEnterWindow: |
gCanAccept = IsOnlyThisFlavor(theDragRef, 'TEXT'); |
gCaretTime = theTime; |
gCaretOffset = gLastOffset = -1; |
gCaretShow = true; |
gCursorInContent = false; |
gAutoScrollTicks = 0; |
break; |
case kDragTrackingInWindow: |
if (gCanAccept) |
{ |
GetDragMouse(theDragRef, &dragMouseLoc, 0L); |
localMouseLoc = dragMouseLoc; |
GlobalToLocal(&localMouseLoc); |
if (attributes & kDragInsideSenderWindow) |
{ |
short deltaV = 0; |
if ((localMouseLoc.v < 16) && (localMouseLoc.v > 0)) |
deltaV = theData->vScrollAmount; |
if (localMouseLoc.v > GetWindowPort(pWindow)->portRect.bottom - 16) |
deltaV = - theData->vScrollAmount; |
if (deltaV == 0) |
{ |
gAutoScrollTicks = 0; |
} |
else |
{ |
if (gAutoScrollTicks == 0) |
{ |
gAutoScrollTicks = theTime; |
} |
else |
{ |
if (theTime - gAutoScrollTicks > 10) // 10 ticks to start is what the H.I. doc says |
{ |
// remove the drag-destination caret if it's showing |
if (gCaretOffset != -1) |
{ |
DrawCaret(gCaretOffset, ((TextDataPtr) pData)->hTE); |
gCaretOffset = -1; |
} |
SetControlAndClipAmount(theData->vScroll, &deltaV); |
if (deltaV != 0) |
{ |
DragPreScroll(theDragRef, 0, deltaV); |
DoScrollContent(pWindow, theData, 0, deltaV); |
DragPostScroll(theDragRef); |
} |
gAutoScrollTicks = theTime - 7; // let's wait 3 more ticks until next jump |
} |
} |
} |
} |
if (attributes & kDragHasLeftSenderWindow) |
{ |
if (PtInRect(localMouseLoc, &(theData->contentRect))) |
{ |
if (!gCursorInContent) |
{ |
hilightRgn = NewRgn(); |
RectRgn(hilightRgn, &theData->contentRect); |
ShowDragHilite(theDragRef, hilightRgn, true); |
DisposeRgn(hilightRgn); |
} |
gCursorInContent = true; |
} |
else |
{ |
if (gCursorInContent) |
{ |
HideDragHilite(theDragRef); |
gCursorInContent = false; |
} |
} |
} |
} |
textOffset = HitTest(dragMouseLoc, ((TextDataPtr)pData)->hTE); |
// If this application is the sender, do not allow tracking through |
// the selection in the window that sourced the drag. |
if (attributes & kDragInsideSenderWindow) |
{ |
if ((textOffset >= (**((TextDataPtr)pData)->hTE).selStart) && |
(textOffset <= (**((TextDataPtr)pData)->hTE).selEnd)) |
{ |
textOffset = -1; |
} |
} |
gInsertPosition = textOffset; |
// Reset the flashing counter if the offset has moved. This makes the |
// caret blink only after the caret has stopped moving long enough. |
if (textOffset != gLastOffset) |
{ |
gCaretTime = theTime; |
gCaretShow = true; |
} |
gLastOffset = textOffset; |
// Flash the caret, blinky-blinky-blinky. |
if (theTime - gCaretTime > GetCaretTime()) |
{ |
gCaretShow = !gCaretShow; |
gCaretTime = theTime; |
} |
if (!gCaretShow) |
textOffset = -1; |
// If the caret offset has changed, move the caret on the screen. |
if (textOffset != gCaretOffset) |
{ |
if (gCaretOffset != -1) |
DrawCaret(gCaretOffset, ((TextDataPtr)pData)->hTE); |
if (textOffset != -1) |
DrawCaret(textOffset, ((TextDataPtr)pData)->hTE); |
} |
gCaretOffset = textOffset; |
break; |
case kDragTrackingLeaveWindow: |
// If the caret is on the screen, remove it. |
if (gCaretOffset != -1) |
{ |
DrawCaret(gCaretOffset, ((TextDataPtr)pData)->hTE); |
gCaretOffset = -1; |
} |
// Remove the window hilighting, if any. |
if ((gCursorInContent) && (attributes & kDragHasLeftSenderWindow)) |
HideDragHilite(theDragRef); |
break; |
} // switch (message) |
return noErr; |
} // TextDragTracking |
// -------------------------------------------------------------------------------------------------------------- |
OSErr TextDragReceive(WindowRef pWindow, void *pData, DragReference theDragRef) |
{ |
OSErr error; |
unsigned short items, index; |
DragAttributes attributes; |
ItemReference theItem; |
Ptr textData; |
StScrpHandle styleHand; |
Size textSize, styleSize, totalTextSize; |
short mouseDownModifiers, mouseUpModifiers, moveText, selStart, selEnd; |
long totalTextStart; |
long additionalChars; |
TEHandle hTE; |
Boolean wasActive; |
if ((!gCanAccept) || (gInsertPosition == -1)) |
return dragNotAcceptedErr; |
hTE = ((TextDataPtr) pData)->hTE; |
// We're going to try our best to insert some text, so first save off the beginning of where |
// we'll do it. |
totalTextStart = gInsertPosition; |
totalTextSize = 0L; |
// draw in this window, and activate the text editing record so that selections |
// happen properly |
SetPort((GrafPtr) GetWindowPort(pWindow)); |
wasActive = (*hTE)->active != 0; // can't test window == FrontWindow (might not be front app) |
if (!wasActive) |
TEActivate(hTE); |
GetDragAttributes(theDragRef, &attributes); |
GetDragModifiers(theDragRef, 0L, &mouseDownModifiers, &mouseUpModifiers); |
moveText = (attributes & kDragInsideSenderWindow) && |
(!((mouseDownModifiers & optionKey) | (mouseUpModifiers & optionKey))); |
// Loop through all of the drag items contained in this drag and snag all of the 'TEXT'. |
CountDragItems(theDragRef, &items); |
for (index = 1; index <= items; index++) |
{ |
// Get the item's reference number, so we can refer to it. |
GetDragItemReferenceNumber(theDragRef, index, &theItem); |
// Try to get the size for a 'TEXT' flavor. If this returns noErr, |
// then we know that a 'TEXT' flavor exists in the item. |
error = GetFlavorDataSize(theDragRef, theItem, 'TEXT', &textSize); |
if (error == noErr) |
{ |
// If the current length, plus the drag data would make the document too large, say so. |
if (((*hTE)->teLength + textSize) > kMaxLength) |
return eDocumentTooLarge; |
textData = NewPtr(textSize); |
// If we couldn't get a chunk of memory for the text, bail. |
if(textData == 0L) |
return memFullErr; |
GetFlavorData(theDragRef, theItem, 'TEXT', textData, &textSize, 0); |
// Let's see if there is an optional 'styl' flavor. |
styleHand = 0L; |
error = GetFlavorDataSize(theDragRef, theItem, 'styl', &styleSize); |
// If there was no 'styl' data, or it somehow was zero in length, don't |
// attempt to insert it along with the text, 'cause we'd fail miserably. |
if ((error == noErr) && (styleSize != 0)) |
{ |
styleHand = (StScrpHandle) NewHandle(styleSize); |
// If we couldn't get a chunk of memory for the styles, also bail. |
if (styleHand == 0L) |
{ |
DisposePtr(textData); |
return memFullErr; |
} |
HLock((Handle) styleHand); |
GetFlavorData(theDragRef, theItem, 'styl', *styleHand, &styleSize, 0L); |
HUnlock((Handle) styleHand); |
} |
// If the caret or highlighting is on the screen, remove it/them. |
if (gCaretOffset != -1) |
{ |
DrawCaret(gCaretOffset, hTE); |
gCaretOffset = -1; |
} |
if (attributes & kDragHasLeftSenderWindow) |
HideDragHilite(theDragRef); |
// save away any changes, so that we can undo them |
SaveCurrentUndoState(pData, cTypingCommand); |
// If this window is also the sender, delete the source selection text if the |
// option key is not being held down. |
if (moveText) |
{ |
selStart = (**hTE).selStart; |
selEnd = (**hTE).selEnd; |
if ( WhiteSpaceAtOffset(selStart - 1, hTE) && |
!WhiteSpaceAtOffset(selStart, hTE) && |
!WhiteSpaceAtOffset(selEnd - 1, hTE) && |
WhiteSpaceAtOffset(selEnd, hTE)) |
{ |
if (GetCharAtOffset(selEnd, hTE) == ' ') |
(**hTE).selEnd++; |
} |
if (gInsertPosition > selStart) |
{ |
selEnd = (**hTE).selEnd; |
gInsertPosition -= (selEnd - selStart); |
totalTextStart -= (selEnd - selStart); |
} |
TEDelete(hTE); |
} |
// We can finally insert the text and style data into our record. |
additionalChars = InsertTextAtOffset(gInsertPosition, textData, textSize, styleHand, hTE); |
// In case we're inserting multiple chunks of text, we need to update the location of where we |
// need to insert the next block. |
gInsertPosition += textSize + additionalChars; |
totalTextSize += textSize + additionalChars; |
DisposePtr(textData); |
if (styleHand) |
DisposeHandle((Handle) styleHand); |
} |
} |
// Select everything we've just inserted. |
TESetSelect(totalTextStart, totalTextStart + totalTextSize, hTE); |
AdjustTE(pData, false); |
AdjustScrollBars(pWindow, false, false, nil); |
((WindowDataPtr) pData)->changed = true; |
// if we had to activate the edit record, deactivate it after we are all done |
if (!wasActive) |
TEDeactivate(hTE); |
return noErr; |
} // TextDragReceive |
// -------------------------------------------------------------------------------------------------------------- |
Boolean DragText(WindowRef pWindow, void *pData, EventRecord *pEvent, RgnHandle hilightRgn) |
{ |
Point theLoc = {0,0}; |
RgnHandle dragRegion, tempRegion; |
DragReference theDragRef; |
StScrpHandle theStyleHand = 0L; |
OSErr error; |
AEDesc dropLocation; |
DragAttributes attributes; |
short mouseDownModifiers, mouseUpModifiers, copyText; |
// Copy the hilight region into dragRegion and offset it into global coordinates. |
CopyRgn(hilightRgn, dragRegion = NewRgn()); |
LocalToGlobal(&theLoc); |
OffsetRgn(dragRegion, theLoc.h, theLoc.v); |
// Wait for the mouse to move or the mouse button to be released. If the mouse button was |
// released before the mouse moves, return false. Returing false from DragText means that |
// a drag operation did not occur. |
if (!WaitMouseMoved(pEvent->where)) |
return false; |
NewDrag(&theDragRef); |
AddDragItemFlavor(theDragRef, 1, 'TEXT', GetSelectedTextPtr(((TextDataPtr)pData)->hTE), GetSelectionSize(((TextDataPtr)pData)->hTE), 0); |
theStyleHand = TEGetStyleScrapHandle(((TextDataPtr)pData)->hTE); |
// Just be a little paranoid and see if we did get a handle. |
if (theStyleHand) |
{ |
HLock((Handle) theStyleHand); |
AddDragItemFlavor(theDragRef, 1, 'styl', (Ptr) *theStyleHand, GetHandleSize((Handle) theStyleHand), 0); |
DisposeHandle((Handle) theStyleHand); |
} |
// Set the item's bounding rectangle in global coordinates. |
SetDragItemBounds(theDragRef, 1, &(**dragRegion).rgnBBox); |
// Prepare the drag region. |
tempRegion = NewRgn(); |
CopyRgn(dragRegion, tempRegion); |
InsetRgn(tempRegion, 1, 1); |
DiffRgn(dragRegion, tempRegion, dragRegion); |
DisposeRgn(tempRegion); |
// Drag the text. TrackDrag will return userCanceledErr if the drop whooshed back for any reason. |
error = TrackDrag(theDragRef, pEvent, dragRegion); |
if ((error != noErr) && (error != userCanceledErr)) |
return true; |
// Check to see if the drop occurred in the Finder's Trash. If the drop occurred |
// in the Finder's Trash and a copy operation wasn't specified, delete the |
// source selection. Note that we can continute to get the attributes, drop location |
// modifiers, etc. of the drag until we dispose of it using DisposeDrag. |
GetDragAttributes(theDragRef, &attributes); |
if (!(attributes & kDragInsideSenderApplication)) |
{ |
GetDropLocation(theDragRef, &dropLocation); |
GetDragModifiers(theDragRef, 0L, &mouseDownModifiers, &mouseUpModifiers); |
copyText = (mouseDownModifiers | mouseUpModifiers) & optionKey; |
if ((!copyText) && (IsDropInFinderTrash(&dropLocation))) |
{ |
TEDelete(((TextDataPtr)pData)->hTE); |
AdjustTE(pData, false); |
AdjustScrollBars(pWindow, false, false, nil); |
((WindowDataPtr) pData)->changed = true; |
} |
AEDisposeDesc(&dropLocation); |
} |
// Dispose of this drag, 'cause we're done. |
DisposeDrag(theDragRef); |
DisposeRgn(dragRegion); |
return true; |
} // DragText |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-03-26