CApp.cp

/*
    Make QTVR Panorama 1.0b1
    
    A simple PowerPlant application for processing PICT files into QuickTime VRª movies.
    
    Created 29 Jan 1996 by EGH
    
    Copyright © 1996, Apple Computer, Inc. All rights reserved.
*/
 
#include <StandardFile.h>
 
#include <String_Utils.h>
#include <LCaption.h>
#include <UDesktop.h>
#include <UDrawingState.h>
#include <LEditField.h>
#include <LGrowZone.h>
#include <UMemoryMgr.h>
#include <LPlaceHolder.h>
#include <URegistrar.h>
#include <LScroller.h>
#include <LStdControl.h>
#include <LTabGroup.h>
#include <LTextEdit.h>
#include <LUndoer.h>
#include <LWindow.h>
#include <PP_Resources.h>
 
#include "CApp.h"
 
#include "CBeachBall.h"
#include "CMovieMaker.h"
#include "CPict2VRWindow.h"
#include "CPrefsWindow.h"
#include "CUtils.h"
 
CPreferences *gAppPrefs = nil;
 
CApp *gApp = nil;
 
void main(void)
{
    SetDebugThrow_(debugAction_Alert);
    SetDebugSignal_(debugAction_Alert);
 
    InitializeHeap(8);
    UQDGlobals::InitializeToolbox(&qd);
    
        // check for the existence of QuickTime
    Int32 result;
    OSErr err = ::Gestalt(gestaltQuickTime, &result);
    if (err != noErr)
    {
        ::StopAlert(ALRT_NoQT, nil);
        ExitToShell();
    }
    
    new LGrowZone(20000);
    
        // fire up QuickTime and load the components necessary for
        // creating pano media
    ::EnterMovies();
    ::RegisterComponentResourceFile(CurResFile(), 0);
    
    CApp theApp;
    gApp = &theApp;
    theApp.Run();
    
    ::ExitMovies();
}
 
 
CApp::CApp()
{
    mDropMode = false;
    
        // Register classes for objects created from 'PPob' resources
    URegistrar::RegisterClass(CPict2VRWindow::class_ID, CPict2VRWindow::CreatePict2VRWindowWindowStream);
    URegistrar::RegisterClass(CPrefsWindow::class_ID,   CPrefsWindow::CreatePrefsWindowWindowStream);
 
    URegistrar::RegisterClass(LCaption::class_ID,       LCaption::CreateCaptionStream);
    URegistrar::RegisterClass(LDialogBox::class_ID,     LDialogBox::CreateDialogBoxStream);
    URegistrar::RegisterClass(LEditField::class_ID,     LEditField::CreateEditFieldStream);
    URegistrar::RegisterClass(LPicture::class_ID,       LPicture::CreatePictureStream);
    URegistrar::RegisterClass(LPlaceHolder::class_ID,   LPlaceHolder::CreatePlaceHolderStream);
    URegistrar::RegisterClass(LStdButton::class_ID,     LStdButton::CreateStdButtonStream);
    URegistrar::RegisterClass(LStdCheckBox::class_ID,   LStdCheckBox::CreateStdCheckBoxStream);
    URegistrar::RegisterClass(LStdControl::class_ID,    LStdControl::CreateStdControlStream);
    URegistrar::RegisterClass(LStdPopupMenu::class_ID,  LStdPopupMenu::CreateStdPopupMenuStream);
    URegistrar::RegisterClass(LStdRadioButton::class_ID,    LStdRadioButton::CreateStdRadioButtonStream);
    URegistrar::RegisterClass(LTabGroup::class_ID,      LTabGroup::CreateTabGroupStream);
    URegistrar::RegisterClass(LView::class_ID,          LView::CreateViewStream);
    URegistrar::RegisterClass(LWindow::class_ID,        LWindow::CreateWindowStream);
    
        // create application preferences
    Str63 appName;
    ::GetIndString(appName, STRx_Standards, str_ProgramName);
    gAppPrefs = new CPreferences('p2vr', Creator_, appName);
    
        // look for preferences and use them
    mPrefsHdl = (P2VRPrefsHdl)gAppPrefs->GetPreferenceResource(ResType_Preferences, 128);
    if (mPrefsHdl == nil)
    {
            // no prefs, so create default ones
        mPrefsHdl = (P2VRPrefsHdl)::NewHandle(sizeof (P2VRPrefsRec));
        if (mPrefsHdl != nil)
        {
            (*mPrefsHdl)->dropMode = mDropMode;
            
            (*mPrefsHdl)->width = 400;
            (*mPrefsHdl)->height = 300;
            (*mPrefsHdl)->codec = 'cvid';
            (*mPrefsHdl)->pan = 0;
            (*mPrefsHdl)->tilt = 0;
            (*mPrefsHdl)->zoom = 0;
            (*mPrefsHdl)->spatialQuality = codecHighQuality;
            (*mPrefsHdl)->depth = 32;
            CopyPStr("\p.tile", (*mPrefsHdl)->tileSuffix);
            CopyPStr("\p.snm", (*mPrefsHdl)->movieSuffix);
            
            (*mPrefsHdl)->replaceFiles = false;
        }
    }
    else
    {
        mDropMode = (*mPrefsHdl)->dropMode;
    }
    
        // initialize spinning cursor
    CBeachBall::InitBeachBall();
    
    if (!mDropMode)
    {
        try
        {
            LWindow *splash = LWindow::CreateWindow(WIND_Splash, this);
            
            if (splash != nil)
            {
                splash->Show();
                splash->UpdatePort();
                UInt32 splashTime = ::TickCount() + 120; // this should be up for at least 180 ticks
                CBeachBall::StartSpinningTask(5);
                while (::TickCount() < splashTime) ;
                CBeachBall::StopSpinningTask();
                
                delete splash;
            }
        }
        catch (...)
        {
            // fall out
        }
    }
}
 
 
CApp::~CApp()
{
        // save the preferences
    if (gAppPrefs != nil && mPrefsHdl != nil)
    {
        (*mPrefsHdl)->dropMode = mDropMode;
        gAppPrefs->SavePreferenceResource(ResType_Preferences, 128, (Handle)mPrefsHdl);
    }
}
 
 
Boolean CApp::ObeyCommand(
    CommandT inCommand,
    void *ioParam)
{
    Boolean cmdHandled = true;
    
    switch (inCommand)
    {
        case cmd_DropMode:
        {
            mDropMode = !mDropMode;
            SetUpdateCommandStatus(true);
            break;
        }
        
        case cmd_Preferences:
        {
            CPrefsWindow *pw = (CPrefsWindow *)LWindow::CreateWindow(WIND_Preferences, this);
            pw->Show();
            break;
        }
        
        default:
            cmdHandled = LDocApplication::ObeyCommand(inCommand, ioParam);
            break;
    }
    
    return cmdHandled;
}
 
 
void CApp::FindCommandStatus(
    CommandT    inCommand,
    Boolean     &outEnabled,
    Boolean     &outUsesMark,
    Char16      &outMark,
    Str255      outName)
{
    outUsesMark = false;
    
    if (sBusy)
        outEnabled = false;
    else
        switch (inCommand)
        {
            case cmd_DropMode:
                outEnabled = true;
                outUsesMark = true;
                outMark = mDropMode ? checkMark:noMark;
                break;
            
            case cmd_Preferences:
                outEnabled = true;
                break;
            
            default:
                LDocApplication::FindCommandStatus(inCommand, outEnabled, outUsesMark,
                        outMark, outName);
                break;
        }
}
 
 
/* CApp::DoAEOpenOrPrintDoc
 
    Overridden from LDocApplication because we may want to quit afterwards.
*/
void CApp::DoAEOpenOrPrintDoc(
    const AppleEvent &inAppleEvent,
    AppleEvent& /* outAEReply */,
    Int32 inAENumber)
{
    AEDescList docList;
    OSErr err = ::AEGetParamDesc(&inAppleEvent, keyDirectObject,
                            typeAEList, &docList);
    ThrowIfOSErr_(err);
    
    Int32 numDocs;
    err = ::AECountItems(&docList, &numDocs);
    ThrowIfOSErr_(err);
    
    for (Int32 i = 1; i <= numDocs; i++)
    {
        AEKeyword   theKey;
        DescType    theType;
        FSSpec      theFileSpec;
        Size        theSize;
        err = ::AEGetNthPtr(&docList, i, typeFSS, &theKey, &theType,
                            (Ptr) &theFileSpec, sizeof(FSSpec), &theSize);
        ThrowIfOSErr_(err);
        
        if (inAENumber == ae_OpenDoc)
        {
            OpenDocument(&theFileSpec);
        }
        else
        {
                // let the user know we don't do printing in this app
            ::StopAlert(ALRT_WeDontPrint, nil);
        }
    }
    
    ::AEDisposeDesc(&docList);
    
        // if we are in drop mode, get outta here
    if (mDropMode)
        DoQuit();
}
 
 
void CApp::OpenDocument(
    FSSpec *inMacFSSpec)
{
    Int16 errStrIndex;
    
    try
    {
        errStrIndex = err_Window;
        
        if (mDropMode && mPrefsHdl != nil)
        {
                // just start making the movie
            MovieMakinRec params;
            params.width = (*mPrefsHdl)->width;
            params.height = (*mPrefsHdl)->height;
            params.srcSpec = *inMacFSSpec;
            
            params.tileSpec = *inMacFSSpec;
            unsigned char charspace = 31 - (*mPrefsHdl)->tileSuffix[0];
            if (params.tileSpec.name[0] > charspace)
                params.tileSpec.name[0] = charspace;
            ConcatPStr(params.tileSpec.name, (*mPrefsHdl)->tileSuffix);
 
            params.destSpec = *inMacFSSpec;
            charspace = 31 - (*mPrefsHdl)->movieSuffix[0];
            if (params.destSpec.name[0] > charspace)
                params.destSpec.name[0] = charspace;
            ConcatPStr(params.destSpec.name, (*mPrefsHdl)->movieSuffix);
            
            params.codec = (*mPrefsHdl)->codec;
            params.spatialQuality = (*mPrefsHdl)->spatialQuality;
            params.depth = (*mPrefsHdl)->depth;
            
            ListenToMessage(msg_MakeMovie, &params);
        }
        else
        {
                // put up a window for editing movie making parameters
            CPict2VRWindow *p2vr = (CPict2VRWindow *)LWindow::CreateWindow(WIND_Pict2VR, this);
            p2vr->AddListener(this);
            p2vr->SetPictFile(inMacFSSpec);
        }
    }
    catch (ExceptionCode err)
    {
        ReportError(err, err_Window);
    }
    
        // drop mode leaves the menu bar inactive
    if (mDropMode)
        UpdateMenus();
}
 
