QDrawHandler.c

//////////
//
//  File:       QDrawHandler.c
//
//  Contains:   Code for creating a derived media handler component for QuickDraw pictures.
//
//  Written by: Tim Monroe
//              Based on MyMediaComponent by John Wang (see develop issue 14), with some
//              assistance from SampleMediaHandler by deeje cooley.
//
//  Copyright:  © 1993-1999 by Apple Computer, Inc., all rights reserved.
//
//  Change History (most recent first):
//
//     <7>      02/25/99    rtm     added some comments
//     <6>      01/28/99    rtm     fixed bug that caused garbage to be drawn when Copy menu item selected
//                                  or a drag of movie clipping attempted
//     <5>      01/25/99    rtm     got Windows DLL working
//     <4>      01/21/99    rtm     got both MacOS PPC and 68K versions working
//     <3>      01/15/99    rtm     coordinated with SampleMediaHandler by deeje cooley
//     <2>      01/05/99    rtm     revised coding style
//     <1>      02/25/93    jw      first file; based on MyComponent shell
//
//  This project builds a derived (or custom) media handler. See the chapter "Derived Media Handler
//  Components" in the book Inside Macintosh: QuickTime Components for information about writing
//  derived media handler components. See also John Wang's article on derived media handlers in develop,
//  issue 14, for more information about the QuickDraw media handler. The main difference between his
//  code and the current sample code is that I've made the dispatching routines both PPC- and Windows-savvy,
//  using the ComponentDispatchHelper code. Also fixed a few bugs and added some more comments.
//
//////////
 
 
//////////
//
// header files
//
//////////
 
#include "QDrawHandler.h"
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Component dispatch helper defines
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
#if TARGET_CPU_68K
    #define COMPONENT_C_DISPATCHER
    #define COMPONENT_DISPATCH_MAIN
#endif
 
#define MEDIA_BASENAME()                QDMH_
#define MEDIA_GLOBALS()                 QDMH_GlobalsHdl storage
 
#define CALLCOMPONENT_BASENAME()        MEDIA_BASENAME()
#define CALLCOMPONENT_GLOBALS()         MEDIA_GLOBALS()
 
#define COMPONENT_UPP_PREFIX()          uppMedia
#define COMPONENT_SELECT_PREFIX()       kMedia
#define COMPONENT_DISPATCH_FILE         "QDrawHandlerDispatch.h"
 
#define GET_DELEGATE_COMPONENT()        ((**storage).fDelegate)
 
#include "Components.k.h"
#include "MediaHandlers.k.h"
#include "ComponentDispatchHelper.c"
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Required component calls
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// QDMH_Open
// Open the derived media handler component.
//
//////////
 
PASCAL_RTN ComponentResult QDMH_Open (QDMH_GlobalsHdl storage, ComponentInstance theSelf)
{
#pragma unused(storage)
 
    QDMH_GlobalsHdl             myStorage = NULL;
    ComponentInstance           myComponent = NULL;
    
    // allocate the private global storage used by this component instance
    myStorage = (QDMH_GlobalsHdl)NewHandleClear(sizeof(QDMH_Globals));  
    if (myStorage == NULL)
        return(MemError());
    
    SetComponentInstanceStorage(theSelf, (Handle)myStorage);
    
    // open the base media handler component and target it
    myComponent = OpenDefaultComponent(MediaHandlerType, BaseMediaType);    
    if (myComponent == NULL)
        return(componentNotCaptured);
    
    ComponentSetTarget(myComponent, theSelf);
 
    (**myStorage).fDelegate = myComponent;
    (**myStorage).fSelf = theSelf;
    (**myStorage).fParent = theSelf;
 
    return(noErr);
}
 
 
//////////
//
// QDMH_Close
// Close the derived media handler component.
//
//////////
 
PASCAL_RTN ComponentResult QDMH_Close (QDMH_GlobalsHdl storage, ComponentInstance theSelf)
{
#pragma unused(theSelf)
 
    if (storage != NULL) {
 
        // close the base media handler component instance
        if ((**storage).fDelegate != NULL)
            CloseComponent((**storage).fDelegate);
        
        // dispose of the private global storage created by the _Open routine
        DisposeHandle((Handle)storage);
    }
    
    return(noErr);
}
 
 
//////////
//
// QDMH_Version
// Return the version of the derived media handler component.
//
//////////
 
