ThreadedProgress.c

/*
    File:       ThreadedProgress.c
 
    Contains:   Progress bar implementation using the Thread Manager
 
    Written by: Chris White 
 
    Copyright:  Copyright © 1996-1999 by Apple Computer, Inc., All Rights Reserved.
 
                You may incorporate this Apple sample source code into your program(s) without
                restriction. This Apple sample source code has been provided "AS IS" and the
                responsibility for its operation is yours. You are not permitted to redistribute
                this Apple sample source code as "Apple sample source code" after having made
                changes. If you're going to re-distribute the source, we require that you make
                it clear in the source that the code was descended from Apple sample source
                code, but that you've made changes.
 
    Change History (most recent first):
                8/10/1999   Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
                
 
*/
 
 
#pragma segment Core
 
 
// System Includes
 
 
#include <Threads.h>
 
 
 
// Application includes
 
#ifndef __BAREBONES__
    #include "BareBones.h"
#endif
 
#ifndef __PROTOTYPES__
    #include "Prototypes.h"
#endif
 
 
// static prototypes
 
static pascal void* ProgressDialogThread ( tThreadedOperationPtr theInfo );
static pascal void  ThreadTermination ( ThreadID threadTerminated, void* terminationProcParam );
 
 
 
 
 
OSErr ThreadedProgressOperation ( tThreadedOperation theOperation, void* refCon,
                    StringPtr theText, SInt32* operationErr, Boolean bBarberPole )
{
    OSErr                   theErr = noErr;
    ThreadID                operationsThreadID = 0;
    ThreadID                progressDlgThreadID = 0;
    tThreadedOperationPtr   theInfo = nil;
    
    
    
    theInfo = (tThreadedOperationPtr) NewPtrClear ( sizeof ( tThreadedOperationRec ) );
    theErr = MemError ( );
    if ( theErr )   goto CleanupAndbail;
    
    BlockMoveData ( theText, theInfo->theText, theText[0] + 1 );
    theInfo->bBarberPole = bBarberPole;
    theInfo->refCon = refCon;
    
    
    // Create the Progress Dialog Thread
    theErr = NewThread ( kCooperativeThread, (ThreadEntryProcPtr) ProgressDialogThread,
                            (void*) theInfo, kDefaultStackSpace, kNoCreationOptions,
                            (void*) operationErr, &progressDlgThreadID );
    if ( theErr )   goto CleanupAndbail;
    
    // Install termination routine to decrement the usage count
    // and maybe dispose of the tThreadedOperationRec.
    theInfo->usageCount++;
    theErr = SetThreadTerminator ( progressDlgThreadID, ThreadTermination, (void*) theInfo );
 
    // Create the Operation Thread 
    theErr = NewThread ( kCooperativeThread, (pascal void* (*) (void*)) theOperation, (void*) theInfo,
                            kDefaultStackSpace, kNoCreationOptions, (void*) operationErr,
                            &operationsThreadID );
    if ( theErr )   goto CleanupAndbail;
    
    theInfo->usageCount++;
    theErr = SetThreadTerminator ( operationsThreadID, ThreadTermination, (void*) theInfo );
    
    // Get the dialog drawn _before_ the actual operation is started
    theErr = YieldToThread ( progressDlgThreadID );
    if ( theErr )   goto CleanupAndbail;
 
    return noErr;
    
CleanupAndbail:
    
    if ( theInfo )
        DisposePtr ( (Ptr) theInfo );
        
    // Dispose of the threads, and pass the error codes back
    if ( operationsThreadID )
        DisposeThread ( operationsThreadID, (void*) theErr, false );
    
    if ( operationsThreadID )
        DisposeThread ( progressDlgThreadID, (void*) theErr, false );
    
    return theErr;
}
 
 
 
