Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
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, ¤tBlockingProcess); |
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; |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-05-15