PASCAL_RTN ComponentResult QDMH_Version (QDMH_GlobalsHdl storage)
{
#pragma unused(storage)
 
    return(kQDMH_Version);
}
 
 
//////////
//
// QDMH_Register
// Register.
//
// This routine is called once (usually at boot time) when the Component Manager first
// registers this component. Note that the cmpWantsRegisterMessage bit must be set in
// the component flags of the component in order for this routine to be called.
//
//////////
 
PASCAL_RTN ComponentResult QDMH_Register (QDMH_GlobalsHdl storage)
{
    if (storage != NULL)
        return(noErr);  // globals properly set up: the base media handler was targeted OK
    else
        return(-1);     // globals not properly set up: don't register
}
 
 
//////////
//
// QDMH_Target
// Target.
//
//////////
 
PASCAL_RTN ComponentResult QDMH_Target (QDMH_GlobalsHdl storage, ComponentInstance theTarget)
{
    // remember who is at the top of our calling chain
    (**storage).fParent = theTarget;
    
    // inform the base media handler of the change
    ComponentSetTarget((**storage).fDelegate, theTarget);
    
    return(noErr);
}
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Derived media handler functions
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// QDMH_Initialize
// Initialize our media handler.
//
//////////
 
PASCAL_RTN ComponentResult QDMH_Initialize (QDMH_GlobalsHdl storage, GetMovieCompleteParams *theGMC)
{
    ComponentResult         myErr = noErr;
    long                    myFlags;
 
    if ((storage == NULL) || (theGMC == NULL))
        return(paramErr);
        
    // set general characteristics
    (**storage).fMovie = theGMC->theMovie;
    (**storage).fTrack = theGMC->theTrack;
    (**storage).fMedia = theGMC->theMedia;
    (**storage).fCurMediaRate = theGMC->effectiveRate;
    MacSetRect(&((**storage).fGraphicsBox), 0, 0, (short)(theGMC->width >> 16), (short)(theGMC->height >> 16));
    (**storage).fTrackMatrix = theGMC->trackMovieMatrix;
    (**storage).fPort = theGMC->moviePort;
    (**storage).fDevice = theGMC->movieGD;
    (**storage).fSampleDescIndex = -1;
 
    // set media globals
    (**storage).fWhatChanged = kQDMHAllChanged;
    (**storage).fEnabled = false;
    (**storage).fNewMediaRate = theGMC->effectiveRate;
    (**storage).fPrevMediaTime = -1;
    
    // inform the base media handler of our capabilities:
    // * handlerHasSpatial indicates that we draw
    // * handlerNeedsBuffer indicates that we want QuickTime to maintain a drawing buffer for us
    myFlags = handlerHasSpatial | handlerNeedsBuffer;
    myErr = MediaSetHandlerCapabilities((**storage).fDelegate, myFlags, -1);
 
    return(myErr);
}
 
 
//////////
//
// QDMH_Idle
// Draw our media sample.
//
// Here is where we get time to process the current media sample data. In our case, we want to extract
// the QuickDraw picture data from the media and draw it into the current graphics port. If the sample
// data consisted of a complete QuickDraw picture, we could get that picture by calling GetMediaSample
// and then draw the picture by calling MacSetRect, TransformRect, and DrawPicture. (This is the route
// taken in the derived media handler described in Inside Macintosh: QuickTime Components, pp. 10-9 to
// 10-14.) Our media handler, however, supports key frames (also called sync frames), which complicate
// the work we need to do here. A key frame is a sample that does not rely on previous samples for any 
// of its information. A non-key frame (or difference frame) contains just data that has changed from
// the previous frame. 
//
//////////
 