static pascal void* ProgressDialogThread ( tThreadedOperationPtr theInfo )
{
    SInt16      theType;
    GrafPtr     savePort;
    DialogRef   theDialog = nil;
    Handle      theHan;
    Rect        theRect;
    
    
    theDialog = GetNewDialog ( kProgressDialogID, nil, (WindowPtr) -1 );
    
    SetWRefCon ( theDialog, (long) theInfo );
    
    GetDialogItem ( theDialog, kStaticTextItemID, &theType, &theHan, &theRect );
    SetDialogItemText ( theHan, theInfo->theText );
    
    GetDialogItem ( theDialog, kUserItemID, &theType, &theHan, &theRect );
    if ( !theInfo->bBarberPole )
    {
        SetDialogItem ( theDialog, kUserItemID, theType, (Handle) gOutlineUserItemUPP, &theRect );
        CallUserItemProc ( gOutlineUserItemUPP, theDialog, kUserItemID );
    }
    
    ShowWindow ( theDialog );
    DrawDialog ( theDialog );
    
    
    while ( theInfo->bCancelled == false )
    {
        YieldToAnyThread ( );
        
        // Although the user interface doesn't allow you to execute
        // more than one threaded progress bar, we'll be careful to
        // support it here. It's just a case of making sure the port
        // is setup and restored across calls to YieldToAnyThread.
        
        GetPort ( &savePort );
        SetPort ( theDialog );
        
        if ( theInfo->bBarberPole )
        {
            PicHandle       thePic;
            static SInt16   theID = 1000;       // Gotcha: Non-reentrant, see documentation
            
            
            // Gotcha: Some Resource Manager calls can only be made
            // from the main thread on a Mac Plus. See documentation
            thePic = GetPicture ( theID++ );
            DrawPicture ( thePic, &theRect );
            if ( theID > 1003 )
                theID = 1000;
            
            if ( theInfo->doneAmount == kBarberPoleFinished )
                break;
        }
        else
        {
            if ( theInfo->doneAmount != theInfo->drawnAmount )
            {
                int     theLength;
                float   floatDone, floatMax, thePercent;
                
                // Temporarily adjust the user item rect to draw the bar
                GetDialogItem ( theDialog, kUserItemID, &theType, &theHan, &theRect );
                theLength = theRect.right - theRect.left;
                floatDone = theInfo->doneAmount;
                floatMax = theInfo->maxAmount;
                thePercent = (floatDone / floatMax) * 100;
                theRect.right = theRect.left + ((thePercent / 100) * theLength);
                
                theRect.top--; theRect.bottom++;
                FillRect ( &theRect, &qd.black );
            }
            
            if ( theInfo->doneAmount == theInfo->maxAmount )
                break;
        }
        
        SetPort ( savePort );
    }
    
    // A problem occurs if the thread is terminated before the dialog is
    // disposed. Fortunatly, with the current implementation, it can't.
    // However, if this can occur with your implementation, you'll want
    // to dispose of the dialog and (optionally?) restore the port in the
    // termination routine.
    DisposeDialog ( theDialog );
    
    return noErr;
}
 
 
 
//
// We don't want to dispose of the record when another thread could still
// be accessing it. Although they'll both finish about the same time, we
// don't want to rely on the implementation of the thread scheduler. This
// approach ensures both threads have finished with the record.
//
static pascal void ThreadTermination ( ThreadID threadTerminated, void* terminationProcParam )
{
    #pragma unused(threadTerminated)
    OSErr   theErr;
    
    
    #if DEBUGGING
    if ( terminationProcParam == nil )
        DebugStr ( "\p ThreadTermination: terminationProcParam is nil" );
    #endif
    
    
    ((tThreadedOperationPtr) terminationProcParam)->usageCount--;
    if ( ((tThreadedOperationPtr) terminationProcParam)->usageCount == 0 )
    {
        DisposePtr ( (Ptr) terminationProcParam );
        
        #if DEBUGGING
        theErr = MemError ( );
        if ( theErr )   DebugStrNum ( "\p ThreadTermination: DisposePtr", theErr );
        #endif
    }
    
    return;
}
 
 
 
//
// This routine is one of the operations carried out
// which the progress bar is representing.
//
pascal SInt32 ThreadedStandardDemoOperation ( tThreadedOperationPtr theInfo )
{
    OSErr       theErr = noErr;
    int         i;
    const int   max = 100;
    
    
    for ( i = 1; i <= max && theInfo->bCancelled == false; i++ )
    {
        UInt32  theDelay = 10L;
        Delay ( theDelay, &theDelay );
        
        theInfo->doneAmount = i;
        theInfo->maxAmount = max;
        
        YieldToAnyThread ( );
    }
    
    return (SInt32) theErr;
}
 
 
 
//
// This routine is one of the operations carried out
// which the progress bar is representing.
//
pascal SInt32 ThreadedBarberPoleDemoOperation ( tThreadedOperationPtr theInfo )
{
    // A5 is not garanteed to be valid
    
    OSErr       theErr = noErr;
    int         i;
    const int   max = 100;
    
    
    for ( i = 1; i <= max && theInfo->bCancelled == false; i++ )
    {
        UInt32  theDelay = 10L;
        Delay ( theDelay, &theDelay );
        
        YieldToAnyThread ( );
    }
    
    theInfo->doneAmount = kBarberPoleFinished;
    
    return (SInt32) theErr;
}