QISAPanels.c

/*
    File:       QISAPanels.c
 
    Contains:   Implementation of the various panels.
 
    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):
 
*/
 
/////////////////////////////////////////////////////////////////
 
#include "QISAPanels.h"
 
// System interfaces
 
#if TARGET_RT_MAC_CFM
    #include <IBCarbonRuntime.h>
    #include <CFNumber.h>
#endif
 
#include <stdio.h>
#include <stdlib.h>
 
// MoreIsBetter interfaces
 
#include "MoreControls.h"
#include "MoreDialogs.h"
#include "MoreCFQ.h"
#include "MoreCarbonEvents.h"
#include "MoreProcesses.h"
#include "MoreAEDataModel.h"
#include "MoreAppleEvents.h"
 
// QISA interfaces
 
#include "QISAPlatform.h"
#include "QISA.h"
 
/////////////////////////////////////////////////////////////////
#pragma mark ***** Utilities
 
#if 0
 
static void *GetPopupMenuRefCon(ControlRef control)
    // Gets the menu refcon from a popup menu control.
{
    OSStatus        err;
    void *          result;
    MenuRef         popupMenu;
    MenuItemIndex   currentItem;
    
    assert(control != NULL);
    
    result = NULL;
    err = GetControlData(control, kControlEntireControl, kControlPopupButtonMenuRefTag, 
                         sizeof(popupMenu), &popupMenu, NULL);
    if (err == noErr) {
        currentItem = (MenuItemIndex) GetControlValue(control);
        
        err = GetMenuItemRefCon(popupMenu, (SInt16) currentItem, (UInt32 *) &result);
    }
    assert( (err == noErr) && (result != NULL) );
    return result;
}
 
#endif
 
static OSStatus SetTextControlToGlobalString(QISAPanel *panel, WindowRef window, OSType tag, SInt32 id, CFStringRef key)
    // Gets the global value (using QISAGetGlobalValue) for key 
    // and stores the resulting string into the text contrel 
    // described by window, tag, and id.
{
    OSStatus            err;
    ControlRef          control;
    CFPropertyListRef   value;
    
    assert(QISAIsPanelValid(panel));
    assert(window != NULL);
    assert(key != NULL);
    
    err = QISAGetGlobalValue(panel, key, &value);
    if (err == noErr) {
        err = GetControlByIDQ(window, tag, id, &control);
    }
    if (err == noErr) {
        assert( CFGetTypeID(value) == CFStringGetTypeID() );
        err = SetTextControlTextCompat(control, false, (CFStringRef) value);
    }
    return err;
}
 
/////////////////////////////////////////////////////////////////
#pragma mark ***** PortCCL Panel
 
// The PortCCL panel allows the user to choose a modem port 
// and its CCL.  It stores the results as the kQISAKeyChosenPort and 
// kQISAKeyChosenCCL global values.
 
// PortCCLPanelData represents the panel's private data. 
// A reference to this structure is stored in the panel's 
// refCon.
 
static const OSType kPortCCLPanelMagic = 'PCCL';
 
struct PortCCLPanelData {
    OSType              magic;              // must be kPortCCLPanelMagic
    CFArrayRef          portArray;          // of CFDictionary, as returned by QISACreatePortArray
    ControlRef          portPopup;          // reference to the port popup menu control
    MenuRef             portPopupMenu;      // reference to the menu inside that control
    CFArrayRef          cclArray;           // of CFDictionary, as returned by QISACreateCCLArray
    ControlRef          cclPopup;           // reference to the CCL popup menu control
    MenuRef             cclPopupMenu;       // reference to the menu inside that control
    PanelSwitchToProc   oldSwitchTo;        // inherited SwitchTo proc
    PanelSwitchFromProc oldSwitchFrom;      // inherited SwitchFrom proc
};
typedef struct PortCCLPanelData PortCCLPanelData;
 
static PortCCLPanelData *GetPortCCLPanelData(QISAPanel *panel)
    // Returns the panel's private data.
{
    PortCCLPanelData * result;
    
    assert(QISAIsPanelValid(panel));
    result = (PortCCLPanelData *) panel->refCon;
    assert( (result != NULL) && (result->magic == kPortCCLPanelMagic) );
    return result;
}
 
#if 0
 
static void ResetMenuWithCFRefCons(MenuRef menu, CFTypeID refConType)
{
    OSStatus    junk;
    UInt16      menuItemCount;
    SInt16      menuItemIndex;
    
    menuItemCount = CountMenuItems(menu);
    for (menuItemIndex = 1; menuItemIndex <= menuItemCount; menuItemIndex++) {
        CFDictionaryRef thisItemRefCon;
        
        thisItemRefCon = NULL;
        
        junk = GetMenuItemRefCon(menu, menuItemIndex, (UInt32 *) &thisItemRefCon);
        assert(junk == noErr);
        assert(thisItemRefCon != NULL);
        assert( CFGetTypeID(thisItemRefCon) == refConType );
        
        CFQRelease(thisItemRefCon);
    }
 
    junk = DeleteMenuItems(menu, 1, menuItemCount);
    assert(junk == noErr);  
}
 
#endif
 
static void PortCCLPanelTerminate(QISAPanel *panel)
    // See PanelTerminateProc comments.
{
    PortCCLPanelData *panelData;
    
    // Can't call GetPortCCLPanelData because assertions may not hold 
    // during termination if we weren't properly initialised.
    
    panelData =  (PortCCLPanelData *) panel->refCon;
    if (panelData != NULL) {
        CFQRelease(panelData->portArray);
        CFQRelease(panelData->cclArray);
        panelData->magic = 'free';
        free(panelData);
    }
}
 
