QISA.c

/*
    File:       QISA.c
 
    Contains:   Main application for QISA program.
 
    Written by: DTS
 
    Copyright:  Copyright © 2002 by Apple Computer, Inc., All Rights Reserved.
 
    Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
                ("Apple") in consideration of your agreement to the following terms, and your
                use, installation, modification or redistribution of this Apple software
                constitutes acceptance of these terms.  If you do not agree with these terms,
                please do not use, install, modify or redistribute this Apple software.
 
                In consideration of your agreement to abide by the following terms, and subject
                to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs
                copyrights in this original Apple software (the "Apple Software"), to use,
                reproduce, modify and redistribute the Apple Software, with or without
                modifications, in source and/or binary forms; provided that if you redistribute
                the Apple Software in its entirety and without modifications, you must retain
                this notice and the following text and disclaimers in all such redistributions of
                the Apple Software.  Neither the name, trademarks, service marks or logos of
                Apple Computer, Inc. may be used to endorse or promote products derived from the
                Apple Software without specific prior written permission from Apple.  Except as
                expressly stated in this notice, no other rights or licenses, express or implied,
                are granted by Apple herein, including but not limited to any patent rights that
                may be infringed by your derivative works or by other works in which the Apple
                Software may be incorporated.
 
                The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
                WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
                WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
                PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
                COMBINATION WITH YOUR PRODUCTS.
 
                IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
                CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
                GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
                ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
                OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
                (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
                ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
    Change History (most recent first):
 
*/
 
/////////////////////////////////////////////////////////////////
 
// System interfaces
 
#if defined(__MACH__)
    #include <Carbon/Carbon.h>
#else
    #include <Gestalt.h>
    #include <Dialogs.h>
    #include <Menus.h>
    #include <IBCarbonRuntime.h>
    #include <PLStringFuncs.h>
#endif
 
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
 
// MoreIsBetter interfaces
 
#include "MoreCFQ.h"
#include "MoreDialogs.h"
#include "MoreControls.h"
#include "MoreAppleEvents.h"
#include "MoreOSUtils.h"
 
// Our interfaces
 
#include "QISAPanels.h"
#include "QISA.h"
#include "QISAPlatform.h"
 
/////////////////////////////////////////////////////////////////
#pragma mark ***** State and Panels
 
// Right now the panels are managed via a large constant data structure. 
// I don't support plug-in panels.  The main reason for this is time: 
// I wanted to ship and doing plug-in panels would have slowed me down.  
// However, I have kept a high degree of separation between the panels 
// and QISA itself, so there's very little excluding plug-in panels in 
// the future.
 
enum {
    kQISAStatePanelCount = 4
};
 
// QISAState represents the state for a QISA setup window. 
// Within this structure there is an array that holds the 
// state for each panel.
//
// A reference to this state is stored in the window's refCon.
 
static const OSType kQISAStateMagic = 'QISA';
 
struct QISAState {
    OSType                  magic;                      // must be kQISAStateMagic
    WindowRef               mainWindow;                 // the setup window
    EventHandlerRef         eventHandler;               // reference to our event handler
    ControlRef              tabControl;                 // reference to the tab control within the window
    Rect                    tabBounds;                  // bounds of tabControl, in window coordinates
    ControlRef              leftButton;                 // reference to the left button control within the window
    ControlRef              rightButton;                // reference to the right button control within the window
    QISAPanel *             currentPanel;               // reference to the current panel, always points within state->panels
    QISAPanel               panels[kQISAStatePanelCount];           // an array of per-panel data structures
    ControlRef              focusedControl[kQISAStatePanelCount];   // for each panel, a reference to the control that had
                                                                    // the keyboard focus when we switched from the panel
    CFMutableDictionaryRef  globalValues;               // global values, see "QISA.h" for the API that accesses this
};
 
static const OSType kQISAPanelMagic = 'PANL';
 
extern Boolean QISAIsPanelValid(const QISAPanel *panel)
    // See comment in header.
{
    return  (panel->magic      == kQISAPanelMagic)
         && (panel->state      != NULL)
         && (panel->index >= 0) && (panel->index < kQISAStatePanelCount)
         && (panel->window     != NULL)
         && (panel->Initialise != NULL)
         && (panel->Terminate  != NULL)
         && (panel->SwitchTo   != NULL)
         && (panel->SwitchFrom != NULL)
         ;
}
 
static Boolean QISAIsStateValid(const QISAState *state)
    // For debugging.  Returns true if state is valid.
{
    Boolean result;
    int     panel;
    
    result =   (state->magic        == kQISAStateMagic)
//          && (state->mainWindow   != NULL)                // window can be NULL invalid during termination
            && (state->tabControl   != NULL)
            && (state->leftButton   != NULL)
            && (state->rightButton  != NULL)
            && (state->currentPanel >= &state->panels[0])
            && (state->currentPanel <= &state->panels[ kQISAStatePanelCount - 1])
            && (state->globalValues != NULL);
            ;
    if (result) {
        for (panel = 0; panel < kQISAStatePanelCount; panel++) {
            if (state->panels[panel].initialised && ! QISAIsPanelValid(&state->panels[panel]) ) {
                result = false;
            }
        }
    }
    return result;
}
 
