Sources/MSFile.c

// MSFile.c
//
// Original version by Jon Lansdell and Nigel Humphreys.
// 4.0 and 3.1 updates by Greg Sutton.
// ©Apple Computer Inc 1996, all rights reserved.
 
/*
    Changes for 4.0
 
    28-Feb-96 : GS : Added script to document file. This script is attached to the document
                        and gets first go at handling Apple events targetted at the document.
*/
 
#include <Errors.h>
#include <Resources.h>
#include <Desk.h>
#ifdef THINK_C
    #include "PLStrs.h"
#else
    #include <PLStringFuncs.h>
#endif
#include <AppleEvents.h>
#include <AERegistry.h>
#include <StandardFile.h>
#include "MSFile.h"
#include "Offscreen.h"
#include "MSResultWind.h"
#include "MSAEWindowUtils.h"
#include "MSScript.h"
#include "MSAESetData.h"
 
#include <LowMem.h>
 
    // Constants
const   OSType  kStyleInfoType      = 'TFSF';
const   OSType  kHeaderInfoType     = 'TFSS';
const   OSType  kGXPrinterInfoType  = 'TFGX';
 
 
//  Name:           FileError
//  Purpose:        Puts up an error alert.
 
#pragma segment File
 
void    FileError( Str255 s, Str255 f )
{
    short    alertResult;
    
    SetCursor(&qd.arrow);
    ParamText(s, f, (unsigned char *)"", (unsigned char *)"");
    alertResult = Alert( ErrorAlert, NULL );
}
 
//  Name:           DoClose
//  Purpose:        Closes a window.
 
#pragma segment File
 
OSErr   DoClose( WindowPtr aWindow,Boolean canInteract,DescType dialogAnswer )
{
    DPtr    aDocument;
    short   alertResult;
    OSErr   myErr;
                        
    myErr = noErr;
        
    aDocument = DPtrFromWindowPtr(aWindow);
    if ( ! aDocument )
        return noErr;
        
// Save if the windows contents have changed and it's not the results window
    if ((aDocument->dirty) && !IsThisResultWind(aWindow))
    {
        if (canInteract && (dialogAnswer==kAEAsk))
        {
            alertResult = DoFileDialog ( kSaveDontsaveDialog, aWindow );
            switch ( alertResult )
            {
                case kStdOkItemIndex:
                    if (aDocument->everSaved == false)
                    {
                        myErr = GetFileNameToSaveAs(aDocument);
                        if (myErr == noErr)
                            myErr = DoSave(aDocument, aDocument->theFSSpec);
                    }
                    else
                        myErr = SaveUsingTemp(aDocument);
                break;
                                                            
                case kStdCancelItemIndex:
                    return(userCanceledErr);
                break;
                
                case kDontSaveItemIndex:
                    aDocument->dirty = false;
                break;
            }
        }
        else
        {
            if (dialogAnswer==kAEYes)
                if (aDocument->everSaved == false)
                {
                    if (canInteract)
                    {
                        myErr = GetFileNameToSaveAs(aDocument);
                        if (myErr==noErr)
                            myErr = DoSave(aDocument, aDocument->theFSSpec);
                    }
                    else    
                        return(errAENoUserInteraction);
                }
                else
                    myErr = SaveUsingTemp(aDocument);
            else
                myErr = noErr; // Don't save
        }
    }
            
    if ( myErr == noErr )
        CloseMyWindow(aWindow);
            
    return(myErr);
}
 
 
// This is called to display the "save before closing" and "revert" dialogs.
 
short   DoFileDialog( short theDlogID, WindowRef theWindow )
{
    short       theItem;
    DialogRef   theDialog;
    WindowRef   dialogWindow;
    DPtr        theDocument;
    Str255      theTitle, theReason;
    
    
    theDocument = DPtrFromWindowPtr ( theWindow );
    
    theDialog = GetNewDialog ( theDlogID, nil, (WindowRef) -1 );
    dialogWindow = GetDialogWindow ( theDialog );
    SetPortWindowPort ( dialogWindow );
    
    
    if ( theDocument->everSaved == false )
        GetWTitle ( theWindow, theTitle );      // Pick it up as a script may have changed it
    else
        PLstrcpy ( theTitle, theDocument->theFileName );
    
    if ( gQuitting )
        GetIndString ( theReason, kMiscStrings, kQuittingIndex );
    else
        GetIndString ( theReason, kMiscStrings, kClosingindex );
    
    SetCursor ( &qd.arrow );
    ParamText ( theTitle, theReason, nil, nil );
    ShowMSWindow ( dialogWindow );
    SelectWindow ( dialogWindow );
    
    SetDialogDefaultItem ( theDialog, kStdOkItemIndex );
    SetDialogCancelItem ( theDialog, kStdCancelItemIndex );
    
    // As long as the only enabled items are the dimissers, 
    // there is no need to call ModalDialog within a loop.
    ModalDialog ( nil, &theItem );
    DisposeDialog ( theDialog );
    
    return theItem;
}       
 
 
#pragma segment File
 