PASCAL_RTN ComponentResult QDMH_Idle (QDMH_GlobalsHdl storage, TimeValue theMediaTime, long theFlagsIn, long *theFlagsOut, const TimeRecord *theMovieTime)
{
#pragma unused(theMovieTime)
 
    Media               myMedia = (**storage).fMedia;
    TimeValue           myPrevMediaTime = (**storage).fPrevMediaTime;
    long                myWhatChanged;          // flags indicating changes in media environment
    GDHandle            mySavedGD;
    CGrafPtr            mySavedPort;
    Rect                myDrawRect;
    Boolean             myRedraw;               // do we need to draw anything?
    Boolean             myIsDone;
    TimeValue           myTime;
    long                mySize;
    PicHandle           mySyncPic = NULL;
    PicHandle           myCurrPic = NULL;
    TimeValue           mySyncSampleTime;
    TimeValue           myCurrSampleTime;
    long                myCurrSampleIndex;
    OSErr               myErr = noErr;
 
    //////////
    //
    // inspect theFlagsIn to determine what the Movie Toolbox wants us to do
    //
    //////////
 
    // don't draw anything if mPreflightDraw flag is set
    if (theFlagsIn & mPreflightDraw)
        return(myErr);
 
    //////////
    //
    // initialize
    //
    //////////
 
    // allocate space for two pictures (the current sample's picture and the previous sync frame);
    // GetMediaSample resizes these handles as necessary when returning the media data
    mySyncPic = (PicHandle)NewHandle(sizeof(Picture));
    myCurrPic = (PicHandle)NewHandle(sizeof(Picture));
    if ((mySyncPic == NULL) || (myCurrPic == NULL)) {
        myErr = memFullErr;
        goto bail;
    }
    
    GetGWorld(&mySavedPort, &mySavedGD);
    SetGWorld((GWorldPtr)(**storage).fPort, (**storage).fDevice);
    
    //////////
    //
    // get information about the current sample
    //
    //////////
 
    myErr = GetMediaSample(myMedia, (Handle)myCurrPic, 0, NULL, theMediaTime, &myCurrSampleTime, NULL, NULL, &myCurrSampleIndex, 0, NULL, NULL);
    if (myErr != noErr)
        goto bail;
    
    // check to see whether the sample description index has changed since the last sample
    if (myCurrSampleIndex != (**storage).fSampleDescIndex) {
        (**storage).fSampleDescIndex = myCurrSampleIndex;
        (**storage).fWhatChanged |= kQDMHSampleDescChanged;
    }
 
    //////////
    //
    // determine what, if anything, in the media has changed
    // (we don't need to draw the current media sample if nothing has changed)
    //
    //////////
 
    myRedraw = false;
    myWhatChanged = (**storage).fWhatChanged;
    
    if (myWhatChanged != kQDMHNothingChanged) {
 
        if (myWhatChanged & kQDMHSetActive)     // if media was just enabled, then redraw; else don't change
            myRedraw = (**storage).fEnabled;
 
        if (myWhatChanged & kQDMHSetRate) {
            // if we are now playing in the opposite direction, then redraw
            if ((((**storage).fCurMediaRate < 0) && ((**storage).fNewMediaRate > 0))
             || (((**storage).fCurMediaRate > 0) && ((**storage).fNewMediaRate < 0)))
                myRedraw = true;
            (**storage).fCurMediaRate = (**storage).fNewMediaRate;
        }
        
        if (myWhatChanged & kQDMHTrackEdited)
            myRedraw = true;                    // if track edited, then redraw
        
        if (myWhatChanged & kQDMHSetGWorld)
            myRedraw = true;                    // if new GWorld, then redraw
        
        if (myWhatChanged & kQDMHSetDimensions)
            myRedraw = true;                    // if new dimensions, then redraw
        
        if (myWhatChanged & kQDMHSetMatrix)
            myRedraw = true;                    // if new matrix, then redraw
        
        if (myWhatChanged & kQDMHSampleDescChanged) {
            QDrawDescriptionHandle      myQDDesc;
            
            // the sample description has changed; make sure it's a version we can handle
            myQDDesc = (QDrawDescriptionHandle)NewHandleClear(sizeof(QDrawDescription));
            if (myQDDesc != NULL) {
                GetMediaSampleDescription(myMedia, (**storage).fSampleDescIndex, (SampleDescriptionHandle)myQDDesc);
#if HANDLER_SWAPS_SAMPLE_DESC
                if ((**myQDDesc).version > kQDMH_Version)
#else
                if ((**myQDDesc).version > EndianU32_NtoB(kQDMH_Version))
#endif
                    (**storage).fEnabled = false;
                DisposeHandle((Handle)myQDDesc);
            } else {
                (**storage).fEnabled = false; 
            }
        }
        
        // clear out flags indicating changes in media environment
        (**storage).fWhatChanged = kQDMHNothingChanged;
    }
    
    // if we are playing the movie backwards, we must always redraw from last sync frame
    if ((**storage).fCurMediaRate < 0)
        myRedraw = true;
    
    //////////
    //
    // we do not need to draw anything if myRedraw is false and if myPrevMediaTime == myCurrSampleTime
    // (since we've already drawn that sample and nothing has occurred to cause us to have to redraw it)
    //
    // otherwise, we do need to draw; what we draw depends on the value of myRedraw:
    //  (a) if myRedraw = true, redraw everything since the previous key frame
    //  (b) if myRedraw = false, redraw everything since the previous key frame or the previous media frame,
    //      whichever is closer to current frame
    //
    //////////
 
    if (myRedraw || (myPrevMediaTime != myCurrSampleTime)) {
    
        // find the previous key frame; note that we use the nextTimeEdgeOK flag, since the current frame
        // might be a key frame; note also that if the media doesn't contain any key frames, the sample
        // time returned in mySyncSampleTime is the same as myCurrSampleTime (that is, every frame is a key
        // frame)
        
        GetMediaNextInterestingTime(myMedia, nextTimeSyncSample + nextTimeEdgeOK, myCurrSampleTime, -1, &mySyncSampleTime, NULL);
        
        // if myRedraw = false, mySyncSampleTime <= myPrevMediaTime, and myCurrSampleTime is ahead of
        // myPrevMediaTime, then search to set the place to draw as the sample after myPrevMediaTime
        if (!myRedraw && (mySyncSampleTime <= myPrevMediaTime) && (myPrevMediaTime < myCurrSampleTime)) {
            myTime = mySyncSampleTime;
            
            while ((myTime >= 0) && (myTime < myPrevMediaTime))
                GetMediaNextInterestingTime(myMedia, nextTimeMediaSample, myTime, 1, &myTime, NULL);
 
            if ((myTime == myPrevMediaTime) && (myTime != -1)) {
                GetMediaNextInterestingTime(myMedia, nextTimeMediaSample, myTime, 1, &myTime, NULL);
                if (myTime != -1)
                    mySyncSampleTime = myTime;  
            }
        }
        
    //////////
    //
    // draw the picture, beginning at mySyncSampleTime; but don't draw if the media is disabled
    //
    //////////
 
        myIsDone = false;
        myTime = mySyncSampleTime;
        while (!myIsDone && (**storage).fEnabled) {
        
            if (myTime == myCurrSampleTime)
                myIsDone = true;
                
            myErr = GetMediaSample(myMedia, (Handle)mySyncPic, 0, &mySize, myTime, NULL, NULL, NULL, NULL, 0, NULL, NULL);
            if (myErr != noErr)
                goto bail;
                
            myDrawRect = (**storage).fGraphicsBox;
            TransformRect(&(**storage).fTrackMatrix, &myDrawRect, NULL);
            DrawPicture(mySyncPic, &myDrawRect);
            if (!myIsDone) {
                GetMediaNextInterestingTime(myMedia, nextTimeMediaSample, myTime, 1, &myTime, NULL);
                if (myTime < 0)
                    myIsDone = true;
            }
        }
        
        // say we drew somthing
        *theFlagsOut |= mDidDraw;
    }
    
    // update the previous media time
    (**storage).fPrevMediaTime = myCurrSampleTime;
        
    //////////
    //
    // clean up and return
    //
    //////////
    
bail:
    SetGWorld((GWorldPtr)mySavedPort, mySavedGD);
    
    if (mySyncPic != NULL)
        DisposeHandle((Handle)mySyncPic);
    if (myCurrPic != NULL)
        DisposeHandle((Handle)myCurrPic);
    
    return(myErr);
}
 
 
//////////
//
// QDMH_SetActive
// Set the enabled state of the media.
//
//////////
 