extern Boolean QISAIsButtonEnabled(QISAPanel *panel, QISAPanelSwitchDirection direction)
    // See comment in header.
{
    Boolean     result;
    ControlRef  button;
    
    assert(QISAIsPanelValid(panel));
    assert(QISAIsStateValid(panel->state));
    
    switch (direction) {
        case kQISAPanelSwitchDirectionForward:
            button = panel->state->rightButton;
            break;
        case kQISAPanelSwitchDirectionBackward:
            button = panel->state->leftButton;
            break;
        default:
            assert(false);
            button = NULL;
            result = false;
            break;
    }
    if (button != NULL) {
        result = IsControlActive(button);
    }
    return result;
}
 
extern void QISASetButtonEnable(QISAPanel *panel, QISAPanelSwitchDirection direction, Boolean enable)
    // See comment in header.
{
    ControlRef button;
 
    assert(QISAIsPanelValid(panel));
    assert(QISAIsStateValid(panel->state));
    
    switch (direction) {
        case kQISAPanelSwitchDirectionForward:
            button = panel->state->rightButton;
            break;
        case kQISAPanelSwitchDirectionBackward:
            button = panel->state->leftButton;
            break;
        default:
            assert(false);
            button = NULL;
            break;
    }
    if (button != NULL) {
        SetControlActive(button, enable);
    }
}
 
extern OSStatus QISAGetGlobalValue(const QISAPanel *panel, CFStringRef key, CFPropertyListRef *value)
    // See comment in header.
{
    OSStatus err;
    
    assert(QISAIsPanelValid(panel));
    assert(QISAIsStateValid(panel->state));
    assert(key != NULL);
    assert(value != NULL);
    
    if ( CFDictionaryGetValueIfPresent(panel->state->globalValues, key, (const void **) value) ) {
        err = noErr;
    } else {
        *value = NULL;
        err = kCFQKeyNotFoundErr;
    }
    
    assert( (err == noErr) == (*value != NULL) );
    
    return err;
}
 
extern OSStatus QISASetGlobalValue(const QISAPanel *panel, CFStringRef key, CFPropertyListRef  value)
    // See comment in header.
{
    OSStatus err;
    
    assert(QISAIsPanelValid(panel));
    assert(QISAIsStateValid(panel->state));
    assert(key != NULL);
    
    if (value == NULL) {
        CFDictionaryRemoveValue(panel->state->globalValues, key);
        err = noErr;
    } else {
        CFDictionarySetValue(panel->state->globalValues, key, value);
        err = noErr;
    }
    return err;
}
 
extern OSStatus QISACopyGlobalsDict(const QISAPanel *panel, CFMutableDictionaryRef *result)
    // See comment in header.
{
    OSStatus err;
    
    assert(QISAIsPanelValid(panel));
    assert(QISAIsStateValid(panel->state));
    assert( result != NULL);
    assert(*result == NULL);
 
    *result = CFDictionaryCreateMutableCopy(NULL, 0, panel->state->globalValues);
    err = CFQError(*result);
    
    assert( (err == noErr) == (*result != NULL) );
    
    return err;
}
 
static OSStatus SwitchFromPanel(QISAState *state, QISAPanelSwitchDirection direction, QISAPanel **nextPanel)
    // Switches away from the current panel in the specified 
    // direction and returns the next panel that should be 
    // switched to.  This is called as part of handling 
    // the forward and back buttons (ChangeToNewPanel).
{
    OSStatus    err;
    OSStatus    junk;
    
    assert(QISAIsStateValid(state));
    assert(nextPanel != NULL);
    
    err = state->currentPanel->SwitchFrom(state->currentPanel, direction, nextPanel);
    if (err == noErr) {
 
        // Save the keyboard focus for the panel we just switched from 
        // and then clear the focus.  If you don't do this, then weird 
        // things happen (for example, an insertion point in an edit 
        // text control in the hidden panel will keep blinking on top 
        // of the new panel!).
        
        junk = GetKeyboardFocus(state->mainWindow, &state->focusedControl[state->currentPanel->index]);
        assert(junk == noErr);
        junk = ClearKeyboardFocus(state->mainWindow);
        assert(junk == noErr);
 
        // Hide the current panel's root control (which hides all of 
        // its embedded sub-controls).
        
        HideControl(state->currentPanel->panelControl);
    }
 
    assert( (err != noErr) || (*nextPanel >= &state->panels[0]) && (*nextPanel <= &state->panels[kQISAStatePanelCount - 1]) );
 
    return err;
}
 
