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