#if MORE_DEBUG
    
    static void MungePortArray(QISAPanel *panel)
        // This routine adds a dummy modem port to the port array 
        // if the port array is empty.  I added it so that I could test 
        // certain aspects of QISA on a victim machine that does not 
        // have a modem port.
    {
        PortCCLPanelData *  panelData;
 
        panelData = GetPortCCLPanelData(panel);
 
        assert(panelData->portArray != NULL);
        if ( CFArrayGetCount(panelData->portArray) == 0 ) {
            CFStringRef     keys[2];
            CFStringRef     values[2];
            CFDictionaryRef portDict;
            
            CFRelease(panelData->portArray);
            
            keys[0] = kSCPropUserDefinedName;
            values[0] = CFSTR("Hack Port");
            
            keys[1] = kSCPropNetInterfaceHardware;
            values[1] = kSCEntNetModem;
 
            portDict = CFDictionaryCreate(NULL, (const void **) keys, (const void **) values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
            assert(portDict != NULL);
            
            panelData->portArray = CFArrayCreate(NULL, (const void **) &portDict, 1, &kCFTypeArrayCallBacks);
            assert(panelData->portArray != NULL);
        }
    }
 
#endif
 
static OSStatus SetupPortPopup(QISAPanel *panel)
{
    OSStatus            err;
    OSStatus            junk;
    PortCCLPanelData *  panelData;
    CFDictionaryRef     chosenPortDict;
    CFStringRef         chosenPortUserDefinedName;
    MenuItemIndex       chosenPortMenuItemIndex;
    int                 numSerialPorts;
 
    chosenPortMenuItemIndex = 0;
    numSerialPorts = 0;
    
    panelData = GetPortCCLPanelData(panel);
 
    // First get the chosen value, if any, from the global dictionary.
    
    junk = QISAGetGlobalValue(panel, kQISAKeyChosenPort, (CFPropertyListRef *) &chosenPortDict);
    assert( (junk == noErr) == (chosenPortDict != NULL) );
 
    if ( chosenPortDict != NULL ) {
        assert( CFGetTypeID(chosenPortDict) == CFDictionaryGetTypeID() );
        
        chosenPortUserDefinedName = CFDictionaryGetValue(chosenPortDict, kSCPropUserDefinedName);
    } else {
        chosenPortUserDefinedName = NULL;
    }
    assert( (chosenPortUserDefinedName == NULL) || (CFGetTypeID(chosenPortUserDefinedName) == CFStringGetTypeID()) );
 
    // Delete the old menu contents and also get rid of the port array.
    
    junk = DeleteMenuItems(panelData->portPopupMenu, 1, CountMenuItems(panelData->portPopupMenu));
    assert(junk == noErr);
    
    CFQRelease(panelData->portArray);
    panelData->portArray = NULL;
    
    // Now create the popup based on the current port scan.  Note
    // that we don't release panelData->portArray if we get an error, 
    // because we'll release the next time PortCCLPanelSwitchTo calls us, or 
    // when PortCCLPanelTerminate is called.
    
    err = QISACreatePortArray(&panelData->portArray);
    if (err == noErr) {
        CFIndex portCount;
        CFIndex portIndex;
        
        #if 0 && MORE_DEBUG
            MungePortArray(panel);
        #endif
        
        portCount = CFArrayGetCount(panelData->portArray);
        for (portIndex = 0; portIndex < portCount; portIndex++) {
            CFDictionaryRef portDict;
            MenuItemIndex   addedItemIndex;
            
            portDict = CFArrayGetValueAtIndex(panelData->portArray, portIndex);
            assert((portDict != NULL) && (CFGetTypeID(portDict) == CFDictionaryGetTypeID()));
            
            if ( CFEqual(CFDictionaryGetValue(portDict, kSCPropNetInterfaceHardware), kSCEntNetModem) ) {
                CFStringRef thisPortUserDefinedName;
                
                thisPortUserDefinedName = CFDictionaryGetValue(portDict, kSCPropUserDefinedName);
                assert(thisPortUserDefinedName != NULL);
                
                err = AppendMenuItemTextWithCFString(panelData->portPopupMenu, 
                                                     thisPortUserDefinedName, 
                                                     0, 0, &addedItemIndex);
                if (err == noErr) {
                    numSerialPorts += 1;
 
                    if ( (chosenPortUserDefinedName != NULL) && CFEqual(chosenPortUserDefinedName, thisPortUserDefinedName) ) {
                        chosenPortMenuItemIndex = addedItemIndex;
                    }
                }
                if (err != noErr) {
                    break;
                }
            }
        }
    }
    
    // Finally, set the popup value to the chosen port if it's present.
    
    if (err == noErr) {
        assert(numSerialPorts < 32768);
        SetControlMaximum(panelData->portPopup, (short) numSerialPorts);
        assert( (numSerialPorts == 0) || (GetControlValue(panelData->portPopup) == 1) );
        
        if (numSerialPorts == 0) {
            chosenPortMenuItemIndex = 0;
        } else if (chosenPortMenuItemIndex == 0) {
            chosenPortMenuItemIndex = 1;
        }
        SetControlValue(panelData->portPopup, (short) chosenPortMenuItemIndex );
    }
    
    return err;
}
 
static OSStatus SetupCCLPopup(QISAPanel *panel)
{
    OSStatus            err;
    OSStatus            junk;
    PortCCLPanelData *  panelData;
    CFIndex             defaultCCLIndex;
    CFStringRef         chosenCCLName;
    MenuItemIndex       chosenCCLMenuItemIndex;
    int                 numCCLs;
 
    chosenCCLMenuItemIndex = 0;
    numCCLs = 0;
 
    panelData = GetPortCCLPanelData(panel);
 
    // Get the default CCL name, if any, from the global dictionary.
    
    junk = QISAGetGlobalValue(panel, kQISAKeyChosenCCL, (CFPropertyListRef *) &chosenCCLName);
    assert( (junk == noErr) == (chosenCCLName != NULL) );
    assert( (chosenCCLName == NULL) || (CFGetTypeID(chosenCCLName) == CFStringGetTypeID()) );
 
    // Delete the old menu contents and also get rid of the CCL array.
    
    junk = DeleteMenuItems(panelData->cclPopupMenu, 1, CountMenuItems(panelData->cclPopupMenu));
    assert(junk == noErr);
    
    CFQRelease(panelData->cclArray);
    panelData->cclArray = NULL;
 
    // Now create the popup based on the current CCL scan.  Note
    // that we don't release panelData->cclArray if we get an error, 
    // because we'll release the next time PortCCLPanelSwitchTo calls us, or 
    // when PortCCLPanelTerminate is called.
    
    err = QISACreateCCLArray(&panelData->cclArray, &defaultCCLIndex);
    if (err == noErr) {
        // The index returned by QISACreateCCLArray is an index of the 
        // system default CCL in the array, which is 0 based.  However,
        // chosenCCLMenuItemIndex is 1 based, so I have to add one to 
        // defaultCCLIndex.
        //
        // In the case of serial ports, I think it's very unlikely that 
        // you'll have more than 32767 serial ports, so I just assert 
        // that limitation.  However, for CCLs it's feasible that you
        // might have more than 32767, so I give a helpful error 
        // message in that case.
        //
        // This check also guarantees that chosenCCLMenuItemIndex is 
        // in the range [1..32767].
        
        if (err == noErr) {
            if (CFArrayGetCount(panelData->cclArray) < 32768) {
                chosenCCLMenuItemIndex = (MenuItemIndex) (defaultCCLIndex + 1);
            } else {
                err = 5504;
            }
        }
    }
    if (err == noErr) {
        CFIndex cclCount;
        CFIndex cclIndex;
        
        cclCount = CFArrayGetCount(panelData->cclArray);
        for (cclIndex = 0; cclIndex < cclCount; cclIndex++) {
            MenuItemIndex   addedItemIndex;
            CFStringRef     cclStr;
            
            cclStr = CFArrayGetValueAtIndex(panelData->cclArray, cclIndex);
            assert(cclStr != NULL);
            err = AppendMenuItemTextWithCFString(panelData->cclPopupMenu, 
                                                 cclStr,
                                                 0, 0, &addedItemIndex);
            if (err == noErr) {
                numCCLs += 1;
 
                if ( (chosenCCLName != NULL) && CFEqual(chosenCCLName, cclStr) ) {
                    chosenCCLMenuItemIndex = addedItemIndex;
                }
            }
            if (err != noErr) {
                break;
            }
        }
    }
 
    // Finally, set the popup value to the default if it's present.
 
    if (err == noErr) {
        assert( (numCCLs >= 0) && (numCCLs <= 32767) );
        SetControlMaximum(panelData->cclPopup, (short) numCCLs);
        
        assert( chosenCCLMenuItemIndex <= 32767 );
        if (numCCLs == 0) {
            chosenCCLMenuItemIndex = 0;
        } else if (chosenCCLMenuItemIndex == 0) {
            chosenCCLMenuItemIndex = 1;
        }
        SetControlValue(panelData->cclPopup, (short) chosenCCLMenuItemIndex);
    }
    
    return err;
}   
 
static OSStatus PortCCLPanelSwitchTo(QISAPanel *panel)
    // See PanelSwitchToProc comments.
{
    OSStatus            err;
    PortCCLPanelData *  panelData;
    
    panelData = GetPortCCLPanelData(panel);
 
    // Set up port and CCL popup menus.
    
    err = SetupPortPopup(panel);
    if (err == noErr) {
        err = SetupCCLPopup(panel);
    }
 
    // Call through to inherited implementation and then override 
    // the forward button enabled state.  The forward button can 
    // only be enabled if both popups have a selection.
    
    err = panelData->oldSwitchTo(panel);
    if (err == noErr) {
        if (QISAIsButtonEnabled(panel, kQISAPanelSwitchDirectionForward)) {
            QISASetButtonEnable(panel, kQISAPanelSwitchDirectionForward, 
                       (GetControlMaximum(panelData->cclPopup) > 0) 
                    && (GetControlMaximum(panelData->portPopup) > 0)  );
        }
    }
 
 
    return err;
}
 
static OSStatus PortCCLPanelSwitchFrom(QISAPanel *panel, QISAPanelSwitchDirection direction, QISAPanel **nextPanel)
    // See PanelSwitchFromProc comments.
{
    OSStatus            err;
    PortCCLPanelData *  panelData;
    CFIndex             index;
 
    panelData = GetPortCCLPanelData(panel);
    
    // Store the state of the popups as global values.
    
    err = panelData->oldSwitchFrom(panel, direction, nextPanel);
    if (err == noErr && direction == kQISAPanelSwitchDirectionForward) {
        index = GetControlValue(panelData->cclPopup) - 1;
        assert( (index >= 0) && (index < CFArrayGetCount(panelData->cclArray)) );
        
        err = QISASetGlobalValue(panel, kQISAKeyChosenCCL, CFArrayGetValueAtIndex(panelData->cclArray, index));
        if (err == noErr) {
            index = GetControlValue(panelData->portPopup) - 1;
            assert( (index >= 0) && (index < CFArrayGetCount(panelData->portArray)) );
 
            err = QISASetGlobalValue(panel, kQISAKeyChosenPort, CFArrayGetValueAtIndex(panelData->portArray, index));
        }
    }
    return err;
}
 
extern OSStatus  PortCCLPanelInitialise(QISAPanel *panel)
    // See PanelInitialiseProc comments.
{
    OSStatus            err;
    PortCCLPanelData *  panelData;
    
    assert(panel->refCon == NULL);
    
    // Create and initialise panel private data.
    
    err = noErr;
    panelData = (PortCCLPanelData *) calloc( 1, sizeof(*panelData) );
    if (panelData == NULL) {
        err = memFullErr;
    }
    if (err == noErr) {
        err = GetControlByIDQ(panel->window, 'PPOP', 0, &panelData->portPopup);
    }
    if (err == noErr) {
        err = GetControlData(panelData->portPopup, kControlEntireControl, kControlPopupButtonMenuRefTag, 
                             sizeof(panelData->portPopupMenu), &panelData->portPopupMenu, NULL);
    }
    if (err == noErr) {
        err = GetControlByIDQ(panel->window, 'SPOP', 0, &panelData->cclPopup);
    }
    if (err == noErr) {
        err = GetControlData(panelData->cclPopup, kControlEntireControl, kControlPopupButtonMenuRefTag, 
                             sizeof(panelData->cclPopupMenu), &panelData->cclPopupMenu, NULL);
    }
    
    // Clean up.
    
    panel->refCon = panelData;
    if (err == noErr) {
        panelData->magic         = kPortCCLPanelMagic;
        panel->Terminate         = PortCCLPanelTerminate;
        panelData->oldSwitchTo   = panel->SwitchTo;
        panel->SwitchTo          = PortCCLPanelSwitchTo;
        panelData->oldSwitchFrom = panel->SwitchFrom;
        panel->SwitchFrom        = PortCCLPanelSwitchFrom;
        
    } else {
        PortCCLPanelTerminate(panel);
        panel->refCon = NULL;
    }
    assert( (err == noErr) == (panel->refCon != NULL) );
    
    return err;
}
 
/////////////////////////////////////////////////////////////////
#pragma mark ***** User/Pass Panel
 
// The User/Pass panel allows the user to enter a user name, 
// password, and phone number.  It stores the results as 
// kQISAKeyUsername, kQISAKeyPassword, and kQISAKeyNumber 
// global values.
 
// UserPassPanelData represents the panel's private data.
// A reference to this structure is stored in the panel's 
// refCon.
 
static const OSType kUserPassPanelMagic = 'USPW';
 
struct UserPassPanelData {
    OSType              magic;              // must be kUserPassPanelMagic
    ControlRef          userText;           // reference to the user name edit text control
    ControlRef          passText;           // reference to the password edit text control
    ControlRef          numberText;         // reference to the phone number edit text control
    PanelSwitchToProc   oldSwitchTo;        // inherited SwitchTo proc
    PanelSwitchFromProc oldSwitchFrom;      // inherited SwitchFrom proc
    Boolean             forwardAllowed;     // true if the inherited SwitchTo enabled the forward button
};
typedef struct UserPassPanelData UserPassPanelData;
 
static UserPassPanelData *GetUserPassPanelData(QISAPanel *panel)
    // Returns the panel's private data.
{
    UserPassPanelData * result;
    
    assert(QISAIsPanelValid(panel));
    result = (UserPassPanelData *) panel->refCon;
    assert( (result != NULL) && (result->magic == kUserPassPanelMagic) );
    return result;
}
 
static void UserPassPanelTerminate(QISAPanel *panel)
    // See PanelTerminateProc comments.
{
    UserPassPanelData *panelData;
 
    // Can't call GetUserPassPanelData because assertions may not hold 
    // during termination if we weren't properly initialised.
    
    panelData =  (UserPassPanelData *) panel->refCon;
    if (panelData != NULL) {
        panelData->magic = 'free';      
        free(panelData);
    }
}
 
static OSStatus UserPassUpdateForwardButton(QISAPanel *panel)
    // Sets the enabled state of the forward button based on 
    // the values in the text controls.  Called from the SwitchTo 
    // routine and whenever the text in one of the controls changes.
{
    OSStatus            err;
    UserPassPanelData * panelData;
 
    panelData = GetUserPassPanelData(panel);
 
    err = noErr;
    if (panelData->forwardAllowed) {
        Boolean     enabled;
        CFStringRef tmpText;
        
        // Assume the button can be enabled.
        
        enabled = true;
        
        // Get the text for each control, and validate it.  Right now all 
        // I do is check for empty text, but this could be smarter.
        
        tmpText = NULL;
        err = CopyTextControlTextCompat(panelData->userText, false, &tmpText);
        if (err == noErr) {
            if ( CFStringGetLength(tmpText) == 0 ) {
                enabled = false;
            }
        }
        CFQRelease(tmpText);
 
        tmpText = NULL;
        if (err == noErr) {
            err = CopyTextControlTextCompat(panelData->passText, true, &tmpText);
        }
        if (err == noErr) {
            if ( CFStringGetLength(tmpText) == 0 ) {
                enabled = false;
            }
        }
        CFQRelease(tmpText);
        
        tmpText = NULL;
        if (err == noErr) {
            err = CopyTextControlTextCompat(panelData->numberText, false, &tmpText);
        }
        if (err == noErr) {
            if ( CFStringGetLength(tmpText) == 0 ) {
                enabled = false;
            }
        }
        CFQRelease(tmpText);
        
        // Finally, set the enabled state of the button.
        
        QISASetButtonEnable(panel, kQISAPanelSwitchDirectionForward, enabled);
    }
    return err;
}
 
static OSStatus UserPassPanelSwitchTo(QISAPanel *panel)
    // See PanelSwitchToProc comments.
{
    OSStatus            err;
    UserPassPanelData * panelData;
 
    panelData = GetUserPassPanelData(panel);
    
    // Get the inherited state of the forward button and then call 
    // UserPassUpdateForwardButton to set the forward button based on 
    // the content of the text fields.
    
    err = panelData->oldSwitchTo(panel);
    if (err == noErr) {
        panelData->forwardAllowed = QISAIsButtonEnabled(panel, kQISAPanelSwitchDirectionForward);
 
        err = UserPassUpdateForwardButton(panel);
    }
    return err;
}
 
static OSStatus UserPassPanelSwitchFrom(QISAPanel *panel, QISAPanelSwitchDirection direction, QISAPanel **nextPanel)
    // See PanelSwitchFromProc comments.
{
    OSStatus            err;
    UserPassPanelData * panelData;
 
    panelData = GetUserPassPanelData(panel);
    
    // Store the text fields as global values.
 
    err = panelData->oldSwitchFrom(panel, direction, nextPanel);
    if (err == noErr && direction == kQISAPanelSwitchDirectionForward) {
        CFStringRef tmpText;
        
        tmpText = NULL;
        err = CopyTextControlTextCompat(panelData->userText, false, &tmpText);
        if (err == noErr) {
            err = QISASetGlobalValue(panel, kQISAKeyUsername, tmpText);
        }
        CFQRelease(tmpText);
 
        tmpText = NULL;
        if (err == noErr) {
            err = CopyTextControlTextCompat(panelData->passText, true, &tmpText);
        }
        if (err == noErr) {
            err = QISASetGlobalValue(panel, kQISAKeyPassword, tmpText);
        }
        CFQRelease(tmpText);
 
        tmpText = NULL;
        if (err == noErr) {
            err = CopyTextControlTextCompat(panelData->numberText, false, &tmpText);
        }
        if (err == noErr) {
            err = QISASetGlobalValue(panel, kQISAKeyNumber, tmpText);
        }
        CFQRelease(tmpText);
    }
    return err;
}
 
// Blah!  In the Carbon events universe there's no easy way to be told when the 
// contents of a text field changes.  I had to implement a bunch of ugly code 
// to make this work.  Fortunately most of that code is hidden away in 
// MoreControls.  All this module has to do is to declare a Carbon event 
// handler and install it appropriately.
 
static const EventTypeSpec kUserPassRootEvents[1] = { {kMoreIsBetterEventClass, kMIBControlEditTextModifiedKind} };
 
static EventHandlerUPP gUserPassRootEventHandlerUPP;        // -> UserPassRootEventHandler
 
static pascal OSStatus UserPassRootEventHandler(EventHandlerCallRef inHandlerCallRef, 
                                               EventRef inEvent, void *inUserData)
    // This event handler is called whenever the text in one of our 
    // edit text controls is modified.  We respond to that by calling 
    // UserPassUpdateForwardButton to update the enabled state of 
    // the forward button.
{
    OSStatus            junk;
    QISAPanel *         panel;
    #pragma unused(inHandlerCallRef)
    
    panel     = (QISAPanel *) inUserData;
    
    assert( GetEventClass(inEvent) == kMoreIsBetterEventClass        );
    assert( GetEventKind(inEvent)  == kMIBControlEditTextModifiedKind);
    
    junk = UserPassUpdateForwardButton(panel);
    assert(junk == noErr);
    
    // Return eventNotHandledErr to allow other handlers to see this event.
    
    return eventNotHandledErr;
}
 
extern OSStatus  UserPassPanelInitialise(QISAPanel *panel)
    // See PanelInitialiseProc comments.
{
    OSStatus            err;
    UserPassPanelData * panelData;
    
    assert(panel->refCon == NULL);
    
    // Create and initialise panel private data.
 
    err = noErr;
    panelData = (UserPassPanelData *) calloc( 1, sizeof(*panelData) );
    if (panelData == NULL) {
        err = memFullErr;
    }
    if (err == noErr) {
        err = GetControlByIDQ(panel->window, 'USER', 0, &panelData->userText);
    }
    if (err == noErr) {
        err = GetControlByIDQ(panel->window, 'PASS', 0, &panelData->passText);
    }
    if (err == noErr) {
        err = GetControlByIDQ(panel->window, 'NUMB', 0, &panelData->numberText);
    }
 
    // Set the value of each edit text control from global values.
    // After updating the text fields, we don't need to UserPassUpdateForwardButton 
    // because QISA will call our SwitchTo routine before the user has a chance 
    // to click on the button, and UserPassPanelSwitchTo routine will update the 
    // state of the forward button.
    
    if (err == noErr) {
        CFStringRef value;
        
        if ( QISAGetGlobalValue(panel, kQISAKeyUsername, (CFPropertyListRef *) &value) == noErr) {
            assert( CFGetTypeID(value) == CFStringGetTypeID() );
            err = SetTextControlTextCompat(panelData->userText, false, (CFStringRef) value);
        }
    }
    if (err == noErr) {
        CFStringRef value;
 
        if ( QISAGetGlobalValue(panel, kQISAKeyPassword, (CFPropertyListRef *) &value) == noErr) {
            assert( CFGetTypeID(value) == CFStringGetTypeID() );
            err = SetTextControlTextCompat(panelData->passText, true, (CFStringRef) value);
        }
    }
    if (err == noErr) {
        CFStringRef value;
 
        if ( QISAGetGlobalValue(panel, kQISAKeyNumber, (CFPropertyListRef *) &value) == noErr) {
            assert( CFGetTypeID(value) == CFStringGetTypeID() );
            err = SetTextControlTextCompat(panelData->numberText, false, (CFStringRef) value);
        }
    }
    
    // Install the callbacks required to get UserPassRootEventHandler called 
    // whenever the content of our edit text controls change.
    
    if (err == noErr) {
        if (gUserPassRootEventHandlerUPP == NULL) {
            gUserPassRootEventHandlerUPP = NewEventHandlerUPP(UserPassRootEventHandler);
            assert(gUserPassRootEventHandlerUPP != NULL);
        }
 
        err = InstallControlEventHandler(panel->panelControl, gUserPassRootEventHandlerUPP, 
                                      GetEventTypeCount(kUserPassRootEvents), 
                                      kUserPassRootEvents, panel, NULL);
    }
    if (err == noErr) {
        err = InstallControlKeyFilter(panelData->userText, kMIBControlNoReturnsKeyFilterKeyFilterTag, GetControlNoReturnsKeyFilterUPP());
    }
    if (err == noErr) {
        err = InstallControlKeyFilter(panelData->userText, kMIBControlEditTextModifiedKeyFilterTag,   GetControlEditTextModifiedKeyFilterUPP());
    }
    if (err == noErr) {
        err = InstallControlKeyFilter(panelData->passText, kMIBControlNoReturnsKeyFilterKeyFilterTag, GetControlNoReturnsKeyFilterUPP());
    }
    if (err == noErr) {
        err = InstallControlKeyFilter(panelData->passText, kMIBControlEditTextModifiedKeyFilterTag,   GetControlEditTextModifiedKeyFilterUPP());
    }
    if (err == noErr) {
        err = InstallControlKeyFilter(panelData->numberText, kMIBControlNoReturnsKeyFilterKeyFilterTag, GetControlNoReturnsKeyFilterUPP());
    }
    if (err == noErr) {
        err = InstallControlKeyFilter(panelData->numberText, kMIBControlEditTextModifiedKeyFilterTag,   GetControlEditTextModifiedKeyFilterUPP());
    }
    if (err == noErr) {
        ControlEditTextValidationUPP tmp;
        
        tmp = GetControlEditTextModifiedValidationProcUPP();
        err = SetControlData(panelData->userText, kControlEntireControl, kControlEditTextValidationProcTag, sizeof(tmp), &tmp);
        if (err == noErr) {
            err = SetControlData(panelData->passText, kControlEntireControl, kControlEditTextValidationProcTag, sizeof(tmp), &tmp);
        }
        if (err == noErr) {
            err = SetControlData(panelData->numberText, kControlEntireControl, kControlEditTextValidationProcTag, sizeof(tmp), &tmp);
        }
    }
 
    // Clean up.
    
    panel->refCon = panelData;
    if (err == noErr) {
        panelData->magic         = kUserPassPanelMagic;
        panel->Terminate         = UserPassPanelTerminate;
        panelData->oldSwitchTo   = panel->SwitchTo;
        panel->SwitchTo          = UserPassPanelSwitchTo;
        panelData->oldSwitchFrom = panel->SwitchFrom;
        panel->SwitchFrom        = UserPassPanelSwitchFrom;
        
    } else {
        UserPassPanelTerminate(panel);
        panel->refCon = NULL;
    }
    assert( (err == noErr) == (panel->refCon != NULL) );
    
    return err;
}
 
/////////////////////////////////////////////////////////////////
#pragma mark ***** Setup Panel
 
// The Setup panel is where the rubber hits the road.  It is the place 
// where the user actually commits to creating a new network configuration.
 
// In a future world, the Setup panel will be more asynchronous, allowing all 
// sorts of advanced technology (such as dialling the modem using a temporary 
// connection).  Right now the panel is only mildly asynchronous, while we 
// wait for network control panels to quit.  Thus, I only use the states 
// marked with a "*".
 
typedef enum  {
    kSetupStateNotStarted,              // * user can not yet clicked "Do It" button
    kSetupStateWaitingForQuit,          // * waiting for control panels to quit
    kSetupStateCreatingTemporary,       //   creating a temporary configuration to allow the creation of the user account
    kSetupStateConnecting,              //   connecting with that temporary configuration
    kSetupStateConfiguring,             //   creating the user account on the server
    kSetupStateDisconnecting,           //   disconnecting the temporary connection
    kSetupStateCreatingPermanent,       // * creating the user's permanent network settings
    kSetupStateDone                     // * all done
} SetupState;
 
/*  State Transitions
    -----------------
    kSetupStateNotStarted        + "Do It"       +   SetupPanelAnyControlPanelsRunning  -> kSetupStateWaitingForQuit + SetupPanelQuitAllControlPanels + start timer
    kSetupStateNotStarted        + "Do It"       + ! SetupPanelAnyControlPanelsRunning  -> kSetupStateCreatingPermanent
    kSetupStateWaitingForQuit    + timer expires +   SetupPanelAnyControlPanelsRunning  -> do nothing
    kSetupStateWaitingForQuit    + timer expires + ! SetupPanelAnyControlPanelsRunning  -> kSetupStateCreatingPermanent + SetupPanelStopTimer
    kSetupStateWaitingForQuit    + "Stop"                                               -> kSetupStateNotStarted        + SetupPanelStopTimer
    kSetupStateCreatingPermanent                                                        -> kSetupStateDone
*/
 
// SetupPanelData represents the panel's private data.
// A reference to this structure is stored in the panel's 
// refCon.
 
static const OSType kSetupPanelMagic = 'StUp';
 
struct SetupPanelData {
    OSType              magic;              // must be kSetupPanelMagic
    ControlRef          statusText;         // reference to the status static text control
    ControlRef          arrow;              // reference to the chasing arrows control
    ControlRef          doItButton;         // reference to the Do It button control
    ControlRef          stopButton;         // reference to the Cancel button control
    ControlRef          infoButton;         // reference to the Info button control
    PanelSwitchToProc   oldSwitchTo;        // inherited SwitchTo proc
    PanelSwitchFromProc oldSwitchFrom;      // inherited SwitchFrom proc
    Boolean             forwardAllowed;     // true if the inherited SwitchTo enabled the forward button
    SetupState          state;              // current state of the panel
    EventLoopTimerRef   waitQuitTimer;      // -> gSetupPanelWaitQuitTimerUPP -> SetupPanelWaitQuitTimerProc
    CFArrayRef          appsToGo;           // platforms kQISAKeyControlPanels information
    CFStringRef         blockingProcessName;// name of process that we're waiting for; only meaningful in state kSetupStateWaitingForQuit
};
typedef struct SetupPanelData SetupPanelData;
 
static SetupPanelData *GetSetupPanelData(QISAPanel *panel)
    // Returns the panel's private data.
{
    SetupPanelData * result;
    
    assert(QISAIsPanelValid(panel));
    result = (SetupPanelData *) panel->refCon;
    assert( (result != NULL) && (result->magic == kSetupPanelMagic) );
    return result;
}
 
static void SetupPanelStopTimer(SetupPanelData *panelData)
    // Stop the waitQuitTimer.
{
    OSStatus            junk;
 
    assert(panelData != NULL);
    assert(panelData->waitQuitTimer != NULL);
    
    junk = RemoveEventLoopTimer(panelData->waitQuitTimer);
    assert(junk == noErr);
    panelData->waitQuitTimer = NULL;
}
 
static void SetupPanelTerminate(QISAPanel *panel)
    // See PanelTerminateProc comments.
{
    SetupPanelData *panelData;
    
    // Can't call GetSetupPanelData because assertions may not hold 
    // during termination if we weren't properly initialised.
 
    panelData =  (SetupPanelData *) panel->refCon;
    if (panelData != NULL) {
 
        // The timer should not be running because our SetupPanelSwitchFrom must 
        // return successfully before we're terminated, and SetupPanelSwitchFrom 
        // only succeeds if it SetupPanelCommandStop succeeds, and SetupPanelCommandStop 
        // calls SetupPanelStopTimer.  However, in the principle of boots and suspenders, 
        // we do handle this weird case in the non-debug build.  We also have an 
        // assert so that if it even happens in the debug build we hear about it.
        
        assert(panelData->waitQuitTimer == NULL);
        if (panelData->waitQuitTimer != NULL) {
            SetupPanelStopTimer(panelData);
        }
        
        CFQRelease(panelData->appsToGo);
        CFQRelease(panelData->blockingProcessName);
        
        panelData->magic = 'free';      
        free(panelData);
    }
}
 
static OSStatus SetupPanelSwitchTo(QISAPanel *panel)
    // See PanelSwitchToProc comments.
{
    OSStatus            err;
    SetupPanelData *    panelData;
 
    panelData = GetSetupPanelData(panel);
    
    // Call through to inherited implementation and then override 
    // the forward button enabled state.  The forward button can 
    // only be enabled if the state is kSetupStateDone.
 
    assert( (panelData->state == kSetupStateNotStarted) || (panelData->state == kSetupStateDone) );
    err = panelData->oldSwitchTo(panel);
    if (err == noErr) {
        panelData->forwardAllowed = QISAIsButtonEnabled(panel, kQISAPanelSwitchDirectionForward);
 
        QISASetButtonEnable(panel, kQISAPanelSwitchDirectionForward, panelData->forwardAllowed && (panelData->state == kSetupStateDone));
    }
    return err;
}
 
static void SetupPanelCommandStop(QISAPanel *panel);
 
static OSStatus SetupPanelSwitchFrom(QISAPanel *panel, QISAPanelSwitchDirection direction, QISAPanel **nextPanel)
    // See PanelSwitchFromProc comments.
{
    OSStatus            err;
    SetupPanelData *    panelData;
 
    panelData = GetSetupPanelData(panel);
    
    // If we're stepping backwards and the state is not quiescent, then 
    // simulate a click on the Stop button.  If that doesn't quiesce the 
    // state (in a future build this could happen if SetupPanelCommandStop 
    // displays a confirmation alert), then the user canceled and we 
    // return an appropriate error.
 
    err = panelData->oldSwitchFrom(panel, direction, nextPanel);
    if ( (err == noErr) && (direction == kQISAPanelSwitchDirectionBackward) ) {
        if ( (panelData->state != kSetupStateNotStarted) && (panelData->state != kSetupStateDone) ) {
            SetupPanelCommandStop(panel);
        }
        if ( (panelData->state != kSetupStateNotStarted) && (panelData->state != kSetupStateDone) ) {
            err = userCanceledErr;
        }
    }
    return err;
}
 
static void SetupPanelCommandInfo(QISAPanel *panel)
    // Runs the dialog box that comes up when you click the Info button.
{
    OSStatus            err;
    IBNibRef            nibRef;
    WindowRef           infoWindow;
 
    assert(QISAIsPanelValid(panel));
 
    nibRef = NULL;
    infoWindow = NULL;
 
    // Create the dialog from our NIB.
    
    err = CreateNibReference(CFSTR("QISASetupPanel"), &nibRef);
    if (err == noErr) {
        err = CreateWindowFromNib(nibRef, CFSTR("InfoWindow"), &infoWindow);
    }
    if (err == noErr) {
        err = InstallOKCancelWindowEventHandler(infoWindow);
    }
    
    // Fill out the fields.
    
    // Modem Port
 
    if (err == noErr) {
        ControlRef          control;
        CFPropertyListRef   value;
 
        err = QISAGetGlobalValue(panel, kQISAKeyChosenPort, &value);
        if (err == noErr) {
            err = GetControlByIDQ(infoWindow, 'INFO', 0, &control);
        }
        if (err == noErr) {
            assert( CFGetTypeID(value) == CFDictionaryGetTypeID() );
            value = CFDictionaryGetValue( (CFDictionaryRef) value, kSCPropUserDefinedName);
            
            assert( CFGetTypeID(value) == CFStringGetTypeID() );
            err = SetTextControlTextCompat(control, false, (CFStringRef) value);
        }
    }
 
    // Modem Script
 
    if (err == noErr) {
        err = SetTextControlToGlobalString(panel, infoWindow, 'INFO', 1, kQISAKeyChosenCCL);
    }
 
    // Setup User
 
    if (err == noErr) {
        err = SetTextControlToGlobalString(panel, infoWindow, 'INFO', 2, kQISAKeyUsername);
    }
 
    // Setup Password
 
    if (err == noErr) {
        err = SetTextControlToGlobalString(panel, infoWindow, 'INFO', 3, kQISAKeyPassword);
    }
 
    // Setup Number
 
    if (err == noErr) {
        err = SetTextControlToGlobalString(panel, infoWindow, 'INFO', 4, kQISAKeyNumber);
    }
 
    // Setup Use Terminal
 
    if (err == noErr) {
        ControlRef          control;
        CFPropertyListRef   value;
 
        err = QISAGetGlobalValue(panel, kQISAKeyUseTerminal, &value);
        if (err == noErr) {
            err = GetControlByIDQ(infoWindow, 'INFO', 5, &control);
        }
        if (err == noErr) {
            assert( CFGetTypeID(value) == CFBooleanGetTypeID() );
            err = SetTextControlTextCompat(control, false, CFBooleanGetValue( (CFBooleanRef) value) ? CFSTR("TRUE") : CFSTR("FALSE") );
        }
    }
    
    // Run the dialog.
    
    if (err == noErr) {
        ShowWindow(infoWindow);
        
        err = RunAppModalLoopForWindow(infoWindow);
    }
    
    // Clean up.
    
    if (infoWindow != NULL) {
        DisposeWindow(infoWindow);
    }
    if (nibRef != NULL) {
        DisposeNibReference(nibRef);
    }
    
    QISADisplayError(NULL, err, CFSTR("DisplayingTheInformation"));
}
 
static void SetupPanelSyncUIToState(QISAPanel *panel)
    // Synchronised the control in the window to changes in the panel's state.
    // Called whenever the panel changes state (and by the Initialisation proc).
{
    OSStatus            junk;
    SetupPanelData *    panelData;
    SetupState          state;
    Boolean             started;
    Boolean             active;
    CFStringRef         key;
    CFStringRef         stateFormat;
    CFStringRef         stateStr;
 
    panelData = GetSetupPanelData(panel);
 
    state = panelData->state;
    started =  (state != kSetupStateNotStarted);
    active  = ((state != kSetupStateNotStarted) && (state != kSetupStateDone));
 
    SetControlVisible(panelData->arrow,        active );
    SetControlActive( panelData->doItButton, ! started);
    SetControlActive( panelData->stopButton,   active );
 
    // Put a localised description of the state into the statusText.
    
    stateStr = NULL;
    
    key = CFStringCreateWithFormat(NULL, NULL, CFSTR("state%d"), (int) state);
    assert(key != NULL);
    stateFormat = CFBundleCopyLocalizedString(CFBundleGetMainBundle(), key, NULL, NULL);
    if (stateFormat != NULL) {
        stateStr = CFStringCreateWithFormat(NULL, NULL, stateFormat, panelData->blockingProcessName);
        if (stateStr != NULL) {
            junk = SetTextControlTextCompat(panelData->statusText, false, stateStr);
            assert(junk == noErr);
 
            Draw1Control(panelData->statusText);
        }
    }
    CFQRelease(stateFormat);
    CFQRelease(key);
    CFQRelease(stateStr);
 
    // Update the forward button.  You can only go forward if the state is kSetupStateDone, 
    // indicating that we've correctly set up the network.
    
    QISASetButtonEnable(panel, kQISAPanelSwitchDirectionForward, panelData->forwardAllowed && (panelData->state == kSetupStateDone));
}
 
static void SetupPanelCreateNetworkConfig(QISAPanel *panel, Boolean temporary)
    // A sub-routine used to create a network configuration by 
    // calling the platform support routine, QISAMakeNetworkConfig.  
    // The parameters that control the setup are stored in the 
    // global values dictionary.  Right now all clients pass false to 
    // temporary, so we always set up a permanent configuration.
{
    OSStatus                err;
    CFMutableDictionaryRef  configDict;
    SetupPanelData *        panelData;
 
    panelData = GetSetupPanelData(panel);
    
    configDict = NULL;
 
    // Tell the user what we're doing.
    
    if (temporary) {
        panelData->state = kSetupStateCreatingTemporary;
    } else {
        panelData->state = kSetupStateCreatingPermanent;
    }
    SetupPanelSyncUIToState(panel);
 
    // Do it.
    
    err = QISACopyGlobalsDict(panel, &configDict);
    if (err == noErr && temporary) {
        CFDictionaryAddValue(configDict, kQISAKeyTemporary, kCFBooleanTrue);
    }
    // err = -1;
    if (err == noErr) {
        err = QISAMakeNetworkConfig(configDict);
    }
    
    // Tell the user what happened.
    
    if (err == noErr) {
        if (temporary) {
            assert(false);          
            panelData->state = kSetupStateNotStarted;       // will eventually be kSetupStateConnecting, but that state not implemented yet
        } else {
            panelData->state = kSetupStateDone;
        }
    } else {
        panelData->state = kSetupStateNotStarted;
    }
    SetupPanelSyncUIToState(panel);
 
    QISADisplayError(NULL, err, CFSTR("CreatingTheNetworkSettings"));
 
    // Clean up.
    
    CFQRelease(configDict);
}
 
/*  Control Panel Notes
    -------------------
    Network setup can't take place while the network control panel is 
    open.  This is because the control panel is too lame to notice that 
    we've changed the network state via the API, and continues to show 
    obsolete (and confusing) information.  So, we quit the panel before 
    we start setting up the network preferences.
    
    This raises the question, which panel?  Well, the list is different 
    on traditional Mac OS vs Mac OS X.  So, we get the list of panels to 
    quit from the platform, via the global values dictionary.  See 
    kQISAKeyControlPanels for more information about how this is specified.  
    We get that array and store it in panelData->appsToGo (which is an 
    atrocious pun on a very old DTS sample code library).
*/
 
static OSStatus SetupPanelGetControlPanelPSN(QISAPanel *panel, CFIndex appIndex, ProcessSerialNumber *appPSN)
    // Get the process serial number of the appIndex'th network control panel. 
    // The array of control panels, panelData->appsToGo, is provided to us 
    // by the platform.  See "Control Panel Notes" above.  We index into 
    // that array, extra the type and creator, and then search for a process 
    // using that information.  If we find it, we return true and place 
    // its PSN into *appPSN.
{
    OSStatus            err;
    SetupPanelData *    panelData;
    CFDictionaryRef     appDict;
    CFStringRef         appCreatorStr;
    CFStringRef         appTypeStr;
    OSType              appCreator;
    OSType              appType;
    CFIndex             junkCount;
    CFIndex             junkUsedCount;
 
    panelData = GetSetupPanelData(panel);
 
    assert( (appIndex >= 0) && (appIndex < CFArrayGetCount(panelData->appsToGo)) );
    assert(appPSN != NULL);
    
    appDict = (CFDictionaryRef) CFArrayGetValueAtIndex(panelData->appsToGo, appIndex);
    assert( (appDict != NULL) && (CFGetTypeID(appDict) == CFDictionaryGetTypeID()) );
    // CFShow(appDict);
    
    appCreatorStr = (CFStringRef) CFDictionaryGetValue(appDict, kQISAKeyControlPanelsCreator);
    assert( (appCreatorStr != NULL) && (CFGetTypeID(appCreatorStr) == CFStringGetTypeID()) );
    // CFShow(appCreatorStr);
 
    appTypeStr = (CFStringRef) CFDictionaryGetValue(appDict, kQISAKeyControlPanelsType);
    assert( (appTypeStr != NULL) && (CFGetTypeID(appTypeStr) == CFStringGetTypeID()) );
    // CFShow(appTypeStr);
 
    junkCount = CFStringGetBytes(appCreatorStr, CFRangeMake(0, CFStringGetLength(appCreatorStr)), kCFStringEncodingMacRoman, 
                                 0, false, (UInt8 *) &appCreator, sizeof(appCreator), &junkUsedCount);
    assert(junkCount == CFStringGetLength(appCreatorStr));
    assert(junkUsedCount == sizeof(appCreator));
 
    junkCount = CFStringGetBytes(appTypeStr, CFRangeMake(0, CFStringGetLength(appTypeStr)), kCFStringEncodingMacRoman, 
                                 0, false, (UInt8 *) &appType, sizeof(appType), &junkUsedCount);
    assert(junkCount == CFStringGetLength(appTypeStr));
    assert(junkUsedCount == sizeof(appType));
    
    if ( appType == '????' ) {
        err = MoreProcFindProcessByCreator(appCreator, appPSN);
    } else {
        err = MoreProcFindProcessBySignature(appCreator, appType, appPSN);
    }
    
    return err;
}
 
static Boolean SetupPanelAnyControlPanelsRunning(QISAPanel *panel)
    // Returns true if any of the control panels listed in panelData->appsToGo 
    // is running.  If it does return true, it also sets panelData->blockingProcessName 
    // to the name of the first process it found, so SetupPanelSyncUIToState and 
    // SetupPanelAskUserWhetherToQuit can display the name of the process we're 
    // waiting on.
{
    Boolean             result;
    SetupPanelData *    panelData;
    CFIndex             appCount;
    CFIndex             appIndex;
    ProcessSerialNumber blockingPSN;
 
    panelData = GetSetupPanelData(panel);
    
    result = false;
    appCount = CFArrayGetCount(panelData->appsToGo);
    for (appIndex = 0; appIndex < appCount; appIndex++) {
        if (SetupPanelGetControlPanelPSN(panel, appIndex, &blockingPSN) == noErr) {
            result = true;
            break;
        }
    }
    
    // Put the name of the blocking process into 
    // panelData->blockingProcessName, where it'll be picked up by 
    // SetupPanelSyncUIToState and SetupPanelAskUserWhetherToQuit.
    
    if (result) {
        OSStatus    err;
        CFStringRef currentBlockingProcess;
        
        currentBlockingProcess = NULL;
        
        err = CopyProcessName(&blockingPSN, &currentBlockingProcess);
        if ( (err == noErr) 
              && ((panelData->blockingProcessName == NULL) 
                  || ! CFEqual(currentBlockingProcess, panelData->blockingProcessName)) ) {
            CFQRelease(panelData->blockingProcessName);
            panelData->blockingProcessName = NULL;
            
            CFRetain(currentBlockingProcess);
            panelData->blockingProcessName = currentBlockingProcess;
            
            SetupPanelSyncUIToState(panel);
        }
 
        CFQRelease(currentBlockingProcess);
        
        assert(err == noErr);
    }
 
    return result;
}
 
static void SetupPanelQuitAllControlPanels(QISAPanel *panel)
    // Bring the control panels to the front so that the user can see 
    // any dialog that they show.  We only do this once, here, instead 
    // of in the timer, because the timer would do it once a second, 
    // which makes for a very unusable machine.
    //
    // After bringing the control panels to the front, we send them 
    // a quit Apple event.  Again, we do this once when we start the 
    // process, not in the timer (which I originally had).  This avoids 
    // the control panels beeping once per second, as they get a quit Apple 
    // event that they can't handle while they have a modal dialog up.
{
    SetupPanelData *    panelData;
    CFIndex             appCount;
    CFIndex             appIndex;
    ProcessSerialNumber appPSN;
 
    panelData = GetSetupPanelData(panel);
 
    // First go through the list of control panels and bring each to the front.
    // Bring them to front in the reverse order, which means that, when we send 
    // them quit events, the first event goes to the frontmost panel.
    
    appCount = CFArrayGetCount(panelData->appsToGo);
    for (appIndex = 0; appIndex < appCount; appIndex++) {
        if ( SetupPanelGetControlPanelPSN(panel, (appCount - 1) - appIndex, &appPSN) == noErr) {
            (void) SetFrontProcess(&appPSN);
        }
    }
    
    // Now go through the list again and send them quit events.
    
    appCount = CFArrayGetCount(panelData->appsToGo);
    for (appIndex = 0; appIndex < appCount; appIndex++) {
        if ( SetupPanelGetControlPanelPSN(panel, appIndex, &appPSN) == noErr) {
            OSStatus            err;
            AppleEvent          quitEvent;
            AppleEvent          junkReply;
            
            MoreAENullDesc(&quitEvent);
    
            err = MoreAECreateAppleEventProcessTarget(&appPSN, kCoreEventClass, kAEQuitApplication, &quitEvent);
            if (err == noErr) {
                err = AESend(&quitEvent, &junkReply, kAENoReply, kAENormalPriority, kAEDefaultTimeout, NULL, NULL);
            }
            
            MoreAEDisposeDesc(&quitEvent);
            
            assert(err == noErr);
        }
    }
}
 
static EventLoopTimerUPP gSetupPanelWaitQuitTimerUPP;       // -> SetupPanelWaitQuitTimerProc
 
static pascal void SetupPanelWaitQuitTimerProc(EventLoopTimerRef inTimer, void *inUserData)
    // This timer callback is called periodically while we wait for 
    // network control panels to quit.  If we find that the control 
    // panels have quit, we call SetupPanelCreateNetworkConfig to 
    // actually configure the network settings.
    //
    // This is a (very slow) form of polling.  I could eliminate the 
    // polling by watching application termination using the 
    // kEventClassApplication/kEventAppTerminated Carbon event.  
    // I decided not to do that because a) this polling is very slow, 
    // and hence not particularly expensive, and b) by relying on 
    // events, I expose myself to race conditions.  Closing all 
    // of the race conditions would complicate the code somewhat, 
    // and that was more hassle than was justified given the limited 
    // costs of this polling.
{
    OSStatus            err;
    QISAPanel *         panel;
    SetupPanelData *    panelData;
 
    panel = (QISAPanel *) inUserData;
    assert(QISAIsPanelValid(panel));
    
    panelData = GetSetupPanelData(panel);
 
    assert(inTimer == panelData->waitQuitTimer);
 
    if ( SetupPanelAnyControlPanelsRunning(panel) ) {
        // do nothing, waiting for the applications to quit
        err = noErr;
    } else {
        SetupPanelStopTimer(panelData);
        
        SetupPanelCreateNetworkConfig(panel, false);
        err = noErr;
    }
    
    
    assert(err == noErr);
}
 
static OSStatus SetupPanelAskUserWhetherToQuit(QISAPanel *panel)
    // Pose a dialog asking the user whether they want to quit network 
    // preferences applications before continuing with the setup.
    // Returns userCanceledErr if the user doesn't agree.
    // 
    // Note that this includes the name of one of the blocking 
    // applications as an example.  That name comes from 
    // panelData->blockingProcessName, which is set up by 
    // SetupPanelAnyControlPanelsRunning.  This makes sense given that 
    // SetupPanelAnyControlPanelsRunning is called to determine whether 
    // we need to display this dialog.
{
    OSStatus                        err;
    SetupPanelData *                panelData;
    CFStringRef                     errStr;
    CFStringRef                     expStrFormat;
    CFStringRef                     expStr;
    CFStringRef                     defaultStr;
    AlertStdCFStringAlertParamRec   alertParams;
    SInt16                          itemHit;
 
    panelData = GetSetupPanelData(panel);
    
    errStr = NULL;
    expStrFormat = NULL;
    expStr = NULL;
    defaultStr = NULL;
    
    errStr = CFCopyLocalizedString(CFSTR("QuitAppsErrStr"), CFSTR("QuitNetworkPreferencesApplications?"));
    err = CFQError(errStr);
    if (err == noErr) {
        expStrFormat = CFCopyLocalizedString(CFSTR("QuitAppsExpStrFormat"), CFSTR("QISACanNotModifyNetworkSettingsWhileCertainNetworkPreferencesApplications(SuchAs%@)AreRunning"));
        err = CFQError(expStrFormat);
    }
    if (err == noErr) {
        expStr = CFStringCreateWithFormat(NULL, NULL, expStrFormat, panelData->blockingProcessName);
        err = CFQError(expStr);
    }
    if (err == noErr) {
        defaultStr = CFCopyLocalizedString(CFSTR("QuitAppsDefault"), CFSTR("QuitApplications"));
        err = CFQError(defaultStr);
    }
    if (err == noErr) {
        alertParams.version       = kStdCFStringAlertVersionOne;
        alertParams.movable       = true;
        alertParams.helpButton    = false;
        alertParams.defaultText   = defaultStr;
        alertParams.cancelText    = (CFStringRef) kAlertDefaultCancelText;
        alertParams.otherText     = NULL;
        alertParams.defaultButton = kAlertStdAlertOKButton;
        alertParams.cancelButton  = kAlertStdAlertCancelButton;
        alertParams.position      = kWindowDefaultPosition;
        alertParams.flags         = 0;
    
        err = StandardAlertCFStringCompat(kAlertCautionAlert, errStr, expStr, &alertParams, &itemHit);
        if ( (err == noErr) && (itemHit == kAlertStdAlertCancelButton) ) {
            err = userCanceledErr;
        }
    }
    
    CFQRelease(errStr);
    CFQRelease(expStrFormat);
    CFQRelease(expStr);
    CFQRelease(defaultStr);
 
    return err;
}
 
static void SetupPanelCommandDoIt(QISAPanel *panel)
    // Called when the Do It button is clicked.  This routine 
    // creates the network configuration for the user.  There 
    // are two execution paths here.  If no network control 
    // panel applications are running, we simply call 
    // SetupPanelCreateNetworkConfig to do the work and we're 
    // done.  If network control panels are running, we 
    // enter the kSetupStateWaitingForQuit, start a timer 
    // to poll for the applications having been quit, and 
    // send an Apple event to the control panels to tell 
    // them to quit.
{
    OSStatus            err;
    SetupPanelData *    panelData;
 
    panelData = GetSetupPanelData(panel);
 
    err = noErr;
    if ( SetupPanelAnyControlPanelsRunning(panel) ) {
        assert(panelData->waitQuitTimer == NULL);
 
        err = SetupPanelAskUserWhetherToQuit(panel);
        if (err == noErr) {
            if (gSetupPanelWaitQuitTimerUPP == NULL) {
                gSetupPanelWaitQuitTimerUPP = NewEventLoopTimerUPP(SetupPanelWaitQuitTimerProc);
            }
            assert(gSetupPanelWaitQuitTimerUPP != NULL);
            err = InstallEventLoopTimer(GetCurrentEventLoop(), kEventDurationNoWait, kEventDurationSecond, 
                                        gSetupPanelWaitQuitTimerUPP, panel, &panelData->waitQuitTimer);
        }
        if (err == noErr) {
            SetupPanelQuitAllControlPanels(panel);
 
            panelData->state = kSetupStateWaitingForQuit;
            SetupPanelSyncUIToState(panel);
        }
    } else {
        SetupPanelCreateNetworkConfig(panel, false);
    }
    QISADisplayError(panel, err, CFSTR("CreatingTheNetworkSettings"));
}
 
static void SetupPanelCommandStop(QISAPanel *panel)
    // Called when the Stop button is clicked.  This never 
    // happens in the current implementation because 
    // SetupPanelCommandDoIt is entirely synchronous, but I've 
    // left the code here just as an outline of how it will 
    // eventually work.
{
    OSStatus            err;
    SetupPanelData *    panelData;
 
    panelData = GetSetupPanelData(panel);
    
    SetupPanelStopTimer(panelData);
    
    err = noErr;
    panelData->state = kSetupStateNotStarted;
    SetupPanelSyncUIToState(panel);
}
 
static const EventTypeSpec kSetupRootEvents[1] = { {kEventClassCommand, kEventCommandProcess} };
 
static EventHandlerUPP gSetupRootEventHandlerUPP;       // -> SetupRootEventHandler
 
enum {
    kHICommandInfo = 'INFO'
};
 
static pascal OSStatus SetupRootEventHandler(EventHandlerCallRef inHandlerCallRef, 
                                               EventRef inEvent, void *inUserData)
    // This routine is an HICommand handler that we install in our 
    // panel's root control.  It catches the Info, Do It and Stop 
    // HICommands and dispatches them to their appropriate command 
    // handlers.
{
    OSStatus    err;
    HICommand   command;
    QISAPanel * panel;
    #pragma unused(inHandlerCallRef)
    
    panel     = (QISAPanel *) 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 kHICommandInfo:
                SetupPanelCommandInfo(panel);
                break;
            case kHICommandOK:
                // start the connection process
                SetupPanelCommandDoIt(panel);
                break;
            case kHICommandCancel:
                SetupPanelCommandStop(panel);
                break;
            default:
                err = eventNotHandledErr;
                break;
        }
    }
    
    return err;
}
 