static OSStatus SwitchToPanel(QISAState *state, QISAPanel *newPanel)
    // Switches to a new panel.  This is called as part of handling 
    // the forward and back buttons (ChangeToNewPanel) but is also called 
    // when the window is created to switch to the initial panel.
{
    OSStatus    err;
    OSStatus    junk;
    
    assert(QISAIsStateValid(state));
    assert( (newPanel >= &state->panels[0]) && (newPanel <= &state->panels[kQISAStatePanelCount - 1]) );
 
    // If the panel plug-in has not yet been initialised, initialise it.
    // Wow, lazy code.  Bertrand would be so happy.
    
    err = noErr;
    if ( ! newPanel->initialised ) {
        err = newPanel->Initialise(newPanel);
        newPanel->initialised = (err == noErr);
    }
    
    // Switch the tab control to the right panel and show that panel's 
    // sub-controls.
    
    if (err == noErr) {
        SetControlValue(state->tabControl, (SInt16) (newPanel->index + 1));
 
        ShowControl(newPanel->panelControl);
    }
    
    // Run the plug-in's SwitchTo routine.
    
    if (err == noErr) {
        err = newPanel->SwitchTo(newPanel);
    }
    
    // Restore the panel's saved keyboard focus (if any).
    // If the SwitchTo proc established focus, leave that alone, 
    // otherwise see whether we've remembered focus from a previous 
    // switch from this panel and restore that, otherwise use 
    // AdvanceKeyboardFocus to focus the first item.
    
    if (err == noErr) {
        ControlRef focusedControl;
        
        if ( (GetKeyboardFocus(state->mainWindow, &focusedControl) != noErr) 
                || (focusedControl == NULL) ) {
 
            focusedControl = state->focusedControl[newPanel->index];
            if (focusedControl == NULL) {
                junk = AdvanceKeyboardFocus(state->mainWindow);
                assert( (junk == noErr) || (junk == errCouldntSetFocus) );
            } else {
                junk = SetKeyboardFocus(state->mainWindow, focusedControl, kControlFocusNextPart);
                assert(junk == noErr);
            }
        }
    }
    
    // Finally, record the new panel as the current panel.
    
    if (err == noErr) {
        state->currentPanel = newPanel;
    }
 
    assert(QISAIsStateValid(state));        // post condition
    
    return err;
}
 
static OSStatus ChangeToNewPanel(QISAState *state, QISAPanelSwitchDirection direction)
    // Change to the next panel in the specified direction.  Called when 
    // the user clicks on the forward or back buttons.  
{
    OSStatus    err;
    OSStatus    junk;
    QISAPanel * oldPanel;
    QISAPanel * nextPanel;
    
    assert(QISAIsStateValid(state));
 
    oldPanel = state->currentPanel;
    
    err = SwitchFromPanel(state, direction, &nextPanel);
    
    // At this point we're in a weird state because currentPanel no longer 
    // matches the controls in the window.  Let's get out of that state ASAP, 
    // and hopefully without errors.
    
    if (err == noErr) {
        assert( (nextPanel >= &state->panels[0]) && (nextPanel <= &state->panels[kQISAStatePanelCount - 1]) );
        
        err = SwitchToPanel(state, nextPanel);
        
        // If the new panel opened cleanly, we're good.  Otherwise, 
        // try to switch back to the old panel.  We ignore any 
        // errors coming from the SwitchToPanel(state, oldPanel) 
        // because we're already going to return a (hopefully valid) 
        // error that we get from SwitchToPanel(state, nextPanel).
        
        if (err != noErr) {
            junk = SwitchToPanel(state, oldPanel);
            assert(junk == noErr);
        }
    }
 
    return err;
}
 
static QISAState *GetWindowQISAState(WindowRef window)
    // Returns the QISA state for a window.  This will tolerate 
    // window being NULL, and return NULL in that case.
{
    QISAState *state;
    
    state = NULL;
    if (window != NULL) {
        state = (QISAState *) GetWRefCon(window);
    }
    if (state != NULL) {
        assert(QISAIsStateValid(state));
    }
    return state;
}
 
static void DisposeQISAState(QISAState *state)
    // Disposes of the QISA state and it's corresponding 
    // sub-structures.
{
    OSStatus    junk;
    int         panel;
    
    assert(state->magic == kQISAStateMagic);
    
    for (panel = 0; panel < kQISAStatePanelCount; panel++) {
        if (state->panels[panel].initialised) {
            (void) state->panels[panel].Terminate(&state->panels[panel]);
        }
    }
    
    // Remove the event handler *before* disposing of the window 
    // because otherwise WindowEventHandler will be sent the 
    // kEventClassWindow/kEventWindowClosed event and will 
    // call DisposeQISAState, which is bad.
    
    if (state->eventHandler != NULL) {
        junk = RemoveEventHandler(state->eventHandler);
        assert(junk == noErr);
    }
    if (state->mainWindow != NULL) {
        DisposeWindow(state->mainWindow);
    }
    CFQRelease(state->globalValues);
    state->magic = 'Free';          // overwrite magic to detect double dispose
    free(state);
}
 
/////////////////////////////////////////////////////////////////
#pragma mark ***** Default Panel
 
// The default panel routines represent the default implementation 
// of the panel callbacks.  If a panel does not specifically 
// override those callbacks, this is what they do.  See the 
// panel initialisation routines in "QISAPanels.c" for examples 
// of how these routines are overridden.
 
static OSStatus  DefaultPanelInitialise(QISAPanel *panel)
    // See PanelInitialiseProc comments.  Default implementation.
{
    #pragma unused(panel)
 
    assert( QISAIsPanelValid(panel) );
    assert( QISAIsStateValid(panel->state) );
 
    return noErr;
}
 
static void      DefaultPanelTerminate(QISAPanel *panel)
    // See PanelTerminateProc comments.  Default implementation.
{
    #pragma unused(panel)
 
    assert( QISAIsPanelValid(panel) );
    assert( QISAIsStateValid(panel->state) );
}
 