//  DoQuit
//  saveOpt - one of kAEAsk,kAEYes,kAENo
//  if kAEYes or kAEAsk then AEInteactWithUser should have been called
//  before DoQuit. Assumes that it can interact if it needs to.
 
void    DoQuit( DescType saveOpt )
{
    WindowPtr    aWindow;
    WindowPtr    nextWindow;
    WindowPeek   nextWPeek;
    short        theKind;
    OSErr        check;
    
    aWindow = (WindowPtr)LMGetWindowList();
    
    gQuitting = true;
    while ( aWindow )
    {
        nextWPeek  = ((WindowPeek)aWindow)->nextWindow;
        nextWindow = &nextWPeek->port;
        if (Ours(aWindow))
        {
            check = DoClose(aWindow, true, saveOpt);
            if ( check != noErr )
            {
                gQuitting = false;
                return;
            }
        }
        else
        {
            theKind = ((WindowPeek)aWindow)->windowKind;
            if (theKind < 0)
                CloseDeskAcc(theKind);
        }
        aWindow = nextWindow;
    }
} // DoQuit
 
 
OSErr   GetFile( FSSpec *theFSSpec )
{
    SFTypeList         myTypes;
    StandardFileReply  reply;
    
    myTypes[0] = 'TEXT';
    
    StandardGetFile( NULL, 1, myTypes, &reply );
    
    if (reply.sfGood)
    {
        *theFSSpec = reply.sfFile;
        return(noErr);
    }
    else
        return(userCanceledErr);
}
 
 
#pragma segment File
 
OSErr   DoCreate( FSSpec theSpec )
{
    OSErr err;
    
    err = FSpCreate(&theSpec, MenuScripterAppSig, 'TEXT', smSystemScript);
    
    if (err != noErr)
         ShowError((unsigned char *)"\pDoCreate", err);
            
    return(err);
}
 
 
#pragma segment File
 