PASCAL_RTN ComponentResult QDMH_SetActive (QDMH_GlobalsHdl storage, Boolean theEnableMedia)
{
    if ((**storage).fEnabled != theEnableMedia) {
        (**storage).fEnabled = theEnableMedia;
        (**storage).fWhatChanged |= kQDMHSetActive;
    }
    
    return(noErr);
}
 
 
//////////
//
// QDMH_SetRate
// Set the media rate.
//
//////////
 
PASCAL_RTN ComponentResult QDMH_SetRate (QDMH_GlobalsHdl storage, Fixed theRate)
{
    // save the new rate in fNewMediaRate so that we can compare with previous rate;
    // if the new rate is in the same direction, we won't want to redraw again from the previous key frame
    if ((**storage).fNewMediaRate != theRate) {
        (**storage).fNewMediaRate = theRate;
        (**storage).fWhatChanged |= kQDMHSetRate;
    }
    
    return(noErr);
}
 
 
//////////
//
// QDMH_TrackEdited
// Set the track-edited state.
//
//////////
 
PASCAL_RTN ComponentResult QDMH_TrackEdited (QDMH_GlobalsHdl storage)
{
    (**storage).fWhatChanged |= kQDMHTrackEdited;
    
    return(noErr);
}
 
 
//////////
//
// QDMH_SetGWorld
// Set the media graphics port.
//
//////////
 