static OSStatus DefaultPanelSwitchTo(QISAPanel *panel)
    // See PanelSwitchToProc comments.  Default implementation.
{
    QISAState *state;
    
    assert( QISAIsPanelValid(panel) );
    assert( QISAIsStateValid(panel->state) );
    state = panel->state;
    
    // Enables the forward button if this isn't the last panel, 
    // and the backward button if this isn't the first panel.
    
    QISASetButtonEnable(panel, kQISAPanelSwitchDirectionForward,  (panel != &state->panels[kQISAStatePanelCount - 1]) );
    QISASetButtonEnable(panel, kQISAPanelSwitchDirectionBackward, (panel != &state->panels[0]) );
 
    return noErr;
}
 
static OSStatus DefaultPanelSwitchFrom(QISAPanel *panel, QISAPanelSwitchDirection direction, QISAPanel **nextPanel)
    // See PanelSwitchFromProc comments.  Default implementation.
{
    assert( QISAIsPanelValid(panel) );
    assert( QISAIsStateValid(panel->state) );
    assert(nextPanel != NULL);
 
    // Returns the next or previous panel.
    
    if (direction == kQISAPanelSwitchDirectionForward) {
        *nextPanel = (panel + 1);
    } else {
        assert(direction == kQISAPanelSwitchDirectionBackward);
        *nextPanel = (panel - 1);
    }
    assert( (*nextPanel >= &panel->state->panels[0]) && (*nextPanel <= &panel->state->panels[kQISAStatePanelCount - 1]) );
    return noErr;
}
 
/////////////////////////////////////////////////////////////////
#pragma mark ***** UI Commands
 
// These HI commands are sent when the forward and back buttons 
// are pressed (or their equivalent menu commands are chosen).
 
enum {
    kHICommandLeft  = 'LEFT',
    kHICommandRight = 'RGHT'
};
 
static EventHandlerUPP gWindowEventHandlerUPP;      // -> WindowEventHandler
 
 
static const EventTypeSpec kWindowEvents[] = { 
    {kEventClassCommand, kEventCommandProcess}, 
    {kEventClassWindow,  kEventWindowClose}, 
    {kEventClassWindow,  kEventWindowClosed} 
};
 
static pascal OSStatus WindowEventHandler(EventHandlerCallRef inHandlerCallRef, 
                                          EventRef inEvent, void *inUserData)
    // The Carbon event handler for the window.  This processes the 
    // left and right button HI commands and a two stage close operation.
{
    OSStatus    err;
    UInt32      eventClass;
    UInt32      eventKind;
    QISAState * state;
    #pragma unused(inHandlerCallRef)
    
    state = (QISAState *) inUserData;
    assert( QISAIsStateValid(state) );
 
    eventClass = GetEventClass(inEvent);
    eventKind  = GetEventKind(inEvent);
 
    if ( (eventClass == kEventClassCommand) && (eventKind == kEventCommandProcess) ) {
        HICommand   command;
        
        err = GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(command), NULL, &command);
        if (err == noErr) {
            switch (command.commandID) {
                case kHICommandLeft:
                case kHICommandRight:
                    err = ChangeToNewPanel(state, command.commandID == kHICommandRight ? kQISAPanelSwitchDirectionForward : kQISAPanelSwitchDirectionBackward);
                    QISADisplayError(NULL, err, CFSTR("SwitchingToThePanel"));
                    err = noErr;
                    break;
                default:
                    err = eventNotHandledErr;
                    break;
            }
        }
    } else if ( (eventClass == kEventClassWindow) && (eventKind == kEventWindowClose) ) {
        
        // The user has asked to close the window.  If we're on any panel other than 
        // the first panel, then call SwitchFromPanel to stop the current panel.  
        // If the panel is busy, it can put up a dialog requesting user confirmation, 
        // and return userCanceledErr if it did.
        
        err = noErr;
        if (state->currentPanel != &state->panels[0]) {
            QISAPanel *junkNextPanel;
            
            err = SwitchFromPanel(state, kQISAPanelSwitchDirectionBackward, &junkNextPanel);
        }
        
        // If we get no error, then we're ready to close the window.  We return 
        // eventNotHandledErr, which causes the standard close handler to run. 
        // This calls DisposeWindow on our window, which sends us the event
        // kEventClassWindow/kEventWindowClosed, which we use to dispose of our 
        // state.
        
        if (err == noErr) {
            err = eventNotHandledErr;
        }
    } else if ( (eventClass == kEventClassWindow) && (eventKind == kEventWindowClosed) ) {
        
        // The window is already being disposed of.  NULL out our reference 
        // so that DisposeQISAState does not try to dispose it again.
        
        state->mainWindow = NULL;
        
        // Get rid of our per-window state.
        
        DisposeQISAState(state);
        err = noErr;
    } else {
        assert(false);          // this event shouldn't have been delivered to us
        err = eventNotHandledErr;
    }
    
    return err;
}
 
