/* * WKWizardPanel.m * * Implementation of the WKWizardPanel class for the WizardKit framework * * Copyright (c) 2006, by Saso Kiselkov * * For license details please see the file COPYING included with this * source distribution package. */ #import "WKWizardPanel.h" #import #import #import #import #import #import #import #import /** * Notification sent when the current stage of a WKWizardPanel changes. * The argument dictionary contains a key "Stage" set to the name * of the current stage. */ NSString * const WKWizardPanelDidChangeCurrentStageNotification = @"WKWizardPanelDidChangeCurrentStageNotification"; @interface WKWizardPanel (Private) - (void) setupForStage: (NSString *) aStage; @end /** * A wizard panel class. * * A WKWizardPanel object is an NSPanel object which provides support for * wizard-style operation. General wizard support works like this: * * - when a wizard panel is created, you set it's stages list (using * -[WKWizardPanel setStages:]). The list of stages is a list of * string names of various stages through which the wizard may pass. * Optionally, you may also set it's initial stage (using -[WKWizardPanel * setInitialStage:]) if you want the initial stage to be some stage * other than the first stage. * * - you set a delegate of the wizard panel (using -[NSWindow setDelegate:]), * which must be an object which implements some methods necessary * to supply the wizard panel with information about what it's supposed * to display in each stage. Please consult the * NSObject(WKWizardPanelDelegate) informal protocol for more information * on which methods a wizard panel delegate must and may implement. * * - when events occur in the various stages in the user interface you can * tell the wizard panel to move through the various stages of the wizard * cycle with -[WKWizardPanel goToStage:], -[WKWizardPanel goToNextStage:] * and -[WKWizardPanel goToPreviousStage:]. */ @implementation WKWizardPanel - (void) dealloc { TEST_RELEASE(stages); TEST_RELEASE(initialStage); [super dealloc]; } /** * Sets the stages of the receiver. * * Please note that invoking this method resets the receiver's initial * stage. An NSInternalInconsistencyException is raised in case it is * invoked when the receiver is active. * * @param someStages The new stages of the wizard. */ - (void) setStages: (NSArray *) someStages { if (isActive == YES) { [NSException raise: NSInternalInconsistencyException format: _(@"-[WKWizardPanel setStages:]: panel already " @"active - stages must be set before activating the panel.")]; } ASSIGNCOPY(stages, someStages); DESTROY(initialStage); } /** * Returns a list of stages of the receiver. * * @return The list of stages of the receiver. * * @see [WKWizardPanel setStages:] */ - (NSArray *) stages { return stages; } /** * Sets whether the receiver activates itself in a modal session. * This method raises an NSInternalInconsistencyException in case * it is invoked while the receiver is active. * * @param flag The flag which should be YES if the receiver is to * activate itself in a modal session, or NO if it shouldn't. */ - (void) setRunsInModalSession: (BOOL) flag { if (isActive == YES) { [NSException raise: NSInternalInconsistencyException format: _(@"-[WKWizardPanel setRunsInModalSession:] " @"panel already active - modality must be set before activating " @"the panel.")]; } runsInModalSession = flag; } /** * Returns whether the receiver activates itself in a modal session. * * @return YES if the receiver activates in a modal session, or NO if not. * * @see [WKWizardPanel setRunsInModalSession:] */ - (BOOL) runsInModalSession { return runsInModalSession; } /** * Sets the receiver's initial stage. Raises an NSInvalidArgumentException * in case the provided stage name isn't in the receiver's list of stages. * * @param aStageName The new initial stage of the receiver. */ - (void) setInitialStage: (NSString *) aStageName { if (aStageName != nil && [stages containsObject: aStageName] == NO) { [NSException raise: NSInvalidArgumentException format: _(@"-[WKWizardPanel setInitialStage:]: " @"invalid stage name passed. Stage \"%@\" not in stages list: %@."), aStageName, stages]; } ASSIGN(initialStage, aStageName); } /** * Returns the receiver's initial stage. * * @return The receiver's initial stage. * * @see [WKWizardPanel setInitialStage:] */ - (NSString *) initialStage { return initialStage; } /** * Sets whether the receiver centers itself on the screen before activating. * * @param flag The flag which specifies whether the receiver is to perform * center prior to activation. */ - (void) setCentersBeforeActivating: (BOOL) flag { centersBeforeActivating = flag; } /** * Returns whether the receiver centers itself before activating. * * @return YES if the receiver centers itself before activating, NO otherwise. * * @see [WKWizardPanel setCentersBeforeActivating:] */ - (BOOL) centersBeforeActivating { return centersBeforeActivating; } /** * Makes the receiver display a specified stage. Raises an * NSInvalidArgumentException in case the specified stage isn't contained * in the stages list of the receiver. * * @param aStageName The stage to set. * * @see [WKWizardPanel nextStage:] * @see [WKWizardPanel previousStage:] * @see [WKWizardPanel setStages:] */ - (void) setCurrentStage: (NSString *) aStageName { currentStage = [stages indexOfObject: aStageName]; if (currentStage == NSNotFound) { [NSException raise: NSInvalidArgumentException format: _(@"-[WKWizardPanel setCurrentStage:]: " @"invalid stage name passed. Stage \"%@\" not in stages list: %@."), aStageName, stages]; } [self setupForStage: aStageName]; [[NSNotificationCenter defaultCenter] postNotificationName: WKWizardPanelDidChangeCurrentStageNotification object: self userInfo: [NSDictionary dictionaryWithObject: aStageName forKey: @"Stage"]]; } /** * Returns the current stage which the receiver displays. * * @return The name of the current stage of the receiver. * * @see [WKWizardPanel setCurrentStage:] */ - (NSArray *) currentStage { return [stages objectAtIndex: currentStage]; } /** * Makes the receiver display the next stage. * * @see [WKWizardPanel previousStage:] * @see [WKWizardPanel setCurrentStage:] */ - (void) nextStage: (id) sender { if (currentStage + 1 < [stages count]) { [self setCurrentStage: [stages objectAtIndex: currentStage + 1]]; } } /** * Makes the receiver display the previous stage. * * @see [WKWizardPanel nextStage:] * @see [WKWizardPanel setCurrentStage:] */ - (void) previousStage: (id) sender { if (currentStage > 0) { [self setCurrentStage: [stages objectAtIndex: currentStage - 1]]; } } /** * Makes the panel activate itself. This means that the panel will * become visible and possibly run itself in a modal session (if that * was requested). An NSInternalInconsistencyException is raised in * case the panel is already active. * * @return If activation in a modal session has been requested, this * method returns the code with which the modal session ended. * The code can be controlled by -[WKWizardPanel deactivateWithCode:]. * In case the receiver isn't modal, or the modal session has been * terminated by -[WKWizardPanel deactivate:], this method always * returns NSRunStoppedResponse. * * @see [WKWizardPanel deactivate:] * @see [WKWizardPanel deactivateWithCode:] */ - (int) activate: (id) sender { if (isActive) { [NSException raise: NSInternalInconsistencyException format: _(@"-[WKWizardPanel activate:]: panel " @"already active.")]; } if ([stages count] == 0) { [NSException raise: NSInternalInconsistencyException format: _(@"-[WKWizardPanel activate:]: no stages " @"set. You must set the panel's stages before activating it.")]; } isActive = YES; if (initialStage != nil) { [self setCurrentStage: initialStage]; } else { [self setCurrentStage: [stages objectAtIndex: 0]]; } if (centersBeforeActivating) { [self center]; } if (runsInModalSession) { int code; code = [NSApp runModalForWindow: self]; [self close]; return code; } else { [self makeKeyAndOrderFront: nil]; return NSRunStoppedResponse; } } /** * Makes the receiver deactivate itself. This means that it will close itself * and possibly leave it's modal session (if it was in one). An * NSInternalInconsistencyException is raised in case the panel was not * active. * * In case the panel was run in a modal session, the return code of the * modal session will be NSRunStoppedResponse. * * @see [WKWizardPanel activate] */ - (void) deactivate: (id) sender { if (isActive == NO) { [NSException raise: NSInternalInconsistencyException format: _(@"-[WKWizardPanel deactivate:]: panel " @"not active.")]; } isActive = NO; if (runsInModalSession) { [NSApp stopModal]; } else { [self close]; } } /** * Deactivates the receiver and closes it. This method is suitable only * for use if the receiver was run in a modal session - otherwise use * -[WKWizardPanel deactivate:]. It is different from the previous method * in that it allows you to specify the return code of the modal session * explicitly. * * @param code The code with which the modal session will exit. */ - (void) deactivateWithCode: (int) code { if (isActive == NO) { [NSException raise: NSInternalInconsistencyException format: _(@"-[WKWizardPanel deactivateWithCode:]: " @"panel not active.")]; } if (runsInModalSession == NO) { [NSException raise: NSInternalInconsistencyException format: _(@"-[WKWizardPanel deactivateWithCode]: " @"panel was not run in modal session. Use \"-deactivate:\" to " @"deactivate a non-modal panel instead.")]; } isActive = NO; [NSApp stopModalWithCode: code]; } /** * Returns whether the receiver is active or not. * * @return YES if the receiver is active, NO otherwise. */ - (BOOL) isActive { return isActive; } // NSCoding protocol - (void) encodeWithCoder: (NSCoder*) aCoder { [super encodeWithCoder: aCoder]; if ([aCoder allowsKeyedCoding]) { [aCoder encodeBool: runsInModalSession forKey: @"WKRunsInModalSessionFlag"]; [aCoder encodeBool: runsInModalSession forKey: @"WKCentersBeforeActivatingFlag"]; [aCoder encodeObject: stages forKey: @"WKStages"]; [aCoder encodeObject: initialStage forKey: @"WKInitialStage"]; } else { [aCoder encodeValueOfObjCType: @encode(BOOL) at: &runsInModalSession]; [aCoder encodeValueOfObjCType: @encode(BOOL) at: ¢ersBeforeActivating]; [aCoder encodeObject: stages]; [aCoder encodeObject: initialStage]; } } - (id) initWithCoder: (NSCoder*) aDecoder { if ((self = [super initWithCoder: aDecoder]) != nil) { if ([aDecoder allowsKeyedCoding]) { runsInModalSession = [aDecoder decodeBoolForKey: @"WKRunsInModalSessionFlag"]; runsInModalSession = [aDecoder decodeBoolForKey: @"WKCentersBeforeActivatingFlag"]; ASSIGN(stages, [aDecoder decodeObjectForKey: @"WKStages"]); ASSIGN(initialStage, [aDecoder decodeObjectForKey: @"WKInitialStage"]); } else { [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &runsInModalSession]; [aDecoder decodeValueOfObjCType: @encode(BOOL) at: ¢ersBeforeActivating]; ASSIGN(stages, [aDecoder decodeObject]); ASSIGN(initialStage, [aDecoder decodeObject]); } } return self; } @end /** * Private methods of WKWizardPanel. * * Do not invoke these methods directly. */ @implementation WKWizardPanel (Private) /** * Makes the receiver set it's interface to display a certain stage. * * @param aStage The stage which to display. */ - (void) setupForStage: (NSString *) aStage { [self setContentView: [[self delegate] wizardPanel: self viewForStage: aStage]]; [self setInitialFirstResponder: [[self delegate] wizardPanel: self initialFirstResponderForStage: aStage]]; } @end /** * This informal protocol defines what methods the delegate of a wizard * panel may or must implement to control it's appearance to the user. */ @implementation NSObject (WKWizardPanelDelegate) /** * Requests a wizard panel's delegate to provide a view which will be * put into the panel's main area when in a certain stage. The delegate * object will receive this message for every stage switch, i.e. the * returned view isn't cached, so that the delegate can change the view * which will be used in the same stage. Overriding this method is mandatory. * * @param sender The wizard panel which sent this message. * @param aStageName The stage in which the panel will use the * returned value. * * @return A view object, which will be put into the panel when in the * specified stage. */ - (NSView *) wizardPanel: (WKWizardPanel *) sender viewForStage: (NSString *) aStageName { [NSException raise: NSInternalInconsistencyException format: _(@"Wizard delegate %@ didn't override %@."), [self className], NSStringFromSelector(_cmd)]; return nil; } /** * Requests the wizard panel's delegate to provide a view which * will be set as the panel's initial first responder at the * beginning of a stage. Overriding this method is optional. * * @return A view object which will be set as the panel's initial * first responder. Returning `nil' indicates `no initial first * responder'. */ - (NSView *) wizardPanel: (WKWizardPanel *) sender initialFirstResponderForStage: (NSString *) aStageName { return nil; } @end