void CApp::StartUp()
{
    ChooseDocument();
}
 
void CApp::ChooseDocument()
{
    StandardFileReply macFileReply;
    SFTypeList typeList;
    
    UDesktop::Deactivate();
    typeList[0] = 'PICT';
    ::StandardGetFile(nil, 1, typeList, &macFileReply);
    UDesktop::Activate();
    if (macFileReply.sfGood)
    {
        OpenDocument(&macFileReply.sfFile);
    }
}
 
 
/* CApp::ProgressEvents
 
    An event processing function necessary because the one implemented in
    LApplication does things we dont want such as changing the cursor.
    This one is useful for processing events during lengthy processes
    (and there is a spinning cursor and a modal window in front).
*/
void CApp::ProgressEvents()
{
    EventRecord macEvent;
 
    SetUpdateCommandStatus(false);
    Boolean gotEvent;
    Int16 count = 0;
    
    do
    {
        gotEvent = ::WaitNextEvent(everyEvent, &macEvent, 0, mMouseRgnH);
        
        if (LAttachable::ExecuteAttachments(msg_Event, &macEvent))
        {
            if (gotEvent)
                DispatchEvent(macEvent);
            else
                UseIdleTime(macEvent);
        }
    
        LPeriodical::DevoteTimeToRepeaters(macEvent);
        
        if (GetUpdateCommandStatus())
            UpdateMenus();
    } while (gotEvent && ++count < 3); // do not hand over too much time
}
 
 
// ======================================
// constants and statics for movie making
 