static OSStatus CopyGlobalValuesFromFile(CFMutableDictionaryRef *result)
    // This routine is called by QISACommandNew to initialise the 
    // global values dictionary from the contents of a plist file 
    // within the application package.
{
    OSStatus                err;
    CFURLRef                url;
    CFMutableDictionaryRef  localResult;
    CFDictionaryRef         platformDict;
    
    assert( result != NULL);
    assert(*result == NULL);
    
    localResult = NULL;
    platformDict = NULL;
    
    // Read default prefs from a resource within the application package.
    
    url = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("SetupInfo"), CFSTR("plist"), NULL);
    err = CFQError(url);
    if (err == noErr) {
        err = CFQPropertyListCreateFromXMLCFURL(url, kCFPropertyListMutableContainers, (CFPropertyListRef *) &localResult);
    }
    if (err == noErr) {
        if ( CFGetTypeID(localResult) != CFDictionaryGetTypeID() ) {
            err = kCFQDataErr;
        }
    }
 
    // Merge in the properties from the platform plug-in.
    
    if (err == noErr) {
        err = QISACopyPlatformProperties(&platformDict);
    }
    if (err == noErr) {
        err = CFQDictionaryMerge(localResult, platformDict);
    }
 
    // In the debug build, merge in preferences from another debugging version 
    // of the file.  This allows me to have an Apple-private dialup line configured 
    // while I'm debugging, but not ship that info as part of the sample.
 
    #if ! defined(NDEBUG)
        if (err == noErr) {
            CFDictionaryRef debugDict;
            
            CFQRelease(url);
            
            debugDict = NULL;
            
            url = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("SetupInfoDebug"), CFSTR("plist"), NULL);
            err = CFQError(url);
            if (err == noErr) {
                err = CFQPropertyListCreateFromXMLCFURL(url, kCFPropertyListImmutable, (CFPropertyListRef *) &debugDict);
            }
            if (err == noErr) {
                if ( CFGetTypeID(debugDict) != CFDictionaryGetTypeID() ) {
                    err = kCFQDataErr;
                }
            }
            if (err == noErr) {
                err = CFQDictionaryMerge(localResult, debugDict);
            }
            
            CFQRelease(debugDict);
            
            if (err != noErr) {
                DebugStr("\pCopyGlobalValuesFromFile: Error loading SetupInfoDebug.plist ; g");
                err = noErr;
            }
        }
        // CFShow(localResult);
    #endif
 
    // Clean up.
    
    if (err == noErr) {
        *result = localResult;
    } else {
        CFQRelease(localResult);
    }
    CFQRelease(url);
    CFQRelease(platformDict);
    
    assert( (err == noErr) == (*result != NULL) );
 
    return err;
}
 
// The following array is used to construct the per-panel data structure. 
// It contains the initialisation routine for each panel, in order.  This 
// is currently hard-wired, and is one of the key things that would have 
// to change if panel plug-ins were used.
 
static const PanelInitialiseProc kPanelInitialisers[kQISAStatePanelCount] = { 
    PortCCLPanelInitialise, 
    UserPassPanelInitialise, 
    SetupPanelInitialise, 
    DefaultPanelInitialise 
};
 
static OSStatus QISACommandNew(void)
    // Handles the "New" HI command by creating a new setup window.
{
    OSStatus    err;
    IBNibRef    nibRef;
    QISAState * state;
    int         panel;
 
    nibRef = NULL;
 
    // Allocate the per-window state.
    
    err = noErr;
    state = (QISAState *) calloc(1, sizeof(*state));
    if (state == NULL) {
        err = memFullErr;
    }
    if (err == noErr) {
        state->magic = kQISAStateMagic;
    }
    
    // Create the window from the NIB.
    
    if (err == noErr) {
        err = CreateNibReference(CFSTR("QISAWindow"), &nibRef);
    }
    if (err == noErr) {
        err = CreateWindowFromNib(nibRef, CFSTR("MainWindow"), &state->mainWindow);
    }
    
    // Initialise the state based on the window.
    
    if (err == noErr) {
        SetWRefCon(state->mainWindow, (long) state);
    }
    if (err == noErr) {
        err = GetControlByIDQ(state->mainWindow, 'TAB!', 0, &state->tabControl);
    }
    if (err == noErr) {
        (void) GetControlBounds(state->tabControl, &state->tabBounds);
    }
    if (err == noErr) {
        err = GetControlByIDQ(state->mainWindow, 'LBTN', 0, &state->leftButton);
    }
    if (err == noErr) {
        err = GetControlByIDQ(state->mainWindow, 'RBTN', 0, &state->rightButton);
    }
    if (err == noErr) {     
        err = CopyGlobalValuesFromFile(&state->globalValues);
    }
    
    // Setup each panel.  We don't actually call the Initialise proc 
    // here, but instead do it lazily in SwitchToPanel.
    
    if (err == noErr) {
        for (panel = 0; panel < kQISAStatePanelCount; panel++) {
 
            state->panels[panel].magic      = kQISAPanelMagic;
            state->panels[panel].state      = state;
            state->panels[panel].index      = panel;
            state->panels[panel].window     = state->mainWindow;
 
            state->panels[panel].Initialise = kPanelInitialisers[panel];
            state->panels[panel].Terminate  = DefaultPanelTerminate;
            state->panels[panel].SwitchTo   = DefaultPanelSwitchTo;
            state->panels[panel].SwitchFrom = DefaultPanelSwitchFrom;
 
            err = GetControlByIDQ(state->mainWindow, 'Panl', panel, &state->panels[panel].panelControl);
            if (err == noErr) {
                HideControl(state->panels[panel].panelControl);
            }
 
            if (err != noErr) {
                break;
            }
        }
    }
    
    // Install the window's Carbon event handler.
    
    if (err == noErr) {
        if (gWindowEventHandlerUPP == NULL) {
            gWindowEventHandlerUPP = NewEventHandlerUPP(WindowEventHandler);
            assert(gWindowEventHandlerUPP != NULL);
        }
        err = InstallWindowEventHandler(state->mainWindow, gWindowEventHandlerUPP, 
                                        GetEventTypeCount(kWindowEvents), kWindowEvents, 
                                        state, &state->eventHandler);
    }
    
    // Switch to the first panel.
    
    if (err == noErr) {     
        state->currentPanel = &state->panels[0];
        err = SwitchToPanel(state, state->currentPanel);
    }
    
    // Show the window, and we're done.
    
    if (err == noErr) {
        ShowWindow(state->mainWindow);
    }
    
    // Clean up.
    
    if (nibRef != NULL) {
        DisposeNibReference(nibRef);
    }
    if (err != noErr && state != NULL) {
        DisposeQISAState(state);
    }
    QISADisplayError(NULL, err, CFSTR("CreatingTheNewDocument"));
    
    return err;
}
 