OSErr   WriteFile( DPtr theDocument, short refNum, FSSpec theFSSpec )
{
    short        resFile;
    long         length;
    HHandle      theHHandle;
    StScrpHandle theSHandle;
    OSErr        err;
    StringHandle theAppName;
    short        oldSelStart;
    short        oldSelEnd;
    Handle       thePHandle;
    Handle       myText;
    Handle       thePGXHandle;
    
    tWindowOffscreen*   theOffscreen = nil;
    
    // first write out the text to the data fork
        
    length = (*(theDocument->theText))->teLength;
    myText = (*(theDocument->theText))->hText;
        
    HLock( myText );
    
    err = FSWrite(refNum, &length, *myText);
    if ( noErr != err )
        return err;
            
    HUnlock( myText );
    
        // we are writing to a temporary file, so we need to create the resource file
        // before writing out the resources
        // now open the resource file
        
    HCreateResFile(theFSSpec.vRefNum, theFSSpec.parID, theFSSpec.name);
    err = ResError();
    if (err)
    {
        ShowError((unsigned char *)"\pHCreateResFile", err);
        return(err);
    }
    
    resFile = HOpenResFile( theFSSpec.vRefNum, theFSSpec.parID, theFSSpec.name, fsWrPerm );
    err = ResError();
    
    if (err)
    {
        ShowError((unsigned char *)"\pHOpenResFile", err);
        return(err);
    }
    
    // Write out our 'TFSF' resource to file
     
    //  Draw everything into offscreen pixmap.
    theOffscreen = DrawOffscreen ( theDocument->theWindow );
    if ( theOffscreen  )
        (*theDocument->theText)->inPort = (GrafPtr) theOffscreen->offscreenWorld;
    
    
    oldSelStart = (*(theDocument->theText))->selStart;
    oldSelEnd   = (*(theDocument->theText))->selEnd;
    TESetSelect( 0, kMaxTELength, theDocument->theText );
    
    theSHandle = TEGetStyleScrapHandle(theDocument->theText);
    
    TESetSelect(oldSelStart,oldSelEnd, theDocument->theText);
    
    
    if ( theOffscreen )
    {
        // If it wasn't for the caret, we wouldn't need to draw this
        theOffscreen = DrawOnscreen ( theOffscreen );
        (*theDocument->theText)->inPort = theDocument->theWindow;
    }
    
    AddResource((Handle)theSHandle, kStyleInfoType, 255, (unsigned char *)"\pStyle Info");
    err = ResError();
    if (err)
    {
        ShowError((unsigned char *)"\pAddResource - Style Info", err);
        return(err);
    }
                    
    // Write out the printer info
                
    // First the QD info
                
    if (theDocument->thePrintSetup)
    {
        thePHandle = (Handle)theDocument->thePrintSetup;
        err = HandToHand(&thePHandle);
        
        AddResource(thePHandle, 'TFSP', 255, (unsigned char *)"\pPrinter Info");
        err = ResError();
        if (err)
            {
                ShowError((unsigned char *)"\pAddResource- TFSP", err);
                return(err);
            }
    }
                
    // Next the QD QX info
    if ( ( gGXIsPresent ) && ( theDocument->documentJob ) )
    {
        thePGXHandle = NewHandle(0);
        GXFlattenJobToHdl(theDocument->documentJob, thePGXHandle);
    
        AddResource(thePGXHandle, kGXPrinterInfoType, 255, (unsigned char *)"\pGX Printer Info");
        err = ResError();
        if (err)
        {
            ShowError((unsigned char *)"\pAddResource- TFGX", err);
            return(err);
        }
    }
                
    theHHandle = (HHandle)NewHandle(sizeof(HeaderRec));
    HLock((Handle)theHHandle);
    
    GetFontName(theDocument->theFont, (unsigned char *)&(*theHHandle)->theFont);
    (*theHHandle)->theSize     = theDocument->theSize;
    (*theHHandle)->theStyle    = theDocument->theStyle;
    (*theHHandle)->lastID      = 0;
    
    HUnlock((Handle)theHHandle);
    
    AddResource((Handle)theHHandle, kHeaderInfoType, 255, (unsigned char *)"\pHeader Info");
    
    err = ResError();
    if (err)
    {
        ShowError((unsigned char *)"\pAddResource- Header Info", err);
        return(err);
    }
    
    // Now put an AppName in for Finder in 7.0
    
    theAppName = (StringHandle)NewHandle(gAppRec.theName[0]);
    PLstrcpy( *theAppName, gAppRec.theName );
    
    AddResource((Handle)theAppName, 'STR ', -16396, (unsigned char *)"\pFinder App Info");
    
    err = ResError();
    
    if (err)
    {
        ShowError((unsigned char *)"\pAppName", err);
        return(err);
    }
    
        // Each document has a script associated with it
        //  this can be changed so store in resource.
    err = StoreDocumentScript( theDocument, resFile );
    
    CloseResFile( resFile );
    
    return err;
} // WriteFile
 
 
#pragma segment File
 