Boolean CApp::sCancelled;
LWindow *CApp::sProgressWindow = nil;
Boolean CApp::sBusy = false;
 
const PaneIDT Button_Stop = 'stop';
const PaneIDT Caption_Proc = 'proc';
const PaneIDT Caption_Message = 'mess';
 
const ResIDT WIND_Progress = 207;
 
// ======================================
 
void CApp::ListenToMessage(
    MessageT inMessage,
    void *ioParam)
{
    switch (inMessage)
    {
            // note: one could conceivably tell the app to make a movie
            // WHILE it is making one. this would work, except that the
            // newly dropped one will be completed before the original is
            // processed. this does not include numbers of PICTs dropped
            // on the app at once in the Finder
        case msg_MakeMovie:
        {
                // the make movie message includes parameters for making the movie
            MovieMakinPtr mmp = (MovieMakinPtr)ioParam;
            
            Str255 procStr;
            ::GetIndString(procStr, STRx_Progress, str_Making);
                
                // create a path name that fits into a Str255 minus the above string
            Str255 moviePathStr;
            GetFullPathName(&mmp->destSpec, moviePathStr, sizeof (Str255) - procStr[0]);
            
            try
            {
                    // prepare progress stuff
                sCancelled = false;
                sBusy = true;
                SetUpdateCommandStatus(true);
                UpdateMenus();
                sProgressWindow = LWindow::CreateWindow(WIND_Progress, this);
                ThrowIfNil_(sProgressWindow);
                
                    // set the main progress description string
                ConcatPStr(procStr, moviePathStr);
                SetSizedDescriptor(sProgressWindow, Caption_Proc, procStr);
                
                    // listen to the stop button - provides both mouse and keyboard
                    // initiated user cancellation
                LButton *button;
                button = (LButton *)sProgressWindow->FindPaneByID(Button_Stop);
                if (button != nil)
                    button->AddListener(this);
                
                    // decide if we are replacing files
                Boolean replaceFiles = false;
                if (mPrefsHdl != nil)
                    replaceFiles = (*mPrefsHdl)->replaceFiles;
                
                    // finally, make the movie!
                CBeachBall::StartSpinningTask(5);
                Boolean completed = CMovieMaker::MakeAMovie(
                    replaceFiles,
                    mmp->srcSpec, mmp->tileSpec, mmp->destSpec,
                    mmp->width, mmp->height, 
                    mmp->pan, mmp->tilt, mmp->zoom,
                    mmp->codec, mmp->spatialQuality, mmp->depth, MovieProgressProc);
                CBeachBall::StopSpinningTask();
                
                delete sProgressWindow;
                sProgressWindow = nil;
                
                    // let the caller know how things went
                mmp->completed = completed;
            }
            catch (ExceptionCode err)
            {
                mmp->completed = false;
                
                    // the only exception that can make it here is to fail
                    // while creating the progress window
                    // all other exceptions are caught by CMovieMaker::MakeAMovie
                if (err != Exception_UserCancelled)
                    ReportError(err, err_Window);
            }
            
            sBusy = false;
            UpdateMenus();
            break;
        }
        
        case Button_Stop:
            sCancelled = true;
            break;
    }
}
 