static OSStatus QISACommandAbout(void)
    // Handles the "About" HI command.
{
    OSStatus            err;
    ProcessSerialNumber myPSN;
    CFStringRef         errStr;
    CFStringRef         expStr;
    SInt16              junkHit;
 
    errStr = NULL;
    expStr = NULL;
 
    // First line of alert is the name of the application, from the "Info.plist".
    
    myPSN.highLongOfPSN = 0;
    myPSN.lowLongOfPSN  = kCurrentProcess;
    errStr = NULL;
    err = CopyProcessName(&myPSN, &errStr);
 
    // Second line is a string from "Localizable.strings".
    
    if (err == noErr) {
        expStr = CFCopyLocalizedString(CFSTR("AboutBoxCopyright"), CFSTR("ASampleInternetSetupAssistantBlahBlahBlah"));
        err = CFQError(expStr);
    }
    
    // Now display an alert using our StandardAlert wrapper.
    
    if (err == noErr) {
        err = StandardAlertCFStringCompat(kAlertNoteAlert, errStr, expStr, NULL, &junkHit);
    }
    
    CFQRelease(expStr);
    CFQRelease(errStr);
 
    QISADisplayError(NULL, err, CFSTR("DisplayingTheAboutBox"));
 
    return err;
}
 
static EventHandlerUPP gApplicationEventHandlerUPP;     // -> ApplicationEventHandler
 
static const EventTypeSpec kApplicationEvents[] = { {kEventClassCommand, kEventCommandProcess} };
 
static pascal OSStatus ApplicationEventHandler(EventHandlerCallRef inHandlerCallRef, 
                                               EventRef inEvent, void *inUserData)
    // The Carbon event handler for the application.  This processes the 
    // "New" and "About" HI commands.
{
    OSStatus    err;
    HICommand   command;
    #pragma unused(inHandlerCallRef)
    #pragma unused(inUserData)
    
    assert( GetEventClass(inEvent) == kEventClassCommand  );
    assert( GetEventKind(inEvent)  == kEventCommandProcess);
    
    err = GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(command), NULL, &command);
    if (err == noErr) {
        switch (command.commandID) {
            case kHICommandAbout:
                (void) QISACommandAbout();
                break;
            case kHICommandNew:
                (void) QISACommandNew();
                break;
            default:
                err = eventNotHandledErr;
                break;
        }
    }
    
    return err;
}
 
static EventHandlerUPP gEditMenuEventHandlerUPP;        // -> EditMenuEventHandler
 
static const EventTypeSpec kEditMenuEvents[1] = { {kEventClassMenu, kEventMenuEnableItems} };
 
static pascal OSStatus EditMenuEventHandler(EventHandlerCallRef inHandlerCallRef, 
                                               EventRef inEvent, void *inUserData)
    // The Carbon event handler for the Edit menu.  This enables 
    // and disables the forward and back menu commands base 
    // on the state of the buttons (which are kept up-to-date 
    // by the panels).
{
    OSStatus    err;
    MenuRef     menuH;
    #pragma unused(inHandlerCallRef)
    #pragma unused(inUserData)
    
    assert( GetEventClass(inEvent) == kEventClassMenu   );
    assert( GetEventKind( inEvent) == kEventMenuEnableItems );
 
    err = GetEventParameter(inEvent, kEventParamDirectObject, typeMenuRef, NULL, sizeof(menuH), NULL, &menuH);
    if (err == noErr) {
        QISAState *     state;
        Boolean         leftEnable;
        Boolean         rightEnable;
        
        leftEnable = false;
        rightEnable = false;
        state = GetWindowQISAState(FrontWindow());
        if (state != NULL) {
            leftEnable  = IsControlActive(state->leftButton );
            rightEnable = IsControlActive(state->rightButton);
        }
        if (leftEnable) {
            EnableMenuCommand(menuH,  kHICommandLeft);
        } else {
            DisableMenuCommand(menuH, kHICommandLeft);
        }
        if (rightEnable) {
            EnableMenuCommand(menuH,  kHICommandRight);
        } else {
            DisableMenuCommand(menuH, kHICommandRight);
        }
    }
    
    return err;
}
 