OSErr   ReadFile( DPtr theDocument, short  refNum, Str255 fn )
{
    long            theSize;
    short           resFile;
    OSErr           err;
    HHandle         aHandle;
    Handle          gHandle;
    Handle          hGXJobData;
    Boolean         gotQDPrintRec;
    
    gotQDPrintRec = false;
    
    err = GetEOF(refNum, &theSize);
    if (err)
        return(err);
    
    // We're only using TE, so check that there is not more than 32K worth of text
            
    if (theSize > kMaxTELength)
      return(1);
    
    gHandle = NewHandle(theSize);
    HLock(gHandle);
    err = FSRead(refNum, &theSize, *gHandle);
            
    if (err)
    {
        HUnlock(gHandle);
        return(err);
    }
                            
    resFile = HOpenResFile(theDocument->theFSSpec.vRefNum, 
                                                 theDocument->theFSSpec.parID,
                                                 fn,
                                                 fsWrPerm);
    if ( resFile == -1 )
        err = fnfErr;
                
    if ( err == noErr )
    {
        aHandle = NULL;
 
        if ( Count1Resources( kHeaderInfoType ) )
            aHandle = (HHandle)Get1Resource( kHeaderInfoType, 255 );
 
        //  New Format Info
 
        aHandle = nil;
        
        if ( Count1Resources( kStyleInfoType ) )
            aHandle = (HHandle)Get1Resource( kStyleInfoType, 255 );
            
        HLock(gHandle);
        TEStylInsert( *gHandle, GetHandleSize(gHandle),
                            (StScrpHandle)aHandle, theDocument->theText );
        HUnlock(gHandle);
        
        //  If there is a print record saved, ditch the old one
        //  created by new document and fill this one in
 
        if (Count1Resources('TFSP'))
        {
            if (theDocument->thePrintSetup)
                DisposHandle((Handle)theDocument->thePrintSetup);
            
            theDocument->thePrintSetup = (THPrint)Get1Resource('TFSP', 255);
            err = HandToHand((Handle *)&theDocument->thePrintSetup);
            
            gotQDPrintRec = true;
            if (! gGXIsPresent)
                PrValidate(theDocument->thePrintSetup);
        }
 
        if ( gGXIsPresent )
            if ( Count1Resources( kGXPrinterInfoType ) )
            {
                if (theDocument->documentJob)
                {
//                      GXDisposeJob(theDocument->documentJob);
//                      theDocument->documentJob = nil;
                }
 
                hGXJobData = Get1Resource( kGXPrinterInfoType, 255 );
 
                if (hGXJobData)
                {
                    GXUnflattenJobFromHdl(theDocument->documentJob, hGXJobData);
                    err = GXGetJobError(theDocument->documentJob);
                    ReleaseResource(hGXJobData);
                }
            }
            else
            {
                if (gotQDPrintRec)
                    GXConvertPrintRecord(theDocument->documentJob, theDocument->thePrintSetup);
            }
            
        err = LoadDocumentScript( theDocument, resFile );
 
        CloseResFile(resFile);
 
        err = ResError();
        if (err)
        {
            ShowError((unsigned char *)"\pReadFile - CloseResFile", err);
            return(err);
        }
    }
    else
        TESetText( *gHandle, GetHandleSize(gHandle), theDocument->theText );
        
    if (gHandle)
        DisposHandle(gHandle);
        
    if (err==fnfErr)
        err = noErr;
    
    return(err);
} // ReadFile
 
 
//  Name:           GetFileContents
//  Purpose:        Opens the document specified by theFSSpec and puts
//                   the contents into theDocument.
    
#pragma segment File
 
OSErr   GetFileContents( FSSpec theFSSpec, DPtr theDocument )
{
    OSErr   err;
    short   theRefNum;
 
    // this can be called from two places- on an OpenDoc AppleEvent
    // and by the user just selecting Open from the File Menu
    // assume that the CFS is correct when the routine is called
 
    err = FSpOpenDF( &theFSSpec, fsRdWrPerm, &theRefNum );
    if (err)
    {
        ShowError((unsigned char *)"\pFSpOpenDF", err);
        return(err);
    }
    else
    {
        err = ReadFile(theDocument, theRefNum, theFSSpec.name);
        if (err)
        {
            ShowError((unsigned char *)"\pReadFile", err);
            return(err);
        }
        err=FSClose(theRefNum);
        if (err)
        {
            ShowError((unsigned char *)"\pFSClose", err);
            return(err);
        }
        return(noErr);
    }
}
 
    
#pragma segment File
 
OSErr   SaveUsingTemp( DPtr theDocument )
{
    Str255      tempName;
    FSSpec      tempFSSpec;
    OSErr       err;
    
    // save the file to disk using a temporary file
    // this is the recommended way of doing things
    // first write out the file to disk using a temporary filename
    // if it is sucessfully written, exchange the temporary file with the last one saved
    // then delete the temporary file- so if anything goes wrong, the original version is still there
    // first generate the temporary filename
    
    GetTempFileName(theDocument, tempName);
 
    //create this file on disk
    
    tempFSSpec = theDocument->theFSSpec;
    PLstrcpy( tempFSSpec.name, tempName );
        
    err = DoCreate(tempFSSpec); 
    
    // now save the file as normal
    
    if (err == noErr)
        err = DoSave(theDocument, tempFSSpec);
    
    if (err == noErr)
        err = FSpExchangeFiles(&tempFSSpec, &theDocument->theFSSpec);
    
    // we've exchanged the files, now delete the temporary one
    
    if (err == noErr)
      err = FSpDelete(&tempFSSpec);
    
    return(err);
}
 
    
#pragma segment File
 