extern OSStatus  SetupPanelInitialise(QISAPanel *panel)
    // See PanelInitialiseProc comments.
{
    OSStatus            err;
    SetupPanelData *    panelData;
    
    assert(panel->refCon == NULL);
    
    // Create and initialise panel private data.
 
    err = noErr;
    panelData = (SetupPanelData *) calloc( 1, sizeof(*panelData) );
    if (panelData == NULL) {
        err = memFullErr;
    }
    if (err == noErr) {
        err = GetControlByIDQ(panel->window, 'STAT', 0, &panelData->statusText);
    }
    if (err == noErr) {
        err = GetControlByIDQ(panel->window, 'AROW', 0, &panelData->arrow);
    }
    if (err == noErr) {
        err = GetControlByIDQ(panel->window, 'DOIT', 0, &panelData->doItButton);
    }
    if (err == noErr) {
        err = GetControlByIDQ(panel->window, 'STOP', 0, &panelData->stopButton);
    }
    if (err == noErr) {
        err = GetControlByIDQ(panel->window, 'INFO', 0, &panelData->infoButton);
    }
    if (err == noErr) {
        err = QISAGetGlobalValue(panel, kQISAKeyControlPanels, (CFPropertyListRef *) &panelData->appsToGo);
        if (err == noErr) {
            CFRetain(panelData->appsToGo);
            
            if (CFGetTypeID(panelData->appsToGo) != CFArrayGetTypeID()) {
                err = kCFQDataErr;
            }
        }
    }
 
    // Install our HI command handler.
    
    if (err == noErr) {
        if (gSetupRootEventHandlerUPP == NULL) {
            gSetupRootEventHandlerUPP = NewEventHandlerUPP(SetupRootEventHandler);
            assert(gSetupRootEventHandlerUPP != NULL);
        }
 
        err = InstallControlEventHandler(panel->panelControl, gSetupRootEventHandlerUPP, 
                                      GetEventTypeCount(kSetupRootEvents), 
                                      kSetupRootEvents, panel, NULL);
    }
 
    // Clean up.
    
    panel->refCon = panelData;
    if (err == noErr) {
        panelData->magic         = kSetupPanelMagic;
        panel->Terminate         = SetupPanelTerminate;
        panelData->oldSwitchTo   = panel->SwitchTo;
        panel->SwitchTo          = SetupPanelSwitchTo;
        panelData->oldSwitchFrom = panel->SwitchFrom;
        panel->SwitchFrom        = SetupPanelSwitchFrom;
        panelData->state         = kSetupStateNotStarted;
        SetupPanelSyncUIToState(panel);
    } else {
        SetupPanelTerminate(panel);
        panel->refCon = NULL;
    }
    assert( (err == noErr) == (panel->refCon != NULL) );
    
    return err;
}