/////////////////////////////////////////////////////////////////
#pragma mark ***** Apple event handling
 
static AEEventHandlerUPP gNewAppleEventHandlerUPP;          // -> NewAppleEventHandler
 
static pascal OSErr NewAppleEventHandler(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon)
    // The Apple event handler for the 'oapp' and 'rapp' Apple events. 
    // This basically just calls QISACommandNew.  Note that it only calls
    // QISACommandNew if it's an "open application" event or it's a 
    // "re-open application" event and there's no front window.
{
    OSStatus err;
 
    assert(theAppleEvent != NULL);
    assert(reply != NULL);
    assert( (handlerRefcon == kAEOpenApplication) || (handlerRefcon == kAEReopenApplication) );
    
    err = MoreAEGotRequiredParams(theAppleEvent);
    if (err == noErr) {
        if ( (handlerRefcon == kAEOpenApplication) || (FrontWindow() == NULL) ) {
            err = QISACommandNew();
        }
    }
    return (OSErr) err;
}
 
/////////////////////////////////////////////////////////////////
#pragma mark ***** Error Handling
 
// Change the "1" to a "0" to have QISADisplayError print each 
// of its temporary strings.  This greatly helps in debugging.
 
#if 1 || defined(NDEBUG)
 
    #define CFShowDebugString(str)
    
#else
 
    static void CFShowDebugString(CFStringRef str)
        // Show a CFString in a way that can be seen on both traditional 
        // Mac OS and Mac OS X.
    {
        Str255 pStr;
        
        if ( MoreRunningOnMacOSX() ) {
            CFShow(str);
        } else {
            if ( CFStringGetPascalString(str, pStr, sizeof(pStr), kCFStringEncodingMacRoman) ) {
                (void) PLstrcat(pStr, "\p;g");
                DebugStr(pStr);
            }
        }
    }
    
#endif
 
extern void QISADisplayError(QISAPanel *panel, OSStatus errNum, CFStringRef actionKey)
    // See comment in header.
{
    assert( (panel == NULL) || (QISAIsPanelValid(panel)) );
    assert(actionKey != NULL);
    
    if ( (errNum != noErr) && (errNum != userCanceledErr) && (AEInteractWithUser(1 * 60 * 60, NULL, NULL) == noErr) ) {
        OSStatus    err;
        SInt16      junkItemHit;
        CFStringRef localizedAction;
        CFStringRef errNumKey;
        CFStringRef localizedErrNum;
        CFStringRef errStrFormat;
        CFStringRef expStrFormat;
        CFStringRef errStr;
        CFStringRef expStr;
        
        localizedAction = NULL;
        errNumKey       = NULL;
        localizedErrNum = NULL;
        errStrFormat    = NULL;
        expStrFormat    = NULL;
        errStr          = NULL;
        expStr          = NULL;
        
        // Localize the action.
        
        localizedAction = CFCopyLocalizedString(actionKey, NULL);
        err = CFQError(localizedAction);
        CFShowDebugString(localizedAction);
 
        // Localize the error.
        
        if (err == noErr) {
            errNumKey = CFStringCreateWithFormat(NULL, NULL, CFSTR("%ld"), errNum);
            err = CFQError(errNumKey);
            CFShowDebugString(errNumKey);
        }
        if (err == noErr) {
            localizedErrNum = CFBundleCopyLocalizedString(CFBundleGetMainBundle(), errNumKey, NULL, CFSTR("Errors"));
            err = CFQError(localizedErrNum);
            CFShowDebugString(localizedErrNum);
            
            if ( (err == noErr) && CFEqual(localizedErrNum, errNumKey) ) {
                CFQRelease(localizedErrNum);
                
                localizedErrNum = CFBundleCopyLocalizedString(CFBundleGetMainBundle(), CFSTR("default"), NULL, CFSTR("Errors"));
                err = CFQError(localizedErrNum);
                CFShowDebugString(localizedErrNum);
            }
        }
 
        // Create the error and explanation strings.
        
        if (err == noErr) {
            errStrFormat = CFCopyLocalizedString(CFSTR("ErrorStringFormat"), CFSTR("Error%@"));
            err = CFQError(errStrFormat);
            CFShowDebugString(errStrFormat);
        }
        if (err == noErr) {
            errStr = CFStringCreateWithFormat(NULL, NULL, errStrFormat, localizedAction);
            err = CFQError(errStr);
            CFShowDebugString(errStr);
        }
        if (err == noErr) {
            expStrFormat = CFCopyLocalizedString(CFSTR("ErrorExplanationStringFormat"), CFSTR("TheOperationFailedBecause%@(%@)"));
            err = CFQError(expStrFormat);
            CFShowDebugString(expStrFormat);
        }
        if (err == noErr) {
            expStr = CFStringCreateWithFormat(NULL, NULL, expStrFormat, localizedErrNum, errNumKey);
            err = CFQError(expStr);
            CFShowDebugString(expStr);
        }
        
        // Display the dialog.
        
        if (err == noErr) {     
            err = StandardAlertCFStringCompat(kAlertStopAlert, errStr, expStr, NULL, &junkItemHit);
        }
        
        // If the above fails, use an emergency non-localized dialog.
        
        if (err != noErr) {
            #if defined(NDEBUG)
                (void) StandardAlert(kAlertStopAlert, "\pUnknown error", "\p", NULL,o &junkItemHit);
            #else
                DebugStr("\pQISADisplayError: Got an error displaying the error!");
            #endif
        }
        
        CFQRelease(localizedAction);
        CFQRelease(errNumKey);
        CFQRelease(localizedErrNum);
        CFQRelease(errStrFormat);
        CFQRelease(expStrFormat);
        CFQRelease(errStr);
        CFQRelease(expStr);
    }
}
 