PASCAL_RTN ComponentResult QDMH_SetGWorld (QDMH_GlobalsHdl storage, CGrafPtr thePort, GDHandle theGD)
{
    (**storage).fPort = thePort;
    (**storage).fDevice = theGD;
    (**storage).fWhatChanged |= kQDMHSetGWorld;
    
    return(noErr);
}
 
 
//////////
//
// QDMH_SetDimensions
// Set the media dimensions.
//
//////////
 
PASCAL_RTN ComponentResult QDMH_SetDimensions (QDMH_GlobalsHdl storage, Fixed theWidth, Fixed theHeight)
{
    MacSetRect(&((**storage).fGraphicsBox), 0, 0, (short)(theWidth >> 16), (short)(theHeight >> 16));
    (**storage).fWhatChanged |= kQDMHSetDimensions;
 
    return(noErr);
}
 
 
//////////
//
// QDMH_SetMatrix
// Set the track or movie matrix.
//
//////////
 
PASCAL_RTN ComponentResult QDMH_SetMatrix (QDMH_GlobalsHdl storage, MatrixRecord *theTrackMovieMatrix)
{   
    // don't cause unnecessary updates if the matrix doesn't really change
    // (this can happen if the resize button is clicked on, but not moved)
    if (!EqualMatrix(&((**storage).fTrackMatrix), theTrackMovieMatrix)) {
        (**storage).fTrackMatrix = *theTrackMovieMatrix;
        (**storage).fWhatChanged |= kQDMHSetMatrix;
    }
 
    return(noErr);
}
 
 
//////////
//
// QDMH_SampleDescriptionChanged
// Handle changes to the sample description tables.
//
//////////
 
PASCAL_RTN ComponentResult QDMH_SampleDescriptionChanged (QDMH_GlobalsHdl storage, long theIndex)
{
    // the sample description tables store info such as data version
    (**storage).fSampleDescIndex = theIndex;
    (**storage).fWhatChanged |= kQDMHSampleDescChanged;
 
    return(noErr);
}
 
 
#if HANDLER_SWAPS_SAMPLE_DESC
//////////
//
// QDMH_SampleDescriptionB2N
// Convert our sample description from big- to native-endian format.
//
//////////
 
PASCAL_RTN ComponentResult QDMH_SampleDescriptionB2N (QDMH_GlobalsHdl storage, SampleDescriptionHandle theSampleDesc)
{
#pragma unused(storage)
 
    QDrawDescriptionHandle      myQDDesc = (QDrawDescriptionHandle)theSampleDesc;
    
    if ((myQDDesc == NULL) || (*myQDDesc == NULL))
        return(paramErr);
 
    // flip any fields in our sample description that are not in the SampleDescription structure
    (**myQDDesc).version = EndianU32_BtoN((**myQDDesc).version);
    
    return(noErr);
}
 
 
//////////
//
// QDMH_SampleDescriptionN2B
// Convert our sample description from native- to big-endian format.
//
//////////
 
PASCAL_RTN ComponentResult QDMH_SampleDescriptionN2B (QDMH_GlobalsHdl storage, SampleDescriptionHandle theSampleDesc)
{
#pragma unused(storage)
 
    QDrawDescriptionHandle      myQDDesc = (QDrawDescriptionHandle)theSampleDesc;
 
    if ((myQDDesc == NULL) || (*myQDDesc == NULL))
        return(paramErr);
 
    // flip any fields in our sample description that are not in the SampleDescription structure
    (**myQDDesc).version = EndianU32_NtoB((**myQDDesc).version);
    
    return(noErr);
}
#endif  // HANDLER_SWAPS_SAMPLE_DESC