//  Fills in the document record with the user chosen destination
 
OSErr   GetFileNameToSaveAs(DPtr theDocument)
{           
    StandardFileReply   reply;
    OSErr               err;
    Str255              suggestName;
    
    GetWTitle(theDocument->theWindow, suggestName);
    
    StandardPutFile((unsigned char *)"\pSave Document As:", suggestName, &reply);
    
    if (reply.sfGood)
    {               
        err = FSpDelete(&reply.sfFile);
        
        if (!((err==noErr) || (err==fnfErr)))
            return(err);
        else
            err = noErr;
            
        theDocument->theFSSpec = reply.sfFile;
        PLstrcpy(theDocument->theFileName, reply.sfFile.name);
    }
    else
        err = userCanceledErr;
    
    return(err);
} // GetFileNameToSaveAs
 
    
#pragma segment File
 
OSErr   DoSave( DPtr theDocument, FSSpec theFSSpec )
{
    short      refNum;
    OSErr      fileErr;
    
    fileErr = FSpOpenDF(&theFSSpec, fsRdWrPerm, &refNum);
    
    if (fileErr == fnfErr)
    {
      fileErr = DoCreate(theFSSpec);
        
        if (fileErr)
            return(fileErr);
            
        fileErr = FSpOpenDF(&theFSSpec, fsRdWrPerm, &refNum);
    }
    
    if (fileErr == noErr)
    {
      fileErr = WriteFile(theDocument, refNum, theFSSpec);
        
        if (fileErr==noErr)
            theDocument->dirty = false;
            
        fileErr = FSClose(refNum);
    }
    else
        FileError((unsigned char *)"\perror opening file ", theFSSpec.name);
    
    return(fileErr);
}
 
    
#pragma segment File
 
OSErr   OpenOld( FSSpec aFSSpec )
{       
    DPtr            aDocument;
    AEDesc          aDesc = { typeNull, NULL },
                    aBoundsDesc = { typeNull, NULL };
    WindowPropToken aPropToken;
    Str255          aPStr;
    OSErr           anErr;
    
    aDocument = NewDocument( true, (WindowPtr)-1L );
    
    SetWTitle( aDocument->theWindow, aFSSpec.name );
    
    SetPort( aDocument->theWindow );
    
    aDocument->theFSSpec   = aFSSpec;
    
    PLstrcpy( aDocument->theFileName,aFSSpec.name );
    
    aDocument->dirty       = false;
    aDocument->everSaved   = true;
    
    anErr = GetFileContents( aFSSpec, aDocument );
    
    if ( noErr == anErr )
    {           // Try and get the saved script property
        if ( kOSANullScript != aDocument->theScriptID )
        {
            PLstrcpy( aPStr, "\pwindowbounds" );
            anErr = AECreateDesc( typeChar, (Ptr)&aPStr[1], aPStr[0], &aDesc );
            if ( noErr != anErr ) goto done;
            
            anErr = GetScriptProperty( aDocument->theScriptID, &aDesc, &aBoundsDesc );
            if ( noErr == anErr )
            {       // Set up a token for the document
                aPropToken.tokenWindowToken.tokenWindow = aDocument->theWindow;
                aPropToken.tokenProperty = pBounds;
            
                anErr = SetDocumentTokenProperty( &aPropToken, &aBoundsDesc );
            }
            else
                anErr = noErr;  // Script does not have this property
        }
    
        ResizeWindow( aDocument );
        SelectWindow( aDocument->theWindow );
        ShowMSWindow( aDocument->theWindow );
    }
    else
        FileError( (unsigned char *)"\pError Opening ", aFSSpec.name );
 
done:
    (void)AEDisposeDesc( &aDesc );
    (void)AEDisposeDesc( &aBoundsDesc );
    
    return anErr;
} // OpenOld
 
/*   
#pragma segment File
 
OSErr   OpenUsingAlias( AliasHandle theAliasH )
{
    OSErr    err;
    FSSpec   aFSSpec;
    Boolean  dummy;
    
    err = ResolveAlias(nil, theAliasH, &aFSSpec, &dummy);
    
    if (err == noErr)
        err = OpenOld(aFSSpec);
                
    return(err);
}
*/