/////////////////////////////////////////////////////////////////
#pragma mark ***** Startup
 
static SInt16 kEditMenuID = 130;
 
static OSStatus SetupMenus(void)
    // Sets up the menu bar.
{
    OSStatus    err;
    IBNibRef    nibRef;
    Handle      menuBar;
 
    nibRef  = NULL;
    menuBar = NULL;
    
    // Use a different NIB depending on whether you're running 
    // on traditional Mac OS or Mac OS X.
    
    if ( MoreRunningOnMacOSX() ) {
        err = CreateNibReference(CFSTR("QISAMenus"), &nibRef);
    } else {
        err = CreateNibReference(CFSTR("QISAMenus9"), &nibRef);
    }
    if (err == noErr) {
        err = CreateMenuBarFromNib(nibRef, CFSTR("MainMenuBar"), &menuBar);
    }
    if (err == noErr) {
        SetMenuBar(menuBar);
    }
    
    // Install a Carbon event handler for the Edit menu to handler 
    // enabling and disabling of the forward and back menu items.
    
    if (err == noErr) {
        MenuRef menuH;
        
        menuH = GetMenuHandle(kEditMenuID);
        assert(menuH != NULL);
 
        gEditMenuEventHandlerUPP = NewEventHandlerUPP(EditMenuEventHandler);
        assert(gEditMenuEventHandlerUPP != NULL);
 
        err = InstallMenuEventHandler(menuH, gEditMenuEventHandlerUPP, 
                                      GetEventTypeCount(kEditMenuEvents), 
                                      kEditMenuEvents, NULL, NULL);
    }
    
    // Clean up.
    
    if (nibRef != NULL) {
        DisposeNibReference(nibRef);
    }
    if (menuBar != NULL) {
        DisposeHandle(menuBar);
    }
    return err;
}
 
int main(int argc, char **argv)
    // The application's entry point!  Some platform version checking, 
    // following by some initialisation, following by the main event loop.
{
    OSStatus err;
    UInt32   version;
    #pragma unused(argc)
    #pragma unused(argv)
 
    #if !defined(NDEBUG) && TARGET_RT_MAC_CFM
        if ( GetCurrentKeyModifiers() & optionKey ) {
            DebugStr("\pQISA main");
        }
    #endif
    
    err = noErr;
    version = GetSystemVersion();
    if (version < 0x0910) {
        // Right now we've only tested on 9.1 and above.  We should bring this 
        // requirement down at some later time.
        err = 5500;
    } else if ( (GetSystemVersion() >= 0x01000) && (GetSystemVersion() < 0x01010) ) {
        // 10.0.x is not supported because it does not have a public 
        // System Configuration framework API.
        err = 5501;
    }
    if (err == noErr) {
        err = Gestalt(gestaltCarbonVersion, (SInt32 *) &version);
    }
    if ( (err == noErr) && (GetSystemVersion() < 0x01000) && (version < 0x00000160) ) {
        // We've only tested with CarbonLib 1.6, so we won't run on earlier versions.
        // However, we only care about gestaltCarbonVersion on Mac OS 9.  Versions of 
        // Mac OS X prior to 10.2 have lower gestaltCarbonVersion and that's OK.
        err = 5502;
    }
    if ( (err == noErr) && MoreRunningOnClassic() ) {
        err = 5503;
    }
    if (err == noErr) {
        err = QISAPlatformInit(CFBundleGetMainBundle());
    }
    if (err == noErr) {
        err = SetupMenus();
    }
    if (err == noErr) {
        gApplicationEventHandlerUPP = NewEventHandlerUPP(ApplicationEventHandler);
        assert(gApplicationEventHandlerUPP != NULL);
 
        err = InstallApplicationEventHandler(gApplicationEventHandlerUPP, 
                                             GetEventTypeCount(kApplicationEvents), 
                                             kApplicationEvents, NULL, NULL);
    }
    if (err == noErr) {
        gNewAppleEventHandlerUPP = NewAEEventHandlerUPP(NewAppleEventHandler);
        assert(gNewAppleEventHandlerUPP != NULL);
 
        err = AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, 
                                    gNewAppleEventHandlerUPP, kAEOpenApplication, false);
        if (err == noErr) {
            err = AEInstallEventHandler(kCoreEventClass, kAEReopenApplication, 
                                        gNewAppleEventHandlerUPP, kAEReopenApplication, false);
        }
    }
    if (err == noErr) {
        RunApplicationEventLoop();
    }
 
    QISADisplayError(NULL, err, CFSTR("StartingUp"));
 
    return 0;
}