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.
TextFile.c
/* |
** File: TextFile.c |
** |
** Contains: Text file support for simple text application. |
** |
** 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 |
// -------------------------------------------------------------------------------------------------------------- |
// INTERNAL DEFINES |
// -------------------------------------------------------------------------------------------------------------- |
#define kOnePageWidth 600 // preferred width of a window |
#define kMargins 4 // margins in window |
#define kPrintMargins 8 // margins in printing window |
#define kGXPrintMargins 10 // margins in printing a GX window |
#define kPictureBase 1000 // resource base ID for PICTs in documents |
#define kSoundBase 10000 // resource base ID for 'snd 's in documents |
// resources for controling printing |
#define kFormResource 'form' |
#define kFormFeed 0x00000001 // form feed after this line |
#define kContentsListID 10000 // resource ID for STR# contents menu |
// some memory requirements |
#define kPrefBufferSize 90*1024 |
#define kMinBufferSize 5*1024 |
#define kDeleteKey 8 |
#define kForwardDeleteKey 0x75 |
extern pascal void AsmClikLoop(); |
#define ABS(n) (((n) < 0) ? -(n) : (n)) |
// -------------------------------------------------------------------------------------------------------------- |
// GLOBALS USED ONLY BY THESE ROUTINES |
// -------------------------------------------------------------------------------------------------------------- |
// These variables are used for speech, notice that on purpose we only |
// support a single channel at a time. |
static SpeechChannel gSpeechChannel = nil; |
static VoiceSpec gCurrentVoice; |
static Ptr gSpeakPtr = nil; |
static Boolean gAddedVoices = false; |
static Str31 gPictMarker1, gPictMarker2; |
// -------------------------------------------------------------------------------------------------------------- |
// EXTERNAL FUNCTIONS |
// -------------------------------------------------------------------------------------------------------------- |
extern OSErr TextDragTracking(WindowRef pWindow, void *pData, DragReference theDragRef, short message); |
extern OSErr TextDragReceive(WindowRef pWindow, void *refCon, DragReference theDragRef); |
extern Boolean DragText(WindowRef pWindow, void *pData, EventRecord *pEvent, RgnHandle hilightRgn); |
// -------------------------------------------------------------------------------------------------------------- |
// INTERNAL ROUTINES |
// -------------------------------------------------------------------------------------------------------------- |
static void TextAddContentsMenu(WindowDataPtr pData); |
static void TextRemoveContentsMenu(WindowDataPtr pData); |
static OSErr TextGetContentsListItem(WindowDataPtr pData, short itemNum, StringPtr menuStr, StringPtr searchStr, short *totalItems); |
static OSErr TextAdjustContentsMenu(WindowDataPtr pData); |
// -------------------------------------------------------------------------------------------------------------- |
// UNDO UTILITY FUNCTIONS |
// -------------------------------------------------------------------------------------------------------------- |
OSErr SaveCurrentUndoState(WindowDataPtr pData, short newCommandID) |
{ |
OSErr anErr = noErr; |
TEHandle hTE = ((TextDataPtr) pData)->hTE; |
// this is only a new command if: |
// the command ID is different |
// OR |
// the selection is a range, and not equal to the old one |
if ( |
( ((TextDataPtr) pData)->prevCommandID != newCommandID ) |
|| |
( |
( (**hTE).selStart != (**hTE).selEnd ) |
|| |
( ABS((**hTE).selStart - ((TextDataPtr) pData)->prevSelStart) > 1 ) |
) |
) |
{ |
Handle tempHandle; |
Size tempSize; |
// if we don't have save handles, make em! |
if (!((TextDataPtr) pData)->prevText) |
{ |
((TextDataPtr) pData)->prevText = NewHandle(0); |
anErr = MemError(); |
nrequire(anErr, MakeTextSave); |
} |
if (!((TextDataPtr) pData)->prevStyle) |
{ |
((TextDataPtr) pData)->prevStyle = NewHandle(0); |
anErr = MemError(); |
nrequire(anErr, MakeStyleSave); |
} |
// grow the save handles and block move into them |
tempHandle = (**hTE).hText; |
tempSize = GetHandleSize(tempHandle); |
SetHandleSize( ((TextDataPtr) pData)->prevText, tempSize); |
anErr = MemError(); |
nrequire(anErr, GrowTextSave); |
BlockMoveData(*tempHandle, * ((TextDataPtr) pData)->prevText, tempSize ); |
tempHandle = (Handle) TEGetStyleHandle(hTE); |
tempSize = GetHandleSize(tempHandle); |
SetHandleSize( ((TextDataPtr) pData)->prevStyle, tempSize); |
anErr = MemError(); |
nrequire(anErr, GrowTextSave); |
BlockMoveData(*tempHandle, * ((TextDataPtr) pData)->prevStyle, tempSize ); |
// save length |
((TextDataPtr) pData)->prevLength = (**hTE).teLength; |
((TextDataPtr) pData)->beforeSelStart = (**hTE).selStart; |
((TextDataPtr) pData)->beforeSelEnd = (**hTE).selEnd; |
} |
// save start and end of the selection |
((TextDataPtr) pData)->prevSelStart = (**hTE).selStart; |
// if we didn't have any problems, then we can undo this operation |
((TextDataPtr) pData)->prevCommandID = newCommandID; |
return(noErr); |
// EXCEPTION HANDLING |
GrowStyleSave: |
GrowTextSave: |
MakeStyleSave: |
MakeTextSave: |
// can't undo because of an error |
// (at some point might warn the user, but that's probably more annoying) |
((TextDataPtr) pData)->prevCommandID = cNull; |
return(anErr); |
} // SaveCurrentUndoState |
// -------------------------------------------------------------------------------------------------------------- |
static void PerformUndo(WindowDataPtr pData) |
{ |
if (((TextDataPtr) pData)->prevCommandID != cNull) |
{ |
TEHandle hTE = ((TextDataPtr) pData)->hTE; |
long tempLong; |
// undo text |
tempLong = (long) (**hTE).hText; |
(**hTE).hText = ((TextDataPtr) pData)->prevText; |
((TextDataPtr) pData)->prevText = (Handle)tempLong; |
// undo length |
tempLong = (long) (**hTE).teLength; |
(**hTE).teLength = ((TextDataPtr) pData)->prevLength; |
((TextDataPtr) pData)->prevLength = tempLong; |
// undo style |
tempLong = (long) TEGetStyleHandle(hTE); |
if (HandToHand((Handle*) &tempLong) == noErr) |
{ |
TESetStyleHandle( (TEStyleHandle) ((TextDataPtr) pData)->prevStyle, hTE ); |
((TextDataPtr) pData)->prevStyle = (Handle)tempLong; |
} |
// undo selection |
{ |
short start, end; |
start = ((TextDataPtr) pData)->beforeSelStart; |
end = ((TextDataPtr) pData)->beforeSelEnd; |
((TextDataPtr) pData)->prevSelStart = (**hTE).selStart; |
TESetSelect(start, end, hTE); |
} |
} |
} // PerformUndo |
// -------------------------------------------------------------------------------------------------------------- |
// TEXT EDIT UTILITY FUNCTIONS |
// -------------------------------------------------------------------------------------------------------------- |
static long CalculateTextEditHeight(TEHandle hTE) |
{ |
long result; |
short length; |
result = TEGetHeight(32767, 0, hTE); |
length = (**hTE).teLength; |
// Text Edit doesn't return the height of the last character, if that |
// character is a <cr>. So if we see that, we go grab the height of |
// that last character and add it into the total height. |
if ( (length) && ( (*(**hTE).hText)[length-1] == '\n') ) |
{ |
TextStyle theStyle; |
short theHeight; |
short theAscent; |
TEGetStyle(length, &theStyle, &theHeight, &theAscent, hTE); |
result += theHeight; |
} |
return result; |
} // CalculateTextEditHeight |
// -------------------------------------------------------------------------------------------------------------- |
void AdjustTE(WindowDataPtr pData, Boolean doScroll) |
{ |
TEPtr te; |
short value; |
short prevValue; |
te = *((TextDataPtr) pData)->hTE; |
prevValue = GetControlValue(pData->vScroll); |
value = te->viewRect.top - te->destRect.top; |
SetControlValue(pData->vScroll, value); |
te = *((TextDataPtr) pData)->hTE; |
if (doScroll) |
{ |
short hScroll = te->viewRect.left - te->destRect.left; |
short vScroll = value - prevValue; |
if ((hScroll != 0) || (vScroll != 0)) |
TEScroll(hScroll, vScroll, ((TextDataPtr) pData)->hTE); |
} |
} // AdjustTE |
// -------------------------------------------------------------------------------------------------------------- |
static void RecalcTE(WindowDataPtr pData, Boolean doInval) |
{ |
Rect viewRect, destRect; |
short oldOffset; |
destRect = (**((TextDataPtr) pData)->hTE).destRect; |
viewRect = (**((TextDataPtr) pData)->hTE).viewRect; |
oldOffset = destRect.top - viewRect.top; |
viewRect = pData->contentRect; |
InsetRect(&viewRect, kMargins, kMargins); |
destRect = viewRect; |
OffsetRect(&destRect, 0, oldOffset); |
(**((TextDataPtr) pData)->hTE).viewRect = viewRect; |
(**((TextDataPtr) pData)->hTE).destRect = destRect; |
TECalText(((TextDataPtr) pData)->hTE); |
if (doInval) |
InvalRect(&qd.thePort->portRect); |
} // RecalcTE |
// -------------------------------------------------------------------------------------------------------------- |
pascal TEClickLoopUPP GetOldClickLoop(void) |
{ |
return ((TextDataPtr) FrontWindow())->docClick; |
} // GetOldClikLoop |
pascal void TextClickLoop(void) |
{ |
WindowRef window; |
RgnHandle region; |
window = FrontWindow(); |
region = NewRgn(); |
GetClip(region); /* save clip */ |
ClipRect(&GetWindowPort(window)->portRect); |
((TextDataPtr) window)->insideClickLoop = true; |
AdjustScrollBars(window, false, false, nil); |
((TextDataPtr) window)->insideClickLoop = false; |
SetClip(region); /* restore clip */ |
DisposeRgn(region); |
} // TextClickLoop |
#if GENERATINGPOWERPC |
static pascal Boolean CClikLoop(TEPtr pTE) |
{ |
CallTEClickLoopProc(GetOldClickLoop(), pTE); |
TextClickLoop(); |
return(true); |
} // CClikLoop |
static RoutineDescriptor gMyClickLoopRD = BUILD_ROUTINE_DESCRIPTOR(uppTEClickLoopProcInfo, CClikLoop); |
static TEClickLoopUPP gMyClickLoop = &gMyClickLoopRD; |
#else |
static TEClickLoopUPP gMyClickLoop = NewDrawHookProc(AsmClikLoop); |
#endif |
// -------------------------------------------------------------------------------------------------------------- |
#if GENERATINGPOWERPC |
pascal void MyDrawHook ( unsigned short offset, unsigned short textLen, |
Ptr textPtr, TEPtr tePtr, TEHandle teHdl ) |
#else |
pascal void MyDrawGlue(); |
pascal void MyDrawHook ( TEHandle teHdl, TEPtr tePtr, Ptr textPtr, short textLen, short offset ) |
#endif |
{ |
#pragma unused ( teHdl, tePtr) |
register short drawLen = 0; |
register Ptr drawPtr; |
drawPtr = textPtr + (long)offset; |
while ( textLen--) |
{ |
if ( *drawPtr == 0xCA && |
( *(drawPtr - 1) == 0x0D || *tePtr->hText == textPtr) ) |
{ |
DrawText( textPtr, offset, drawLen); |
DrawChar( '\040'); |
offset += drawLen + 1; |
drawLen = 0; |
} |
else |
{ |
++drawLen; |
} |
++drawPtr; |
} |
DrawText( textPtr, offset, drawLen); |
} // MyDrawHook |
#if GENERATINGCFM |
static RoutineDescriptor gMyDrawGlueRD = BUILD_ROUTINE_DESCRIPTOR(uppDrawHookProcInfo, MyDrawHook); |
static DrawHookUPP gMyDrawGlue = &gMyDrawGlueRD; |
#else |
static DrawHookUPP gMyDrawGlue = NewDrawHookProc(MyDrawGlue); |
#endif |
// -------------------------------------------------------------------------------------------------------------- |
static void DisposeOfSpeech(Boolean throwAway) |
{ |
if (gSpeechChannel) |
{ |
(void) StopSpeech( gSpeechChannel ); |
if (throwAway) |
{ |
(void) DisposeSpeechChannel( gSpeechChannel ); |
gSpeechChannel = nil; |
} |
} |
if (gSpeakPtr) |
{ |
DisposePtr(gSpeakPtr); |
gSpeakPtr = nil; |
} |
} // DisposeOfSpeech |
// -------------------------------------------------------------------------------------------------------------- |
static Boolean FindNextPicture(Handle textHandle, short *offset, short *delta) |
{ |
long result; |
// marker at begining of document means a picture there |
if ( (*offset == 0) && ( (*(unsigned char*)(*textHandle + (*offset))) == 0xCA) ) |
{ |
*delta = 1; |
return true; |
} |
if (gPictMarker1[0] != 0) |
{ |
result = Munger(textHandle, *offset, &gPictMarker1[1], gPictMarker1[0], nil, 0); |
if (result >= 0) |
{ |
*offset = result; |
*delta = gPictMarker1[0]; |
return true; |
} |
} |
if (gPictMarker2[0] != 0) |
{ |
result = Munger(textHandle, *offset, &gPictMarker2[1], gPictMarker2[0], nil, 0); |
if (result >= 0) |
{ |
*offset = result; |
*delta = gPictMarker2[0]; |
return true; |
} |
} |
*delta = 1; |
return false; |
} // FindNextPicture |
// -------------------------------------------------------------------------------------------------------------- |
static Boolean LineHasPageBreak(short lineNum, TEHandle hTE) |
{ |
Boolean result = false; |
short offset, delta, lineStartOffset; |
short textLength; |
Handle textHandle; |
short pictIndex = 0; |
textHandle = (** hTE).hText; |
lineStartOffset = (**hTE).lineStarts[lineNum]; |
textLength = (**hTE).lineStarts[lineNum+1]; |
offset = 0; |
while (offset < textLength) |
{ |
if (FindNextPicture(textHandle, &offset, &delta)) |
{ |
Handle formHandle; |
formHandle = Get1Resource(kFormResource, kFormResource + pictIndex); |
if (formHandle) |
{ |
long options = **(long**)formHandle; |
ReleaseResource(formHandle); |
if ( (options & kFormFeed) && (offset >= lineStartOffset) && (offset < textLength) ) |
{ |
result = true; |
break; |
} |
} |
++pictIndex; |
} |
offset += delta; |
} |
return(result); |
} // LineHasPageBreak |
// -------------------------------------------------------------------------------------------------------------- |
#define USE_PICT_SPOOL_CACHE 1 |
enum {kSpoolCacheBlockSize = 1024}; // 1K is arbitrary, perhaps could be tuned |
static Handle gSpoolPicture; |
static long gSpoolOffset; |
static Handle gSpoolCacheBlock; |
static long gSpoolCacheOffset; |
static long gSpoolCacheSize; |
// -------------------------------------------------------------------------------------------------------------- |
/* |
ReadPartialResource is very painful over slow links such as ARA or ISDN. This code implements a simple |
cache that vastly improves the performance of displaying embedded pictures over a slow network link. |
The cache also improves scrolling performance in documents with many embedded pictures, even on local |
volumes. |
*/ |
static short ReadPartialData(Ptr dataPtr, short byteCount) |
{ |
#if USE_PICT_SPOOL_CACHE |
if (gSpoolCacheBlock == NULL) |
{ |
gSpoolCacheBlock = NewHandle(kSpoolCacheBlockSize); |
if (gSpoolCacheBlock != NULL) |
{ |
long cacheBytes = GetResourceSizeOnDisk(gSpoolPicture) - gSpoolOffset; |
if (cacheBytes > kSpoolCacheBlockSize) |
cacheBytes = kSpoolCacheBlockSize; |
HLock(gSpoolCacheBlock); |
ReadPartialResource(gSpoolPicture, gSpoolOffset, *gSpoolCacheBlock, cacheBytes); |
HUnlock(gSpoolCacheBlock); |
gSpoolCacheOffset = gSpoolOffset; |
gSpoolCacheSize = cacheBytes; |
} |
} |
// if the requested data is entirely present in the cache block, get it from thereÉ |
if (gSpoolCacheBlock != NULL && |
gSpoolOffset >= gSpoolCacheOffset && gSpoolOffset + byteCount <= gSpoolCacheOffset + gSpoolCacheSize) |
{ |
BlockMoveData(*gSpoolCacheBlock + (gSpoolOffset - gSpoolCacheOffset), dataPtr, byteCount); |
return byteCount; |
} |
// Éelse read it directly from disk, and force the cache block to be filled with new data |
else |
{ |
ReadPartialResource(gSpoolPicture, gSpoolOffset, dataPtr, byteCount); |
if (gSpoolCacheBlock != NULL) |
{ |
DisposeHandle(gSpoolCacheBlock); |
gSpoolCacheBlock = NULL; |
} |
return byteCount; |
} |
#else |
ReadPartialResource(gSpoolPicture, gSpoolOffset, dataPtr, byteCount); |
return byteCount; |
#endif |
} // ReadPartialData |
// -------------------------------------------------------------------------------------------------------------- |
static pascal void GetPartialPICTData(Ptr dataPtr,short byteCount) |
{ |
while (byteCount > 0) |
{ |
short readBytes = ReadPartialData(dataPtr, byteCount); |
byteCount -= readBytes; |
dataPtr += readBytes; |
gSpoolOffset += readBytes; |
} |
} // GetPartialPICTData |
#if GENERATINGCFM |
static RoutineDescriptor gGetPartialPICTDataRD = BUILD_ROUTINE_DESCRIPTOR(uppQDGetPicProcInfo, GetPartialPICTData); |
static QDGetPicUPP gGetPartialPICTData = &gGetPartialPICTDataRD; |
#else |
static QDGetPicUPP gGetPartialPICTData = NewQDGetPicProc(GetPartialPICTData); |
#endif |
// -------------------------------------------------------------------------------------------------------------- |
static void SpoolDrawPicture(Handle spoolPicture, PicHandle pictureHeader, Rect *pRect) |
{ |
CQDProcs spoolProcs; |
QDProcs *oldProcs; |
gSpoolPicture = spoolPicture; |
gSpoolOffset = sizeof(Picture); |
if (gMachineInfo.theEnvirons.hasColorQD) |
SetStdCProcs(&spoolProcs); |
else |
SetStdProcs((QDProcs*) &spoolProcs); |
spoolProcs.getPicProc = gGetPartialPICTData; |
oldProcs = qd.thePort->grafProcs; |
qd.thePort->grafProcs = (QDProcs*) &spoolProcs; |
DrawPicture(pictureHeader, pRect); |
qd.thePort->grafProcs = oldProcs; |
if (gSpoolCacheBlock != NULL) |
{ |
DisposeHandle(gSpoolCacheBlock); |
gSpoolCacheBlock = NULL; |
} |
} // SpoolDrawPicture |
// -------------------------------------------------------------------------------------------------------------- |
static void DrawPictures( WindowDataPtr pData, TEHandle hTE) |
{ |
Handle textHandle; |
long textLength; |
short oldResFile; |
short pictIndex; |
short numPicts; |
oldResFile = CurResFile(); |
UseResFile(pData->resRefNum); |
numPicts = Count1Resources('PICT'); |
pictIndex = 0; |
if (numPicts != 0) |
{ |
short offset, delta; |
RgnHandle oldClip; |
Rect theRect; |
Rect viewRect; |
viewRect = theRect = (** hTE).viewRect; |
// intersect our viewing area with the actual clip to avoid |
// drawing over the scroll bars |
{ |
RgnHandle newClip = NewRgn(); |
oldClip = NewRgn(); |
GetClip(oldClip); |
RectRgn(newClip, &theRect); |
SectRgn(oldClip, newClip, newClip); |
SetClip(newClip); |
} |
textHandle = (** hTE).hText; |
textLength = (** hTE).teLength; |
offset = 0; |
while (offset < textLength) |
{ |
if (FindNextPicture(textHandle, &offset, &delta)) |
{ |
Handle pictHandle; |
Point picturePoint = TEGetPoint(offset, hTE); |
SetResLoad(false); |
pictHandle = Get1Resource('PICT', kPictureBase + pictIndex); |
SetResLoad(true); |
if (pictHandle) |
{ |
PicHandle pictHeader = (PicHandle)NewHandle(sizeof(Picture) + sizeof(long)*8); |
if (pictHeader) |
{ |
HLock((Handle) pictHeader); |
ReadPartialResource(pictHandle, 0, (Ptr)*pictHeader, GetHandleSize((Handle)pictHeader)); |
HUnlock((Handle) pictHeader); |
// calculate where to draw the picture, this location is |
// computed by: |
// 1) the frame of the original picture, normalized to 0,0 |
// 2) the location of the non-breaking space character |
// 3) centering the picture on the content frame horizontally |
// 4) subtracting off the line-height of the character |
GetPICTRectangleAt72dpi(pictHeader, &theRect); |
OffsetRect(&theRect, -theRect.left, -theRect.top); |
OffsetRect(&theRect, |
theRect.left + |
((viewRect.right - viewRect.left) >> 1) - |
((theRect.right - theRect.left) >> 1), |
picturePoint.v-theRect.top - pData->vScrollAmount); |
// only draw the picture if it will be visible (vastly improves scrolling |
// performance in documents with many embedded pictures) |
if (RectInRgn(&theRect, qd.thePort->clipRgn)) |
SpoolDrawPicture(pictHandle, pictHeader, &theRect); |
} |
ReleaseResource((Handle) pictHandle); |
} |
++pictIndex; |
offset += delta; |
} |
else |
break; |
} |
SetClip(oldClip); |
DisposeRgn(oldClip); |
} |
UseResFile(oldResFile); |
} // DrawPictures |
// -------------------------------------------------------------------------------------------------------------- |
static void UpdateFileInfo(FSSpec *pSpec, Boolean documentIsText) |
{ |
FInfo theInfo; |
FSpGetFInfo(pSpec, &theInfo); |
theInfo.fdCreator = 'ttxt'; |
// set the stationary bit, if we must |
if (!documentIsText) |
{ |
theInfo.fdFlags |= kIsStationery; |
theInfo.fdType = 'sEXT'; |
} |
else |
{ |
theInfo.fdFlags &= ~kIsStationery; |
theInfo.fdType = 'TEXT'; |
} |
FSpSetFInfo(pSpec, &theInfo); |
} // UpdateFileInfo |
// -------------------------------------------------------------------------------------------------------------- |
static OSErr TextSave(WindowDataPtr pData) |
{ |
OSErr anErr = noErr; |
long amountToWrite; |
// write out the text |
SetFPos(pData->dataRefNum, fsFromStart, 0); |
amountToWrite = (** ((TextDataPtr) pData)->hTE).teLength; |
anErr = FSWrite(pData->dataRefNum, &amountToWrite, * (** ((TextDataPtr) pData)->hTE).hText); |
nrequire(anErr, FailedWrite); |
SetEOF(pData->dataRefNum, amountToWrite); |
if (pData->resRefNum == -1) |
{ |
FSpCreateResFile(&pData->fileSpec, 'ttxt', pData->originalFileType, 0); |
pData->resRefNum = FSpOpenResFile(&pData->fileSpec, fsRdWrPerm); |
} |
else |
{ |
// a save always makes it into file of type 'TEXT', for Save AsÉ we |
// afterwards set it again if the user is saving as stationary. |
UpdateFileInfo(&pData->fileSpec, true); |
} |
if (pData->resRefNum != -1) |
{ |
short oldResFile = CurResFile(); |
Handle resourceHandle; |
UseResFile(pData->resRefNum); |
// remove any old sounds |
resourceHandle = Get1Resource('snd ', kSoundBase); |
if (resourceHandle) |
{ |
RemoveResource(resourceHandle); |
DisposeHandle(resourceHandle); |
} |
// save the new sound |
resourceHandle = ((TextDataPtr) pData)->soundHandle; |
if (resourceHandle) |
{ |
anErr = HandToHand(&resourceHandle); |
if (anErr == noErr) |
{ |
AddResource(resourceHandle, 'snd ', kSoundBase, "\p"); |
anErr = ResError(); |
} |
nrequire(anErr, AddSoundResourceFailed); |
} |
// remove any old styles |
resourceHandle = Get1Resource('styl', 128); |
if (resourceHandle) |
{ |
RemoveResource(resourceHandle); |
DisposeHandle(resourceHandle); |
} |
// save the new style -- get the scrap handle from the TE record |
// To do this, we must select the text -- BUT doing so through the |
// TextEdit API results in lots of flashing and annoying behavior. |
// So we just change the offsets by hand |
{ |
short oldStart, oldEnd; |
oldStart = (** ((TextDataPtr) pData)->hTE).selStart; |
oldEnd = (** ((TextDataPtr) pData)->hTE).selEnd; |
(** ((TextDataPtr) pData)->hTE).selStart = 0; |
(** ((TextDataPtr) pData)->hTE).selEnd = 32767; |
resourceHandle = (Handle) TEGetStyleScrapHandle( ((TextDataPtr) pData)->hTE ); |
(** ((TextDataPtr) pData)->hTE).selStart = oldStart; |
(** ((TextDataPtr) pData)->hTE).selEnd = oldEnd; |
} |
if (resourceHandle) |
{ |
AddResource(resourceHandle, 'styl', 128, "\p"); |
anErr = ResError(); |
nrequire(anErr, AddStyleResourceFailed); |
} |
AddSoundResourceFailed: |
AddStyleResourceFailed: |
UpdateResFile(pData->resRefNum); |
UseResFile(oldResFile); |
} |
// FALL THROUGH EXCEPTION HANDLING |
FailedWrite: |
// if everything went okay, then clear the changed bit |
if (anErr == noErr) |
{ |
pData->changed = false; |
(void) FlushVol("\p", pData->fileSpec.vRefNum); |
} |
return anErr; |
} // TextSave |
// -------------------------------------------------------------------------------------------------------------- |
static pascal void DrawTextUserItem(DialogPtr dPtr, short theItem) |
/* |
Draw text icon in the location |
*/ |
{ |
short kind; |
Handle itemHandle; |
Rect box; |
GetDialogItem(dPtr, theItem, &kind, &itemHandle, &box); |
PlotIconID(&box, ttNone, ttNone, kTextIcon); |
} // DrawTextUserItem |
#if GENERATINGCFM |
static RoutineDescriptor gDrawTextUserItemRD = BUILD_ROUTINE_DESCRIPTOR(uppUserItemProcInfo, DrawTextUserItem); |
static UserItemUPP gDrawTextUserItem = &gDrawTextUserItemRD; |
#else |
static UserItemUPP gDrawTextUserItem = NewUserItemProc(DrawTextUserItem); |
#endif |
// -------------------------------------------------------------------------------------------------------------- |
static pascal void DrawStationeryUserItem(DialogPtr dPtr, short theItem) |
/* |
Draw stationery icon in the location |
*/ |
{ |
short kind; |
Handle itemHandle; |
Rect box; |
GetDialogItem(dPtr, theItem, &kind, &itemHandle, &box); |
PlotIconID(&box, ttNone, ttNone, kStationeryIcon); |
} // DrawStationeryUserItem |
#if GENERATINGCFM |
static RoutineDescriptor gDrawStationeryUserItemRD = BUILD_ROUTINE_DESCRIPTOR(uppUserItemProcInfo, DrawStationeryUserItem); |
static UserItemUPP gDrawStationeryUserItem = &gDrawStationeryUserItemRD; |
#else |
static UserItemUPP gDrawStationeryUserItem = NewUserItemProc(DrawStationeryUserItem); |
#endif |
// -------------------------------------------------------------------------------------------------------------- |
// pre and post update procs for inline input |
static pascal void TSMPreUpdateProc(TEHandle textH, long refCon) |
{ |
#pragma unused (refCon) |
ScriptCode keyboardScript; |
short mode; |
TextStyle theStyle; |
keyboardScript = GetScriptManagerVariable(smKeyScript); |
mode = doFont; |
if (! |
( |
(TEContinuousStyle(&mode, &theStyle, textH) )&& |
(FontToScript(theStyle.tsFont) == keyboardScript) |
) |
) |
{ |
theStyle.tsFont = GetScriptVariable(keyboardScript, smScriptAppFond); |
TESetStyle(doFont, &theStyle, false, textH); |
} |
} // TSMPreUpdateProc |
#if GENERATINGCFM |
static RoutineDescriptor gTSMPreUpdateProcRD = BUILD_ROUTINE_DESCRIPTOR(uppTSMTEPreUpdateProcInfo, TSMPreUpdateProc); |
static TSMTEPreUpdateUPP gTSMPreUpdateProc = &gTSMPreUpdateProcRD; |
#else |
static TSMTEPreUpdateUPP gTSMPreUpdateProc = NewTSMTEPreUpdateProc(TSMPreUpdateProc); |
#endif |
static pascal void TSMPostUpdateProc( |
TEHandle textH, |
long fixLen, |
long inputAreaStart, |
long inputAreaEnd, |
long pinStart, |
long pinEnd, |
long refCon) |
{ |
#pragma unused (textH, fixLen, inputAreaStart, inputAreaEnd, pinStart, pinEnd) |
AdjustScrollBars((WindowRef)refCon, false, false, nil); |
AdjustTE((WindowDataPtr)refCon, true); |
((WindowDataPtr)refCon)->changed = true; |
} // TSMPostUpdateProc |
#if GENERATINGCFM |
static RoutineDescriptor gTSMPostUpdateProcRD = BUILD_ROUTINE_DESCRIPTOR(uppTSMTEPostUpdateProcInfo, TSMPostUpdateProc); |
static TSMTEPostUpdateUPP gTSMPostUpdateProc = &gTSMPostUpdateProcRD; |
#else |
static TSMTEPostUpdateUPP gTSMPostUpdateProc = NewTSMTEPostUpdateProc(TSMPostUpdateProc); |
#endif |
// -------------------------------------------------------------------------------------------------------------- |
static pascal short SaveDialogHook(short item, DialogPtr dPtr, Boolean *isText) |
{ |
short theType; |
Handle theHandle; |
Rect theRect; |
short returnValue = item; |
switch (item) |
{ |
case sfHookFirstCall: |
if (GetWRefCon(GetDialogWindow(dPtr)) == sfMainDialogRefCon) |
{ |
GetDialogItem(dPtr, iTextUserItem, &theType, &theHandle, &theRect); |
theHandle = (Handle) gDrawTextUserItem; |
SetDialogItem(dPtr, iTextUserItem, theType, theHandle, &theRect); |
GetDialogItem(dPtr, iStationeryUserItem, &theType, &theHandle, &theRect); |
theHandle = (Handle) gDrawStationeryUserItem; |
SetDialogItem(dPtr, iStationeryUserItem, theType, theHandle, &theRect); |
GetDialogItem(dPtr, iTextDocumentItem, &theType, &theHandle, &theRect); |
SetControlValue((ControlHandle) theHandle, 1); |
GetDialogItem(dPtr, iStationeryDocumentItem, &theType, &theHandle, &theRect); |
SetControlValue((ControlHandle) theHandle, 0); |
*isText = true; |
} |
break; |
case iTextDocumentItem: |
case iTextUserItem: |
GetDialogItem(dPtr, iTextDocumentItem, &theType, &theHandle, &theRect); |
SetControlValue((ControlHandle) theHandle, 1); |
GetDialogItem(dPtr, iStationeryDocumentItem, &theType, &theHandle, &theRect); |
SetControlValue((ControlHandle) theHandle, 0); |
*isText = true; |
returnValue = sfHookNullEvent; |
break; |
case iStationeryDocumentItem: |
case iStationeryUserItem: |
GetDialogItem(dPtr, iTextDocumentItem, &theType, &theHandle, &theRect); |
SetControlValue((ControlHandle) theHandle, 0); |
GetDialogItem(dPtr, iStationeryDocumentItem, &theType, &theHandle, &theRect); |
SetControlValue((ControlHandle) theHandle, 1); |
*isText = false; |
returnValue = sfHookNullEvent; |
break; |
} |
return returnValue; |
} // SaveDialogHook |
#if GENERATINGCFM |
static RoutineDescriptor gSaveDialogHookRD = BUILD_ROUTINE_DESCRIPTOR(uppDlgHookYDProcInfo, SaveDialogHook); |
static DlgHookYDUPP gSaveDialogHook = &gSaveDialogHookRD; |
#else |
static DlgHookYDUPP gSaveDialogHook = NewDlgHookYDProc(SaveDialogHook); |
#endif |
// -------------------------------------------------------------------------------------------------------------- |
// Handle update/activate events behind Standard File |
static pascal Boolean SaveDialogFilter(DialogPtr theDialog, EventRecord *theEvent, |
short *itemHit, void *myDataPtr) |
{ |
#pragma unused(myDataPtr) |
if (StdFilterProc(theDialog, theEvent, itemHit)) |
return true; |
// Pass updates through (Activates are tricky...was mucking with Apple menu & thereby |
// drastically changing how the system handles the menu bar during our alert) |
if (theEvent->what == updateEvt /* || theEvent->what == activateEvt */ ) |
{ |
HandleEvent(theEvent); |
} |
return false; |
} // SaveDialogFilter |
#if GENERATINGCFM |
static RoutineDescriptor gSaveDialogFilterRD = BUILD_ROUTINE_DESCRIPTOR(uppModalFilterYDProcInfo, SaveDialogFilter); |
static ModalFilterYDUPP gSaveDialogFilter = &gSaveDialogFilterRD; |
#else |
static ModalFilterYDUPP gSaveDialogFilter = NewModalFilterYDProc(SaveDialogFilter); |
#endif |
// -------------------------------------------------------------------------------------------------------------- |
static OSErr TextSaveAs(WindowRef pWindow, WindowDataPtr pData) |
{ |
OSErr anErr = noErr; |
short oldRes, oldData; |
StandardFileReply sfReply; |
Boolean documentIsText; |
// save the old references -- if there is an error, we restore them |
oldRes = pData->resRefNum; |
oldData = pData->dataRefNum; |
// ask where and how to save this document |
{ |
Str255 defaultName; |
Point where = {-1, -1}; |
// setup for the call |
GetWTitle(pWindow, defaultName); |
SetCursor(&qd.arrow); |
// find out where the user wants the file |
CustomPutFile("\p", defaultName, &sfReply, |
kTextSaveAsDialogID, where, |
gSaveDialogHook, gSaveDialogFilter, nil, nil, &documentIsText); |
// map the cancel button into a cancelling error |
if (!sfReply.sfGood) |
anErr = eUserCanceled; |
} |
// can't replace over other types |
if (sfReply.sfReplacing) |
{ |
FInfo theInfo; |
FSpGetFInfo(&sfReply.sfFile, &theInfo); |
if ( (theInfo.fdType != 'TEXT') && (theInfo.fdType != 'sEXT') ) |
anErr = eDocumentWrongKind; |
} |
nrequire(anErr, StandardPutFile); |
if ( |
(sfReply.sfReplacing) && |
(oldData != -1) && |
(pData->fileSpec.vRefNum == sfReply.sfFile.vRefNum) && |
(pData->fileSpec.parID == sfReply.sfFile.parID) && |
EqualString(pData->fileSpec.name, sfReply.sfFile.name, false, false) |
) |
{ |
anErr = TextSave(pData); |
if (anErr == noErr) |
UpdateFileInfo(&sfReply.sfFile, documentIsText); |
} |
else |
{ |
// create the data file and resource fork |
(void) FSpDelete(&sfReply.sfFile); |
anErr = FSpCreate(&sfReply.sfFile, 'ttxt', documentIsText ? 'TEXT' : 'sEXT', 0); |
FSpCreateResFile(&sfReply.sfFile, 'ttxt', documentIsText ? 'TEXT' : 'sEXT', 0); |
nrequire(anErr, FailedCreate); |
// set the stationary bit, if we must |
if (!documentIsText) |
{ |
FInfo theInfo; |
FSpGetFInfo(&sfReply.sfFile, &theInfo); |
theInfo.fdFlags |= kIsStationery; |
FSpSetFInfo(&sfReply.sfFile, &theInfo); |
} |
// open both of forks |
anErr = FSpOpenDF(&sfReply.sfFile, fsRdWrPerm, &pData->dataRefNum); |
if (anErr == noErr) |
{ |
pData->resRefNum = FSpOpenResFile(&sfReply.sfFile, fsRdWrPerm); |
anErr = ResError(); |
} |
nrequire(anErr, FailedOpen); |
// call the standard save function to do the save |
anErr = TextSave(pData); |
// FALL THROUGH EXCEPTION HANDLING |
FailedOpen: |
FSpDelete(&sfReply.sfFile); |
FailedCreate: |
StandardPutFile: |
// finally, close the old files if everything went okay |
if (anErr == noErr) |
{ |
if (oldRes != -1) |
CloseResFile(oldRes); |
if (oldData != -1) |
FSClose(oldData); |
pData->isWritable = true; |
SetWTitle(pWindow, sfReply.sfFile.name); |
} |
else |
{ |
pData->resRefNum = oldRes; |
pData->dataRefNum = oldData; |
} |
} |
// save new location |
if (anErr == noErr) |
BlockMoveData(&sfReply.sfFile, &pData->fileSpec, sizeof(FSSpec)); |
// Return eUserCanceled so we can avoid closing/quitting if they cancel the SF dialog |
// // don't propagate this error |
// if (anErr == eUserCanceled) |
// anErr = noErr; |
return anErr; |
} // TextSaveAs |
// -------------------------------------------------------------------------------------------------------------- |
static void ApplyFace(short requestedFace, WindowRef pWindow, WindowDataPtr pData, short commandID) |
{ |
TextStyle style; |
short oldNumLines = (**(((TextDataPtr) pData)->hTE)).nLines; |
SaveCurrentUndoState(pData, commandID); |
style.tsFace = requestedFace; |
TESetStyle(doFace + ((requestedFace != normal) ? doToggle : 0), &style, true, ((TextDataPtr) pData)->hTE); |
TECalText(((TextDataPtr) pData)->hTE); |
oldNumLines -= (**(((TextDataPtr) pData)->hTE)).nLines; |
AdjustTE(pData, false); |
AdjustScrollBars(pWindow, (oldNumLines > 0), (oldNumLines > 0), nil); |
pData->changed = true; |
} // ApplyFace |
// -------------------------------------------------------------------------------------------------------------- |
static OSErr ApplySize(short requestedSize, WindowRef pWindow, WindowDataPtr pData, short commandID) |
{ |
OSErr anErr = noErr; |
TextStyle style; |
short oldNumLines = (**(((TextDataPtr) pData)->hTE)).nLines; |
SaveCurrentUndoState(pData, commandID); |
style.tsSize = requestedSize; |
TESetStyle(doSize, &style, true, ((TextDataPtr) pData)->hTE); |
TECalText(((TextDataPtr) pData)->hTE); |
if (CalculateTextEditHeight(((TextDataPtr) pData)->hTE) > 32767) |
{ |
style.tsSize = 0; |
TESetStyle(doSize, &style, true, ((TextDataPtr) pData)->hTE); |
anErr = eDocumentTooLarge; |
} |
oldNumLines -= (**(((TextDataPtr) pData)->hTE)).nLines; |
AdjustTE(pData, false); |
AdjustScrollBars(pWindow, (oldNumLines > 0), (oldNumLines > 0), nil); |
pData->changed = true; |
return anErr; |
} // ApplySize |
// -------------------------------------------------------------------------------------------------------------- |
// OOP INTERFACE ROUTINES |
// -------------------------------------------------------------------------------------------------------------- |
static OSErr TextUpdateWindow(WindowRef pWindow, WindowDataPtr pData) |
{ |
Rect updateArea = pData->contentRect; |
// be sure to also erase the area where the horizontal scroll bar |
// is missing. |
updateArea.bottom = GetWindowPort(pWindow)->portRect.bottom; |
EraseRect(&updateArea); |
TEUpdate(&pData->contentRect, ((TextDataPtr) pData)->hTE); |
DrawPictures(pData, ((TextDataPtr) pData)->hTE); |
DrawControls(pWindow); |
DrawGrowIcon(pWindow); |
return noErr; |
} // TextUpdateWindow |
// -------------------------------------------------------------------------------------------------------------- |
static OSErr TextCloseWindow(WindowRef pWindow, WindowDataPtr pData) |
{ |
#pragma unused (pWindow) |
// shut down text services |
if (pData->docTSMDoc) |
{ |
FixTSMDocument(pData->docTSMDoc); |
DeactivateTSMDocument(pData->docTSMDoc); |
DeleteTSMDocument(pData->docTSMDoc); |
} |
DisposeOfSpeech(true); |
DisposeHandle(((TextDataPtr) pData)->soundHandle); |
TEDispose(((TextDataPtr) pData)->hTE); |
DisposeHandle(((TextDataPtr) pData)->prevText); |
DisposeHandle(((TextDataPtr) pData)->prevStyle); |
TextRemoveContentsMenu(pData); |
return noErr; |
} // TextCloseWindow |
// -------------------------------------------------------------------------------------------------------------- |
static OSErr TextActivateEvent(WindowRef pWindow, WindowDataPtr pData, Boolean activating) |
{ |
#pragma unused (pWindow) |
// only modifyable docs can be active |
if (pData->originalFileType == 'TEXT') |
{ |
if (activating) |
{ |
TEActivate(((TextDataPtr) pData)->hTE); |
if (pData->docTSMDoc != nil) |
ActivateTSMDocument(pData->docTSMDoc); |
} |
else |
{ |
TEDeactivate(((TextDataPtr) pData)->hTE); |
if (pData->docTSMDoc != nil) |
DeactivateTSMDocument(pData->docTSMDoc); |
} |
} |
// add contents menu, if appropriate |
if (activating) |
{ |
TextAddContentsMenu(pData); |
} |
else |
{ |
TextRemoveContentsMenu(pData); |
} |
return noErr; |
} // TextActivateEvent |
// -------------------------------------------------------------------------------------------------------------- |
static Boolean TextFilterEvent(WindowRef pWindow, WindowDataPtr pData, EventRecord *pEvent) |
{ |
switch (pEvent->what) |
{ |
case nullEvent: |
if (pData->originalFileType == 'TEXT') |
{ |
if ( pWindow == FrontWindow() ) |
TEIdle(((TextDataPtr) pData)->hTE); |
} |
// if we stop speaking, ditch the channel |
if (gSpeechChannel) |
{ |
SpeechStatusInfo status; // Status of our speech channel. |
if ( |
(GetSpeechInfo( gSpeechChannel, soStatus, (void*) &status ) == noErr) |
&& (!status.outputBusy ) |
) |
DisposeOfSpeech(true); |
} |
break; |
} |
return false; |
} // TextFilterEvent |
// -------------------------------------------------------------------------------------------------------------- |
static OSErr TextScrollContent(WindowRef pWindow, WindowDataPtr pData, short deltaH, short deltaV) |
{ |
GrafPtr port = (GrafPtr) GetWindowPort(pWindow); |
RgnHandle srcRgn, dstRgn; |
Rect viewRect; |
// scroll the text area |
TEScroll(deltaH, deltaV, ((TextDataPtr) pData)->hTE); |
// calculate the region that is uncovered by the scroll |
srcRgn = NewRgn(); |
dstRgn = NewRgn(); |
viewRect = (**((TextDataPtr) pData)->hTE).viewRect; |
RectRgn( srcRgn, &viewRect ); |
SectRgn( srcRgn, port->visRgn, srcRgn ); |
SectRgn( srcRgn, port->clipRgn, srcRgn ); |
CopyRgn( srcRgn, dstRgn ); |
OffsetRgn( dstRgn, deltaH, deltaV ); |
SectRgn( srcRgn, dstRgn, dstRgn ); |
DiffRgn( srcRgn, dstRgn, srcRgn ); |
// clip to this new area |
GetClip(dstRgn); |
SetClip(srcRgn); |
DrawPictures(pData, ((TextDataPtr) pData)->hTE); |
SetClip(dstRgn); |
// all done with these calculation regions |
DisposeRgn( srcRgn ); |
DisposeRgn( dstRgn ); |
return eActionAlreadyHandled; |
} // TextScrollContent |
// -------------------------------------------------------------------------------------------------------------- |
static OSErr TextKeyEvent(WindowRef pWindow, WindowDataPtr pData, EventRecord *pEvent, Boolean isMotionKey) |
{ |
OSErr anErr = noErr; |
if (!(pEvent->modifiers & cmdKey)) |
{ |
char theKey = pEvent->message & charCodeMask; |
char theKeyCode = (pEvent->message >> 8) & charCodeMask; |
if ( ((theKey != kDeleteKey) || (theKeyCode != kForwardDeleteKey)) && |
((** ((TextDataPtr) pData)->hTE).teLength+1 > kMaxLength) ) |
anErr = eDocumentTooLarge; |
else |
{ |
long oldHeight = CalculateTextEditHeight(((TextDataPtr) pData)->hTE); |
long end = (**(((TextDataPtr) pData)->hTE)).selEnd; |
long start = (**(((TextDataPtr) pData)->hTE)).selStart; |
ObscureCursor(); |
SaveCurrentUndoState(pData, cTypingCommand); |
if (theKeyCode != kForwardDeleteKey) |
{ |
if (pEvent->modifiers & shiftKey) |
{ |
switch (theKeyCode) |
{ |
case kUpArrow: |
TEKey(theKey, ((TextDataPtr) pData)->hTE); |
TESetSelect((**(((TextDataPtr) pData)->hTE)).selStart, end, ((TextDataPtr) pData)->hTE); |
break; |
case kDownArrow: |
TESetSelect(end, end, ((TextDataPtr) pData)->hTE); |
TEKey(theKey, ((TextDataPtr) pData)->hTE); |
TESetSelect(start, (**(((TextDataPtr) pData)->hTE)).selEnd, ((TextDataPtr) pData)->hTE); |
break; |
case kRightArrow: |
{ |
Handle textHandle = (**(((TextDataPtr) pData)->hTE)).hText; |
Ptr textBuf; |
char state; |
state = HGetState(textHandle); |
HLock(textHandle); |
textBuf = *(**(((TextDataPtr) pData)->hTE)).hText; |
if (CharacterByteType(textBuf, start, smCurrentScript) != smSingleByte) |
++end; |
HSetState(textHandle, state); |
TESetSelect(start, ++end, ((TextDataPtr) pData)->hTE); |
} |
break; |
case kLeftArrow: |
if (start > 0) |
{ |
if (start > 1) |
{ |
Handle textHandle = (**(((TextDataPtr) pData)->hTE)).hText; |
Ptr textBuf; |
char state; |
state = HGetState(textHandle); |
HLock(textHandle); |
textBuf = *(**(((TextDataPtr) pData)->hTE)).hText; |
if (CharacterByteType(textBuf, start-1, smCurrentScript) != smSingleByte) |
--start; |
HSetState(textHandle, state); |
} |
TESetSelect(--start, end, ((TextDataPtr) pData)->hTE); |
} |
break; |
default: |
TEKey(theKey, ((TextDataPtr) pData)->hTE); |
} |
} |
else |
TEKey(theKey, ((TextDataPtr) pData)->hTE); |
} |
else |
{ |
if (end < (**(((TextDataPtr) pData)->hTE)).teLength) |
{ |
if (start == end) |
TEKey(kRightArrowCharCode, ((TextDataPtr) pData)->hTE); |
TEKey(kBackspaceCharCode, ((TextDataPtr) pData)->hTE); |
} |
} |
oldHeight -= CalculateTextEditHeight(((TextDataPtr) pData)->hTE); |
((TextDataPtr) pWindow)->insideClickLoop = true; |
AdjustTE(pData, false); |
AdjustScrollBars(pWindow, (oldHeight > 0), (oldHeight > 0), nil); |
((TextDataPtr) pWindow)->insideClickLoop = false; |
if (!isMotionKey) |
pData->changed = true; |
} |
} |
return anErr; |
} // TextKeyEvent |
// -------------------------------------------------------------------------------------------------------------- |
static OSErr TextContentClick(WindowRef pWindow, WindowDataPtr pData, EventRecord *pEvent) |
{ |
OSErr anErr = noErr; |
Point clickPoint = pEvent->where; |
ControlHandle theControl; |
RgnHandle hilightRgn; |
GlobalToLocal(&clickPoint); |
if (FindControl(clickPoint, pWindow, &theControl) == 0) |
{ |
if (gMachineInfo.haveDragMgr) |
{ |
hilightRgn = NewRgn(); |
TEGetHiliteRgn(hilightRgn, ((TextDataPtr) pData)->hTE); |
if (PtInRgn(clickPoint, hilightRgn)) |
{ |
SaveCurrentUndoState(pData, cTypingCommand); |
if (!DragText(pWindow, pData, pEvent, hilightRgn)) |
anErr = eActionAlreadyHandled; |
} |
else |
{ |
anErr = eActionAlreadyHandled; |
} |
DisposeRgn(hilightRgn); |
} |
else |
{ |
anErr = eActionAlreadyHandled; |
} |
} |
if ( (anErr == eActionAlreadyHandled) && (PtInRect(clickPoint, &pData->contentRect)) ) |
{ |
TEClick(clickPoint, (pEvent->modifiers & shiftKey) != 0, ((TextDataPtr) pData)->hTE); |
} |
return anErr; |
} // TextContentClick |
// -------------------------------------------------------------------------------------------------------------- |
static OSErr TextAdjustSize(WindowRef pWindow, WindowDataPtr pData, |
Boolean *didReSize) // input: was window resized, output: t->we resized something |
{ |
#pragma unused (pWindow) |
if (*didReSize) |
{ |
RecalcTE(pData, true); |
AdjustTE(pData, true); |
} |
else |
{ |
AdjustTE(pData, false); |
} |
return noErr; |
} // TextAdjustSize |
// -------------------------------------------------------------------------------------------------------------- |
static OSErr TextCommand(WindowRef pWindow, WindowDataPtr pData, short commandID, long menuResult) |
{ |
OSErr anErr = noErr; |
SetPort((GrafPtr) GetWindowPort(pWindow)); |
if ( (pData->docTSMDoc) && (menuResult != 0) ) |
FixTSMDocument(pData->docTSMDoc); |
switch (commandID) |
{ |
case cUndo: |
{ |
short oldNumLines = (**(((TextDataPtr) pData)->hTE)).nLines; |
PerformUndo(pData); |
oldNumLines -= (**(((TextDataPtr) pData)->hTE)).nLines; |
AdjustTE(pData, false); |
AdjustScrollBars(pWindow, (oldNumLines > 0), (oldNumLines > 0), nil); |
RecalcTE(pData, true); |
pData->changed = true; |
} |
break; |
case cCut: |
SaveCurrentUndoState(pData, cCut); |
{ |
short oldNumLines = (**(((TextDataPtr) pData)->hTE)).nLines; |
TECut(((TextDataPtr) pData)->hTE); // no need for TEToScrap with styled TE record |
oldNumLines -= (**(((TextDataPtr) pData)->hTE)).nLines; |
AdjustTE(pData, false); |
AdjustScrollBars(pWindow, (oldNumLines > 0), (oldNumLines > 0), nil); |
pData->changed = true; |
} |
break; |
case cCopy: |
TECopy(((TextDataPtr) pData)->hTE); // no need for TEToScrap with styled TE record |
AdjustTE(pData, false); |
break; |
case cClear: |
SaveCurrentUndoState(pData, cClear); |
{ |
short oldNumLines = (**(((TextDataPtr) pData)->hTE)).nLines; |
TEDelete(((TextDataPtr) pData)->hTE); |
oldNumLines -= (**(((TextDataPtr) pData)->hTE)).nLines; |
AdjustTE(pData, false); |
AdjustScrollBars(pWindow, (oldNumLines > 0), (oldNumLines > 0), nil); |
pData->changed = true; |
} |
break; |
case cPaste: |
SaveCurrentUndoState(pData, cPaste); |
{ |
short oldNumLines = (**(((TextDataPtr) pData)->hTE)).nLines; |
anErr = TEFromScrap(); |
if (anErr == noErr) |
{ |
// if the current length, plus the paste data, minus the data in the selection |
// would make the document too large, say so |
if ( |
((** ((TextDataPtr) pData)->hTE).teLength + |
TEGetScrapLength() - |
((** ((TextDataPtr) pData)->hTE).selEnd-(** ((TextDataPtr) pData)->hTE).selStart) |
) |
> kMaxLength) |
{ |
anErr = eDocumentTooLarge; |
} |
else |
{ |
Handle aHandle = (Handle) TEGetText(((TextDataPtr) pData)->hTE); |
Size oldSize = GetHandleSize(aHandle); |
Size newSize = oldSize + TEGetScrapLength(); |
OSErr saveErr; |
SetHandleSize(aHandle, newSize); |
saveErr = MemError(); |
SetHandleSize(aHandle, oldSize); |
if (saveErr != noErr) |
anErr = eDocumentTooLarge; |
else |
TEStylePaste(((TextDataPtr) pData)->hTE); |
} |
UnloadScrap(); |
} |
oldNumLines -= (**(((TextDataPtr) pData)->hTE)).nLines; |
AdjustTE(pData, false); |
AdjustScrollBars(pWindow, (oldNumLines > 0), (oldNumLines > 0), nil); |
pData->changed = true; |
} |
break; |
case cReplace: |
SaveCurrentUndoState(pData, cReplace); |
{ |
short result = ConductFindOrReplaceDialog(kReplaceWindowID); |
if (result == cancel) |
break; |
if (result == iReplaceAll) |
{ |
long newStart, newEnd; |
short oldNumLines = (**(((TextDataPtr) pData)->hTE)).nLines; |
TESetSelect(0, 0, ((TextDataPtr) pData)->hTE); |
while (PerformSearch((**((TextDataPtr) pData)->hTE).hText, |
(**((TextDataPtr) pData)->hTE).selStart, |
gFindString, gCaseSensitive, false, false, |
&newStart, &newEnd)) |
{ |
TESetSelect(newStart, newEnd, ((TextDataPtr) pData)->hTE); |
TEDelete(((TextDataPtr) pData)->hTE); |
TEInsert(&gReplaceString[1], gReplaceString[0], ((TextDataPtr) pData)->hTE); |
TESetSelect(newStart, newStart+gReplaceString[0], ((TextDataPtr) pData)->hTE); |
pData->changed = true; |
} |
oldNumLines -= (**(((TextDataPtr) pData)->hTE)).nLines; |
AdjustTE(pData, false); |
AdjustScrollBars(pWindow, (oldNumLines > 0), (oldNumLines > 0), nil); |
anErr = eActionAlreadyHandled; |
break; |
} |
} |
// fall through from replace |
case cReplaceAgain: |
SaveCurrentUndoState(pData, cReplaceAgain); |
{ |
long newStart, newEnd; |
Boolean isBackwards = ((gEvent.modifiers & shiftKey) != 0); |
if (PerformSearch((**((TextDataPtr) pData)->hTE).hText, |
isBackwards ? (**((TextDataPtr) pData)->hTE).selEnd : (**((TextDataPtr) pData)->hTE).selStart, |
gFindString, gCaseSensitive, isBackwards, gWrapAround, |
&newStart, &newEnd)) |
{ |
short oldNumLines = (**(((TextDataPtr) pData)->hTE)).nLines; |
TESetSelect(newStart, newEnd, ((TextDataPtr) pData)->hTE); |
TEDelete(((TextDataPtr) pData)->hTE); |
TEInsert(&gReplaceString[1], gReplaceString[0], ((TextDataPtr) pData)->hTE); |
TESetSelect(newStart, newStart+gReplaceString[0], ((TextDataPtr) pData)->hTE); |
oldNumLines -= (**(((TextDataPtr) pData)->hTE)).nLines; |
AdjustTE(pData, false); |
AdjustScrollBars(pWindow, (oldNumLines > 0), (oldNumLines > 0), nil); |
pData->changed = true; |
} |
else |
SysBeep(1); |
anErr = eActionAlreadyHandled; |
} |
break; |
case cFind: |
case cFindSelection: |
if (commandID == cFind) |
{ |
if (ConductFindOrReplaceDialog(kFindWindowID) == cancel) |
break; |
} |
else |
{ |
gFindString[0] = (**((TextDataPtr) pData)->hTE).selEnd - (**((TextDataPtr) pData)->hTE).selStart; |
BlockMoveData( (*(**((TextDataPtr) pData)->hTE).hText) + (**((TextDataPtr) pData)->hTE).selStart, &gFindString[1], gFindString[0]); |
} |
// fall through from find or find selection |
case cFindAgain: |
{ |
long newStart, newEnd; |
Boolean isBackwards = ((gEvent.modifiers & shiftKey) != 0); |
if (PerformSearch((**((TextDataPtr) pData)->hTE).hText, |
isBackwards ? (**((TextDataPtr) pData)->hTE).selStart : (**((TextDataPtr) pData)->hTE).selEnd, |
gFindString, gCaseSensitive, isBackwards, gWrapAround, |
&newStart, &newEnd)) |
{ |
TESetSelect(newStart, newEnd, ((TextDataPtr) pData)->hTE); |
AdjustTE(pData, false); |
AdjustScrollBars(pWindow, false, false, nil); |
} |
else |
SysBeep(1); |
anErr = eActionAlreadyHandled; |
} |
break; |
case cSelectAll: |
TESetSelect(0, (**((TextDataPtr) pData)->hTE).teLength, |
((TextDataPtr) pData)->hTE); |
AdjustTE(pData, false); |
AdjustScrollBars(pWindow, false, false, nil); |
anErr = eActionAlreadyHandled; |
break; |
// save turns into save as if this is a new document or if the original wasn't |
// available for writing |
case cSave: |
if ( |
(!pData->isWritable) || |
( (pData->dataRefNum == -1) && (pData->resRefNum == -1) ) |
) |
anErr = TextSaveAs(pWindow, pData); |
else |
anErr = TextSave(pData); |
break; |
case cSaveAs: |
anErr = TextSaveAs(pWindow, pData); |
break; |
// SUPPORTED FONTS |
// case cSelectFontStyle: |
case cSelectFont: |
SaveCurrentUndoState(pData, cSelectFont); |
{ |
Str255 itemName; |
Str255 menuName; |
TextStyle theStyle; |
short oldNumLines = (**(((TextDataPtr) pData)->hTE)).nLines; |
GetMenuItemText( GetMenuHandle( menuResult>>16 ), menuResult & 0xFFFF, itemName ); |
GetFNum( itemName, &theStyle.tsFont ); |
TESetStyle( doFont, &theStyle, true, ((TextDataPtr) pData)->hTE ); |
TECalText(((TextDataPtr) pData)->hTE); |
oldNumLines -= (**(((TextDataPtr) pData)->hTE)).nLines; |
AdjustTE(pData, false); |
AdjustScrollBars(pWindow, (oldNumLines > 0), (oldNumLines > 0), nil); |
pData->changed = true; |
} |
break; |
// SUPPORTED STYLES |
case cPlain: |
ApplyFace(normal, pWindow, pData, cPlain); |
break; |
case cBold: |
ApplyFace(bold, pWindow, pData, cBold); |
break; |
case cItalic: |
ApplyFace(italic, pWindow, pData, cItalic); |
break; |
case cUnderline: |
ApplyFace(underline, pWindow, pData, cUnderline); |
break; |
case cOutline: |
ApplyFace(outline, pWindow, pData, cOutline); |
break; |
case cShadow: |
ApplyFace(shadow, pWindow, pData, cShadow); |
break; |
case cCondensed: |
ApplyFace(condense, pWindow, pData, cCondensed); |
break; |
case cExtended: |
ApplyFace(extend, pWindow, pData, cExtended); |
break; |
// SUPPORTED SIZES |
case cSize9: |
anErr = ApplySize(9, pWindow, pData, cSize9); |
break; |
case cSize10: |
anErr = ApplySize(10, pWindow, pData, cSize10); |
break; |
case cSize12: |
anErr = ApplySize(12, pWindow, pData, cSize12); |
break; |
case cSize14: |
anErr = ApplySize(14, pWindow, pData, cSize14); |
break; |
case cSize18: |
anErr = ApplySize(18, pWindow, pData, cSize18); |
break; |
case cSize24: |
anErr = ApplySize(24, pWindow, pData, cSize24); |
break; |
case cSize36: |
anErr = ApplySize(36, pWindow, pData, cSize36); |
break; |
// SUPPORTED SOUND COMMANDS |
case cRecord: |
{ |
Handle tempHandle; |
// allocate our prefered buffer if we can, but if we can't |
// make sure that at least a minimum amount of RAM is around |
// before recording. |
tempHandle = NewHandle(kPrefBufferSize); |
anErr = MemError(); |
if (anErr != noErr) |
{ |
tempHandle = NewHandle(kMinBufferSize); |
anErr = MemError(); |
DisposeHandle(tempHandle); |
tempHandle = nil; |
} |
// if the preflight goes okay, do the record |
if (anErr == noErr) |
{ |
Point where = {50, 100}; |
anErr = SndRecord(nil, where, siGoodQuality, (SndListHandle*) &tempHandle); |
if (anErr == noErr) |
{ |
DisposeHandle(((TextDataPtr) pData)->soundHandle); |
((TextDataPtr) pData)->soundHandle = tempHandle; |
pData->changed = true; |
} |
else |
DisposeHandle(tempHandle); |
if (anErr == userCanceledErr) |
anErr = noErr; |
} |
} |
break; |
case cPlay: |
if (((TextDataPtr) pData)->soundHandle) |
(void) SndPlay(nil, (SndListHandle) ((TextDataPtr) pData)->soundHandle, false); |
break; |
case cErase: |
DisposeHandle(((TextDataPtr) pData)->soundHandle); |
((TextDataPtr) pData)->soundHandle = nil; |
pData->changed = true; |
break; |
case cSpeak: |
DisposeOfSpeech(false); |
if (gSpeechChannel == nil) |
anErr = NewSpeechChannel( &gCurrentVoice, &gSpeechChannel ); |
if ( anErr == noErr ) |
{ |
short textLength, textStart; |
// determine which text to speak |
if ( (**((TextDataPtr) pData)->hTE).selEnd > (**((TextDataPtr) pData)->hTE).selStart ) // If there is a selection. |
{ |
textLength = (**((TextDataPtr) pData)->hTE).selEnd - (**((TextDataPtr) pData)->hTE).selStart; |
textStart = (**((TextDataPtr) pData)->hTE).selStart; |
} |
else // No text selected. |
{ |
textLength = (**((TextDataPtr) pData)->hTE).teLength; |
textStart = 0; |
} |
gSpeakPtr = NewPtr(textLength); |
anErr = MemError(); |
if (anErr == noErr) |
{ |
BlockMoveData( *((**((TextDataPtr) pData)->hTE).hText) + textStart, gSpeakPtr, (Size) textLength ); |
anErr = SpeakText( gSpeechChannel, gSpeakPtr, textLength ); |
} |
} |
break; |
case cStopSpeaking: |
DisposeOfSpeech(true); |
break; |
case cSelectVoiceSubMenu: |
{ |
VoiceSpec newSpec; |
short i, menuIndex; |
Str255 itemText; |
short theVoiceCount; |
MenuHandle menu = GetMenuHandle(mVoices); |
// in order to change voices, we need to ditch the speaking |
DisposeOfSpeech(true); |
// get the name of the selected voice |
menuIndex = menuResult & 0xFFFF; |
GetMenuItemText(menu, menuIndex, itemText); |
if (CountVoices( &theVoiceCount ) == noErr) |
{ |
VoiceDescription description; // Info about a voice. |
for (i = 1; i <= theVoiceCount; ++i) |
{ |
if ( (GetIndVoice( i, &newSpec ) == noErr) && |
(GetVoiceDescription( &newSpec, &description, sizeof(description) ) == noErr ) ) |
{ |
if (IUCompString( itemText, description.name ) == 0) |
break; |
} |
} |
} |
gCurrentVoice = newSpec; |
for (i = CountMItems(menu); i >= 1; --i) |
CheckItem(menu, i, (menuIndex == i)); |
} |
break; |
case cSelectContents: |
{ |
Str255 searchStr; |
short menuIndex; |
long newStart, newEnd; |
menuIndex = menuResult & 0xFFFF; |
// get the search string for this menu item |
anErr = TextGetContentsListItem(pData, menuIndex, nil, searchStr, nil); |
if (anErr == noErr) |
{ |
if (PerformSearch( |
(**((TextDataPtr) pData)->hTE).hText, |
0, // start at beginning of text |
searchStr, |
false, // not case sensitive |
false, // forwards |
false, // wrap |
&newStart, |
&newEnd)) |
{ |
// <7> |
short amount; |
Point newSelectionPt; |
// get QuickDraw offset of found text, |
// scroll that amount plus a line height, |
// and add a fifth of the window for aesthetics (and |
// for slop to avoid fraction-of-line problems) |
newSelectionPt = TEGetPoint(newEnd, ((TextDataPtr) pData)->hTE); |
amount = - newSelectionPt.v + pData->vScrollAmount; |
amount += (pData->contentRect.bottom - pData->contentRect.top) / 5; |
SetControlAndClipAmount(pData->vScroll, &amount); |
if (amount != 0) |
{ |
DoScrollContent(pWindow, pData, 0, amount); |
} |
// move selection to beginning of found text |
// (are the Adjust calls necessary?) |
TESetSelect(newStart, newStart, ((TextDataPtr) pData)->hTE); |
AdjustTE(pData, false); |
AdjustScrollBars(pWindow, false, false, nil); |
} |
else |
{ |
// search failed |
SysBeep(10); |
} |
} |
} |
break; |
} |
return anErr; |
} // TextCommand |
// -------------------------------------------------------------------------------------------------------------- |
static OSErr TextAdjustMenus(WindowRef pWindow, WindowDataPtr pData) |
{ |
#pragma unused (pWindow) |
// enable the commands that we support for editable text document |
if (pData->originalFileType == 'TEXT') |
{ |
if (((TextDataPtr) pData)->prevCommandID != cNull) |
EnableCommand(cUndo); |
if ( (**((TextDataPtr) pData)->hTE).selEnd > (**((TextDataPtr) pData)->hTE).selStart ) // If there is a selection. |
{ |
EnableCommand(cCut); |
EnableCommand(cCopy); |
EnableCommand(cClear); |
EnableCommand(cFindSelection); |
} |
TEFromScrap(); |
if (TEGetScrapLength() > 0) |
EnableCommand(cPaste); |
EnableCommand(cSaveAs); |
EnableCommand(cSelectAll); |
EnableCommand(cFind); |
EnableCommand(cReplace); |
if (gFindString[0] != 0) |
{ |
EnableCommand(cFindAgain); |
EnableCommand(cReplaceAgain); |
} |
// enable all fonts, select the font current, if that's what's best |
EnableCommand(cSelectFont); |
{ |
short mode = doFont; |
Str255 fontName, itemName; |
Str255 styleName; |
TextStyle theStyle; |
Boolean isCont; |
isCont = TEContinuousStyle(&mode, &theStyle, ((TextDataPtr) pData)->hTE); |
if (isCont) |
{ |
GetFontName(theStyle.tsFont, fontName); |
} |
{ |
MenuHandle menu = GetMenuHandle( mFont ); |
short count = CountMItems(menu); |
short index; |
for (index = 1; index <= count; ++index) |
{ |
short mark; |
GetItemMark(menu, index, &mark); |
if (isCont) |
{ |
GetMenuItemText( menu, index, itemName ); |
// don't change the checkmark if it's a heirarchichal menu, because |
// the mark actually holds the ID of sub-menu |
if ((mark == noMark) || (mark == checkMark)) |
{ |
CheckItem(menu, index, EqualString(itemName, fontName, true, true) ); |
} |
else |
{ |
// if it is a sub menu, we check there too |
MenuHandle subMenu = GetMenuHandle(mark); |
short subCount = CountMItems(subMenu); |
short subIndex; |
if (EqualString(itemName, fontName, true, true)) |
{ |
SetItemStyle(menu, index, underline); |
for (subIndex = 1; subIndex <= subCount; ++subIndex) |
{ |
GetMenuItemText(subMenu, subIndex, itemName); |
CheckItem(subMenu, subIndex, EqualString(itemName, styleName, true, true) ); |
} |
} |
else |
{ |
SetItemStyle(menu, index, normal); |
for (subIndex = 1; subIndex <= subCount; ++subIndex) |
CheckItem(subMenu, subIndex, false ); |
} |
} |
} |
else |
{ |
if ((mark == noMark) || (mark == checkMark)) |
CheckItem(menu, index, false); |
else |
SetItemStyle(menu, index, normal); |
} |
} |
} |
} |
// enable the sizes, and outline what's currently valid |
{ |
short mode; |
TextStyle theStyle; |
Boolean isCont; |
short whichToCheck; |
// find out the continuous run of sizes |
whichToCheck = 0; |
mode = doSize; |
if (TEContinuousStyle(&mode, &theStyle, ((TextDataPtr) pData)->hTE)) |
{ |
whichToCheck = theStyle.tsSize; |
// default font size -> proper size |
if (whichToCheck == 0) |
whichToCheck = GetDefFontSize(); |
} |
// find out the font runs |
mode = doFont; |
isCont = TEContinuousStyle(&mode, &theStyle, ((TextDataPtr) pData)->hTE); |
EnableCommandCheckStyle(cSize9, whichToCheck == 9, (isCont & RealFont(theStyle.tsFont, 9)) ? outline : normal); |
EnableCommandCheckStyle(cSize10, whichToCheck == 10, (isCont & RealFont(theStyle.tsFont, 10)) ? outline : normal); |
EnableCommandCheckStyle(cSize12, whichToCheck == 12, (isCont & RealFont(theStyle.tsFont, 12)) ? outline : normal); |
EnableCommandCheckStyle(cSize14, whichToCheck == 14, (isCont & RealFont(theStyle.tsFont, 14)) ? outline : normal); |
EnableCommandCheckStyle(cSize18, whichToCheck == 18, (isCont & RealFont(theStyle.tsFont, 18)) ? outline : normal); |
EnableCommandCheckStyle(cSize24, whichToCheck == 24, (isCont & RealFont(theStyle.tsFont, 24)) ? outline : normal); |
EnableCommandCheckStyle(cSize36, whichToCheck == 36, (isCont & RealFont(theStyle.tsFont, 36)) ? outline : normal); |
} |
{ |
short mode = doFace; |
TextStyle theStyle; |
Style legalStyles; |
if (!TEContinuousStyle(&mode, &theStyle, ((TextDataPtr) pData)->hTE)) |
{ |
theStyle.tsFace = normal; |
EnableCommandCheck(cPlain, false); |
} |
else |
EnableCommandCheck(cPlain, theStyle.tsFace == normal); |
// <39> use the script manager to determine legal styles for this |
// run of text. If the legal styles are zero (trap unimplemented), |
// then we assume all styles. |
legalStyles = GetScriptVariable(GetScriptManagerVariable(smKeyScript), smScriptValidStyles); |
if (legalStyles == 0) |
legalStyles = 0xFFFF; |
if (legalStyles & bold) |
EnableCommandCheck(cBold, theStyle.tsFace & bold); |
if (legalStyles & italic) |
EnableCommandCheck(cItalic, theStyle.tsFace & italic); |
if (legalStyles & underline) |
EnableCommandCheck(cUnderline, theStyle.tsFace & underline); |
if (legalStyles & outline) |
EnableCommandCheck(cOutline, theStyle.tsFace & outline); |
if (legalStyles & shadow) |
EnableCommandCheck(cShadow, theStyle.tsFace & shadow); |
if (legalStyles & condense) |
EnableCommandCheck(cCondensed, theStyle.tsFace & condense); |
if (legalStyles & extend) |
EnableCommandCheck(cExtended, theStyle.tsFace & extend); |
} |
} |
// enable commands related to speaking the content if we have support for that |
if (gMachineInfo.haveTTS) |
{ |
// if we are speaking, we can stop |
if (gSpeechChannel) |
EnableCommand(cStopSpeaking); |
// even while speaking, you can re-speak or select a new voice |
EnableCommand(cSpeak); |
EnableCommand(cSelectVoice); |
EnableCommand(cSelectVoiceSubMenu); |
if ( (**((TextDataPtr) pData)->hTE).selEnd > (**((TextDataPtr) pData)->hTE).selStart ) // If there is a selection. |
ChangeCommandName(cSpeak, kTextStrings, iSpeakSelection); |
else |
ChangeCommandName(cSpeak, kTextStrings, iSpeakAll); |
} |
// enable the correct controls to go with sound input/output |
if (((TextDataPtr) pData)->soundHandle) |
EnableCommand(cPlay); |
if (pData->originalFileType == 'TEXT') |
{ |
if (((TextDataPtr) pData)->soundHandle) |
EnableCommand(cErase); |
else |
{ |
if (gMachineInfo.haveRecording) |
EnableCommand(cRecord); |
} |
} |
// enable the contents menu, if any |
(void) TextAdjustContentsMenu(pData); |
// enable commands that we support at all times |
if (GetControlMaximum(pData->vScroll) != 0) |
{ |
EnableCommand(cNextPage); |
EnableCommand(cPreviousPage); |
} |
return noErr; |
} // TextAdjustMenus |
// -------------------------------------------------------------------------------------------------------------- |
static OSErr TextGetDocumentRect(WindowRef pWindow, WindowDataPtr pData, |
LongRect * documentRectangle, Boolean forGrow) |
{ |
#pragma unused (pWindow) |
Rect theRect = pData->contentRect; |
Rect maxRect = (**GetGrayRgn()).rgnBBox; |
if ( (!forGrow) && (!(((TextDataPtr) pData)->insideClickLoop) ) ) |
RecalcTE(pData, false); |
theRect.bottom = CalculateTextEditHeight(((TextDataPtr) pData)->hTE); |
theRect.bottom += kMargins*2; |
theRect.right = maxRect.right; |
if (theRect.bottom < pData->contentRect.bottom) |
theRect.bottom = pData->contentRect.bottom; |
if (forGrow) |
theRect.bottom = maxRect.bottom-kScrollBarSize; |
RectToLongRect(&theRect, documentRectangle); |
return noErr; |
} // TextGetDocumentRect |
// -------------------------------------------------------------------------------------------------------------- |
static OSErr TextGetBalloon(WindowRef pWindow, WindowDataPtr pData, |
Point *localMouse, short * returnedBalloonIndex, Rect *returnedRectangle) |
{ |
#pragma unused (pWindow, pData, localMouse, returnedRectangle) |
*returnedBalloonIndex = iHelpTextContent; |
return noErr; |
} // TextGetBalloon |
// -------------------------------------------------------------------------------------------------------------- |
static long TextCalculateIdleTime(WindowRef pWindow, WindowDataPtr pData) |
{ |
#pragma unused (pWindow, pData) |
if ( (gMachineInfo.amInBackground) || (! (**(((TextDataPtr) pData)->hTE)).active) ) |
return(0x7FFFFFF); |
else |
return(1); |
} // TextCalculateIdleTime |
// -------------------------------------------------------------------------------------------------------------- |
static OSErr TextAdjustCursor(WindowRef pWindow, WindowDataPtr pData, |
Point * localMouse, Rect * globalRect) |
{ |
#pragma unused (pWindow, globalRect) |
OSErr anErr = noErr; |
CursHandle theCross; |
RgnHandle hilightRgn; |
if (gMachineInfo.haveDragMgr) |
{ |
hilightRgn = NewRgn(); |
TEGetHiliteRgn(hilightRgn, ((TextDataPtr) pData)->hTE); |
if (PtInRgn(*localMouse, hilightRgn)) |
{ |
SetCursor(&qd.arrow); |
DisposeRgn(hilightRgn); |
return eActionAlreadyHandled; |
} |
DisposeRgn(hilightRgn); |
} |
theCross = GetCursor(iBeamCursor); |
if (theCross) |
{ |
char oldState; |
oldState = HGetState((Handle) theCross); |
HLock((Handle) theCross); |
SetCursor(*theCross); |
HSetState((Handle) theCross, oldState); |
anErr = eActionAlreadyHandled; |
} |
return anErr; |
} // TextAdjustCursor |
// -------------------------------------------------------------------------------------------------------------- |
short gNilCaretProc[] = { |
0x584F, // ADDQ.W #$4, A7 |
0x4E75}; // RTS |
// -------------------------------------------------------------------------------------------------------------- |
static OSErr TextPrintPage(WindowRef pWindow, WindowDataPtr pData, |
Rect * pageRect, long *pageNum) |
{ |
#pragma unused (pWindow) |
OSErr anErr = noErr; |
short footerHeight; |
TEHandle hTE; |
Rect areaForThisPage; |
short ourPage = 1; |
Boolean documentHasFormControl = Count1Resources(kFormResource) != 0; |
// calculate area for the footer (page number) |
{ |
FontInfo theInfo; |
TextFont(0); |
TextSize(0); |
TextFace(normal); |
GetFontInfo(&theInfo); |
footerHeight = (theInfo.ascent + theInfo.descent + theInfo.leading) << 1; |
} |
// duplicate the text edit record, disable the selection before swapping the new port in |
hTE = ((TextDataPtr) pData)->hTE; |
TEDeactivate(hTE); |
anErr = HandToHand((Handle*) &hTE); |
nrequire(anErr, DuplicateTE); |
// turn off outline hilighting -- because the window is disabled while |
// printing is going on, but we don't want that disabled hilight to draw |
TEFeatureFlag(teFOutlineHilite, teBitClear, ((TextDataPtr) pData)->hTE); |
// now HERE'S a real hack! Under certain conditions, Text Edit will draw the |
// cursor, even if you said the edit record is inactive! This happens when |
// the internal state sez that the cursor hasn't been drawn yet. Lucky |
// for us, the caret is drawn through a hook, which we replace with a NOP. |
(**hTE).caretHook = (CaretHookUPP) gNilCaretProc; |
// point the rectangles to be the page rect minus the footer |
areaForThisPage = *pageRect; |
areaForThisPage.bottom -= footerHeight; |
InsetRect(&areaForThisPage, kPrintMargins, kPrintMargins); |
(**hTE).viewRect = (**hTE).destRect = areaForThisPage; |
// recalculate the line breaks |
TECalText(hTE); |
// point it at the printing port. |
(**hTE).inPort = qd.thePort; |
// now loop over all pages doing page breaking until we find our current |
// page, which we print, and then return. |
{ |
Rect oldPageHeight = (**hTE).viewRect; |
short currentLine = 0; |
long prevPageHeight = 0; |
while (ourPage <= *pageNum) |
{ |
long currentPageHeight = 0; |
// calculate the height including the current page, breaks |
// when one of three things happen: |
// 1) adding another line to this page would go beyond the length of the page |
// 2) a picture needs to be broken to the next page (NOT YET IMPLEMENTED) |
// 3) we run out of lines for the document |
// 4) if the line has a page break (defined as a non breaking space w/o a PICT) |
// POTENTIAL BUG CASES: |
// If a single line > the page height. Can that happen? If so, we need to |
// add something to handle it. |
do |
{ |
long currentLineHeight; |
// zero based count -- but one based calls to TEGetHeight |
currentLineHeight = TEGetHeight(currentLine+1, currentLine+1, hTE); |
// if adding this line would just be too much, break out of here |
if ((currentLineHeight + currentPageHeight) > (areaForThisPage.bottom - areaForThisPage.top)) |
break; |
++ currentLine; |
currentPageHeight += currentLineHeight; |
// if this line had a page break on it, break out of pagination |
if (documentHasFormControl && LineHasPageBreak(currentLine-1, hTE)) |
break; |
} while (currentLine < (**hTE).nLines); |
// if this the page we are trying to print |
if (ourPage == *pageNum) |
{ |
Str255 pageString; |
RgnHandle oldRgn = NewRgn(); |
// move onto the next page via offset by the previous pages -- but |
// clip to the current page height because we wouldn't want to see |
// half of a line from the next page at the bottom of a page. |
OffsetRect(&oldPageHeight, 0, -(prevPageHeight)); |
oldPageHeight.bottom = oldPageHeight.top + currentPageHeight; |
(**hTE).destRect = oldPageHeight; |
// clip to this area as well |
areaForThisPage.bottom = areaForThisPage.top + currentPageHeight; |
GetClip(oldRgn); |
ClipRect(&areaForThisPage); |
// draw the edit record, plus our cool pictures |
TEUpdate(&areaForThisPage, hTE); |
DrawPictures(pData, hTE); |
// restore the clip |
SetClip(oldRgn); |
DisposeRgn(oldRgn); |
// Draw the page string at the bottom of the page, centered |
pageString[0] = 2; |
pageString[1] = '-'; |
NumToString(*pageNum, &pageString[2]); |
pageString[0] += pageString[2]; |
pageString[2] = ' '; |
pageString[++pageString[0]] = ' '; |
pageString[++pageString[0]] = '-'; |
MoveTo( |
pageRect->left + |
((pageRect->right - pageRect->left) >> 1) - |
(StringWidth(pageString)>>1), |
pageRect->bottom - kPrintMargins); |
DrawString(pageString); |
// if we have completed all pages |
if (currentLine >= (**hTE).nLines) |
{ |
// tell it to stop printing |
*pageNum = -1; |
} |
// get out of here! |
break; |
} |
// move onto the next page via count |
++ourPage; |
// and the list of pages before now includes this page we just finished |
prevPageHeight += currentPageHeight; |
} |
} |
// restore text for visible page if done |
if (*pageNum == -1) |
{ |
TEFeatureFlag(teFOutlineHilite, teBitSet, ((TextDataPtr) pData)->hTE); |
TECalText(((TextDataPtr) pData)->hTE); |
if (pData->originalFileType != 'ttro') |
TEActivate(((TextDataPtr) pData)->hTE); |
} |
// FALL THROUGH EXCEPTION HANDLING |
// Dispose this way to avoid disposing of any owned objects |
DisposeHandle((Handle) hTE); |
DuplicateTE: |
return anErr; |
} // TextPrintPage |
// -------------------------------------------------------------------------------------------------------------- |
static OSErr TextMakeWindow(WindowRef pWindow, WindowDataPtr pData) |
{ |
#pragma unused(pWindow) |
OSErr anErr = noErr; |
pData->bumpUntitledCount = true; |
pData->pScrollContent = (ScrollContentProc) TextScrollContent; |
pData->pAdjustSize = (AdjustSizeProc) TextAdjustSize; |
pData->pGetDocumentRect = (GetDocumentRectProc) TextGetDocumentRect; |
pData->pAdjustMenus = (AdjustMenusProc) TextAdjustMenus; |
pData->pCommand = (CommandProc) TextCommand; |
pData->pCloseWindow = (CloseWindowProc) TextCloseWindow; |
pData->pFilterEvent = (FilterEventProc) TextFilterEvent; |
pData->pActivateEvent = (ActivateEventProc) TextActivateEvent; |
pData->pUpdateWindow = (UpdateWindowProc) TextUpdateWindow; |
pData->pPrintPage = (PrintPageProc) TextPrintPage; |
// we only support keydowns and editing for modifable docs |
if (pData->originalFileType != 'ttro') |
{ |
pData->pKeyEvent = (KeyEventProc) TextKeyEvent; |
pData->pContentClick = (ContentClickProc) TextContentClick; |
pData->pAdjustCursor = (AdjustCursorProc) TextAdjustCursor; |
pData->pGetBalloon = (GetBalloonProc) TextGetBalloon; |
pData->pCalculateIdleTime = (CalculateIdleTimeProc) TextCalculateIdleTime; |
// We can always reference our Drag handlers, because they will not be called if we |
// don't have the Drag Manager available. We needn't check here (it would be redundant). |
pData->pDragTracking = (DragTrackingProc) TextDragTracking; |
pData->pDragReceive = (DragReceiveProc) TextDragReceive; |
pData->documentAcceptsText = true; |
} |
// leave room for the grow area at bottom |
pData->hasGrow = true; |
pData->contentRect.bottom -= kScrollBarSize; |
if ((pData->contentRect.right - pData->contentRect.left) > kOnePageWidth) |
pData->contentRect.right = pData->contentRect.left + kOnePageWidth; |
((TextDataPtr) pData)->hTE = TEStyleNew(&pData->contentRect, &pData->contentRect); |
anErr = MemError(); |
nrequire(anErr, TENewFailed); |
pData->hScrollAmount = 0; |
pData->vScrollAmount = TEGetHeight(0, 0, ((TextDataPtr) pData)->hTE); |
TEAutoView(true, ((TextDataPtr) pData)->hTE); |
// Setup our click loop to handle autoscrolling |
((TextDataPtr) pData)->docClick = (**(((TextDataPtr) pData)->hTE)).clickLoop; |
TESetClickLoop(gMyClickLoop, ((TextDataPtr) pData)->hTE); |
// if we have a data fork, read the contents into the record |
if (pData->dataRefNum != -1) |
{ |
long dataSize; |
GetEOF(pData->dataRefNum, &dataSize); |
if (dataSize > kMaxLength) |
anErr = eDocumentTooLarge; |
else |
{ |
Handle tempHandle = NewHandle(dataSize); |
anErr = MemError(); |
if (anErr == noErr) |
{ |
// read the text in |
SetFPos(pData->dataRefNum, fsFromStart, 0); |
anErr = FSRead(pData->dataRefNum, &dataSize, * tempHandle); |
// then insert it. |
if (anErr == noErr) |
{ |
HLock(tempHandle); |
TEStyleInsert(*tempHandle, dataSize, nil, ((TextDataPtr) pData)->hTE); |
anErr = MemError(); |
} |
DisposeHandle(tempHandle); |
} |
} |
} |
nrequire(anErr, ReadData); |
// if we have a resource fork, read the contents |
if (pData->resRefNum != -1) |
{ |
short oldResFile = CurResFile(); |
Handle theStyle; |
// read the style information |
UseResFile(pData->resRefNum); |
theStyle = Get1Resource('styl', 128); |
if (theStyle) |
{ |
HNoPurge(theStyle); |
TEUseStyleScrap(0, 32767, (StScrpHandle) theStyle, true, ((TextDataPtr) pData)->hTE); |
ReleaseResource(theStyle); |
} |
// if we have sound, load it in and detach it |
{ |
Handle soundHandle = Get1Resource('snd ', kSoundBase); |
if (soundHandle) |
{ |
HNoPurge(soundHandle); |
DetachResource(soundHandle); |
((TextDataPtr) pData)->soundHandle = soundHandle; |
} |
} |
UseResFile(oldResFile); |
} |
// hook out drawing of the non-breaking space for read only documents, |
// for modifiable documents, enable outline hiliting (ie, when TE window |
// isn't in front, show the gray outline) |
if (pData->originalFileType == 'ttro') |
{ |
UniversalProcPtr hookRoutine = (UniversalProcPtr)gMyDrawGlue; |
TECustomHook(intDrawHook, &hookRoutine, ((TextDataPtr) pData)->hTE); |
} |
else |
{ |
TEFeatureFlag(teFOutlineHilite, teBitSet, ((TextDataPtr) pData)->hTE); |
} |
// make a TSM document if this is editable |
if ( |
(pData->originalFileType != 'ttro') && |
(gMachineInfo.haveTSMTE) |
) |
{ |
OSType supportedInterfaces[1]; |
supportedInterfaces[0] = kTSMTEInterfaceType; |
if (NewTSMDocument(1, supportedInterfaces, |
&pData->docTSMDoc, (long)&pData->docTSMRecHandle) == noErr) |
{ |
long response; |
(**(pData->docTSMRecHandle)).textH = ((TextDataPtr) pData)->hTE; |
if ((Gestalt(gestaltTSMTEVersion, &response) == noErr) && (response == gestaltTSMTE1)) |
(**(pData->docTSMRecHandle)).preUpdateProc = gTSMPreUpdateProc; |
(**(pData->docTSMRecHandle)).postUpdateProc = gTSMPostUpdateProc; |
(**(pData->docTSMRecHandle)).updateFlag = kTSMTEAutoScroll; |
(**(pData->docTSMRecHandle)).refCon = (long)pData; |
} |
} |
// now we have added text, so adjust views and such as needed |
TESetSelect(0, 0, ((TextDataPtr) pData)->hTE); |
RecalcTE(pData, true); |
AdjustTE(pData, true); |
// ???? Hack to get around a 7.0 TextEdit bug. If you are pasting a multiple |
// line clipboard into TE, *and* the TextEdit record is new, *and* the selection |
// is at the begining of the doc (0,0 as above), *and* you haven't moved the |
// cursor around at all, then TE pastes thinking it's at the end of the line, |
// when it really should be at the begining. Then if you <cr> with the cursor |
// visible, it'll leave a copy behind. |
// I'm not happy with this, but I don't know another way around the problem. |
if (pData->originalFileType != 'ttro') |
{ |
TEKey(0x1F, ((TextDataPtr) pData)->hTE); |
TEKey(0x1E, ((TextDataPtr) pData)->hTE); |
} |
// <39> if this is a new document, convert the "system size", "system font", and |
// "application font" into real font IDs and sizes. This is so that |
// if someone saves this document and opens it with another script |
// system, they don't get all huffy that the font changed on them. |
// It also solves problems with cut and paste to applications too stupid |
// to know that "zero" means system size. |
if (pData->dataRefNum == -1) |
{ |
TEHandle hTE = ((TextDataPtr) pData)->hTE; |
short mode = doAll; |
TextStyle theStyle; |
TEContinuousStyle(&mode, &theStyle, hTE); |
if (theStyle.tsSize == 0) |
theStyle.tsSize = GetDefFontSize(); |
if (theStyle.tsFont == systemFont) |
theStyle.tsFont = GetSysFont(); |
if (theStyle.tsFont == applFont) |
theStyle.tsFont = GetAppFont(); |
mode = doAll; |
TESetStyle(mode, &theStyle, false, hTE); |
} |
// if stationary, use untitled and close down the files |
if (pData->originalFileType == 'sEXT') |
{ |
pData->originalFileType = 'TEXT'; |
pData->openAsNew = true; |
if (pData->resRefNum != -1) |
CloseResFile(pData->resRefNum); |
if (pData->dataRefNum != -1) |
FSClose(pData->dataRefNum); |
pData->resRefNum = pData->dataRefNum = -1; |
} |
// initalize undo information |
((TextDataPtr) pData)->prevCommandID = cNull; |
// if we have voices, add them to the menu |
if ( (gMachineInfo.haveTTS) && (!gAddedVoices) ) |
{ |
short theVoiceCount; |
short i, item; |
if (CountVoices( &theVoiceCount ) == noErr) |
{ |
VoiceSpec spec; // A voice to add to the menu. |
VoiceDescription description; // Info about a voice. |
MenuHandle voicesMenu = GetMenuHandle(mVoices); |
anErr = GetVoiceDescription( nil, &description, sizeof(description) ); |
if (anErr == noErr) |
{ |
gCurrentVoice = description.voice; |
for (i = 1; i <= theVoiceCount; ++i) |
{ |
if ( (GetIndVoice( i, &spec ) == noErr) && |
(GetVoiceDescription( &spec, &description, sizeof(description) ) == noErr ) ) |
{ |
short menuCount = CountMItems( voicesMenu ); |
// first one we are adding == get rid of item already there |
if ( (i == 1) && (menuCount > 0) ) |
{ |
DeleteMenuItem( voicesMenu, 1 ); |
--menuCount; |
} |
for ( item = 1; item <= menuCount; ++item ) |
{ |
Str255 itemText; |
GetMenuItemText( voicesMenu, item, itemText ); |
/*1st > 2nd*/ |
if ( IUCompString( itemText, description.name ) == 1 ) |
break; // Found where name goes in list. |
} |
InsertMenuItem( voicesMenu, "\p ", item - 1 ); |
SetMenuItemText( voicesMenu, item, description.name ); |
CheckItem(voicesMenu, item, |
((gCurrentVoice.creator == spec.creator) && (gCurrentVoice.id == spec.id)) ); |
} |
} |
} |
gAddedVoices = true; |
} |
} // end of adding voices |
return noErr; |
// EXCEPTION HANDLING |
ReadData: |
TEDispose(((TextDataPtr) pData)->hTE); |
TENewFailed: |
return anErr; |
} // TextMakeWindow |
// -------------------------------------------------------------------------------------------------------------- |
OSErr TextPreflightWindow(PreflightPtr pPreflightData) |
{ |
pPreflightData->continueWithOpen = true; |
pPreflightData->wantVScroll = true; |
pPreflightData->doZoom = true; |
pPreflightData->makeProcPtr = TextMakeWindow; |
if (pPreflightData->fileType != 'ttro') |
pPreflightData->openKind = fsRdWrPerm; |
pPreflightData->storageSize = sizeof(TextDataRecord); |
// get strings that mark the picture |
GetIndString(gPictMarker1, kTextStrings, iPictureMarker1); |
GetIndString(gPictMarker2, kTextStrings, iPictureMarker2); |
return noErr; |
} // TextPreflightWindow |
// -------------------------------------------------------------------------------------------------------------- |
void TextGetFileTypes(OSType * pFileTypes, OSType * pDocumentTypes, short * numTypes) |
{ |
pFileTypes[*numTypes] = 'TEXT'; |
pDocumentTypes[*numTypes] = kTextWindow; |
(*numTypes)++; |
pFileTypes[*numTypes] = 'ttro'; |
pDocumentTypes[*numTypes] = kTextWindow; |
(*numTypes)++; |
pFileTypes[*numTypes] = 'sEXT'; |
pDocumentTypes[*numTypes] = kTextWindow; |
(*numTypes)++; |
} // TextGetFileTypes |
// -------------------------------------------------------------------------------------------------------------- |
// TextAddContentsMenu checks if there is a contents list and, if there |
// is, creates a new menu handle for the contents list and fills it with |
// the appropriate visible items |
void TextAddContentsMenu(WindowDataPtr pData) |
{ |
MenuHandle contentsMenu; |
Str255 menuStr; |
short totalItems; |
short index; |
OSErr err; |
contentsMenu = GetMenuHandle(mContents); |
require(contentsMenu == nil, ContentsMenuAlreadyInstalled); |
// Is there a contents list? If so, get the menu name |
// and the number of items in the list |
if (TextGetContentsListItem(pData, 0, menuStr, nil, &totalItems) == noErr) |
{ |
// create the menu and fill it with all the items |
// listed in the string list resource |
contentsMenu = NewMenu(mContents, menuStr); |
require(contentsMenu != nil, CantCreateContentsMenu); |
for (index = 1; index < totalItems; index++) |
{ |
err = TextGetContentsListItem(pData, index, menuStr, nil, nil); |
require(err == noErr, CantGetItem); |
AppendMenu(contentsMenu, menuStr); |
} |
// add the menu to the menu bar, and redraw the menu bar |
InsertMenu(contentsMenu, 0); |
DrawMenuBar(); |
} |
else |
{ |
// no contents, do nothing |
} |
return; |
// error handling |
CantGetItem: |
CantCreateContentsMenu: |
if (contentsMenu) DisposeMenu(contentsMenu); |
ContentsMenuAlreadyInstalled: |
return; |
} // TextAddContentsMenu |
// TextRemoveContentsMenu removes the contents menu, if any, |
// and redraws the menu bar |
static void TextRemoveContentsMenu(WindowDataPtr pData) |
{ |
#pragma unused (pData) |
MenuHandle contentsMenu; |
contentsMenu = GetMenuHandle(mContents); |
if (contentsMenu) |
{ |
DeleteMenu(mContents); |
DisposeMenu(contentsMenu); |
DrawMenuBar(); |
} |
} // TextRemoveContentsMenu |
// TextGetContentsListItem is a general utility routine for examining the |
// contents menu list, returning the menu and search strings, and returning |
// the total number of entries in the contents list. |
// |
// Pass 0 as itemNum to retrieve the strings for the contents menu title. |
// |
// Pass nil for menuStr, searchStr, or totalItems if you don't want that |
// info returned. |
// |
// Returns eDocumentHasNoContentsEntries if there is no contents string list |
// resource for the specified window |
static OSErr TextGetContentsListItem(WindowDataPtr pData, short itemNum, |
StringPtr menuStr, StringPtr searchStr, |
short *totalItems) |
{ |
OSErr err; |
short oldResFile; |
short menuItemNum; |
short searchItemNum; |
Handle contentsStrListHandle; |
// if no original resource file, don't bother |
if (pData->resRefNum == -1) |
{ |
return eDocumentHasNoContentsEntries; |
} |
err = noErr; |
oldResFile = CurResFile(); |
UseResFile(pData->resRefNum); |
// two entries per item |
// |
// first (itemNum zero) is content menu title |
// (second -- itemNum one, search string for menu title -- is unused) |
menuItemNum = itemNum * 2 + 1; |
searchItemNum = menuItemNum + 1; |
contentsStrListHandle = Get1Resource('STR#', kContentsListID); |
if (contentsStrListHandle) |
{ |
if (totalItems) *totalItems = (*(short *)*contentsStrListHandle) / 2; |
if (menuStr) GetIndString(menuStr, kContentsListID, menuItemNum); |
if (searchStr) |
{ |
GetIndString(searchStr, kContentsListID, searchItemNum); |
if (searchStr[0] == 0) |
{ |
// search string was empty, so use the |
// menu string as the search string |
GetIndString(searchStr, kContentsListID, menuItemNum); |
} |
} |
} |
else |
{ |
err = eDocumentHasNoContentsEntries; |
if (totalItems) *totalItems = 0; |
} |
UseResFile(oldResFile); |
return err; |
} // TextGetContentsListItem |
// TextAdjustContentsMenu enables the items in the contents menu |
// |
// This routine is essentially a placeholder in case the contents |
// menu really were to be dynamically enabled. |
static OSErr TextAdjustContentsMenu(WindowDataPtr pData) |
{ |
#pragma unused (pData) |
EnableCommand(cSelectContents); |
return(noErr); |
} // TextAdjustContentsMenu |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-03-26