/* CApp::MovieProgressProc
 
    Called during processing. Gets events handled, messages displayed and
    user cancellation reported.
*/
Boolean CApp::MovieProgressProc(
    StringPtr inMessage)
{
    if (inMessage != nil)
    {
        LPane *messageCaption = sProgressWindow->FindPaneByID(Caption_Message);
        messageCaption->SetDescriptor(inMessage);
        messageCaption->UpdatePort();
    }
    
    gApp->ProgressEvents();
    
    return sCancelled;
}
 
/* CApp::BugUserTilSwitchedIn
 
    Use the notification manager to let the user know they should switch in.
    Returns once this has happened.
    
    Useful for waiting to put up alerts during long processes where the possibility
    of the user having switched out is an issue.
*/
void CApp::BugUserTilSwitchedIn()
{
    if (!IsOnDuty())
    {
        NMRec note;
        OSErr nmErr;
        Handle sicnH = ::GetResource('SICN', 12000);
        
        if (sicnH)
        {
            ::HNoPurge(sicnH);
            ::DetachResource(sicnH);
        }
        
        note.qType = nmType;
        note.nmMark = 1;
        note.nmIcon = sicnH;
        note.nmSound = (Handle) -1;
        note.nmStr = nil;
        note.nmResp = nil;
        note.nmRefCon = 0;
        
        if ((nmErr = ::NMInstall(&note)) == noErr)
        {
            while (!IsOnDuty())
            {
                ProgressEvents();
            }
            nmErr = ::NMRemove(&note);
        }
        
        if (sicnH != nil)
            ::DisposeHandle(sicnH);
    }
}