A. Source Code

Chapter 3—RadarWatcher

AppController.h


#import <Cocoa/Cocoa.h>
@class PreferenceController;

@interface AppController : NSObject
{
    PreferenceController *preferenceController;
}
- (IBAction)showPreferencePanel:(id)sender;

@end

AppController.m


#import "AppController.h"
#import "PreferenceController.h"

@implementation AppController

+ (void)initialize
{
    // Create the user defaults dictionary
    NSUserDefaults *defaults;
    defaults = [NSUserDefaults standardUserDefaults];

    if ([defaults integerForKey:pkeyInitialized] == 0) {
        // Save our defaults if not already initialized
        [defaults setObject:[NSNumber numberWithInt:1] forKey:pkeyInitialized];
        [defaults setObject:[NSNumber numberWithInt:NSOnState]
            forKey:pkeyOpenNewWindow];
        [defaults setObject:[NSNumber numberWithInt:NSOnState]
            forKey:pkeyPlaySound];
        [defaults setObject:[NSNumber numberWithInt:NSOnState]
            forKey:pkeyShowAlert];
    }
}

- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
{
    return ([[NSUserDefaults standardUserDefaults]
        integerForKey:pkeyOpenNewWindow] == NSOnState);
}

- (IBAction)showPreferencePanel:(id)sender
{
    // Create the PreferenceController if it doesn't already exist
    if (!preferenceController)
        preferenceController = [[PreferenceController alloc] init];

    // Display it
    [preferenceController showWindow:self];
}

- (void)dealloc
{
    // We are done with the PreferenceController, release it
    [preferenceController release];
    [super dealloc];
}

@end

main.m


#import <Cocoa/Cocoa.h>

int main(int argc, const char *argv[])
{
    return NSApplicationMain(argc, argv);
}

MyDocument.h


#import <Cocoa/Cocoa.h>
#import <RadarView.h>

#define kSoundName            @"Ping"
#define kDefaultURL            @"http://type your weather map url here"
#define kDefaultReloadSeconds    180
#define kMinimumReloadSeconds    10
#define kMaximumReloadSeconds    (60 * 60 * 24)

#define kNumColors             5

extern NSString *keyURL;                  // The URL to load
extern NSString *keyReloadSeconds;        // The reload interval in seconds
extern NSString *keyWatchBoxRect;         // The watch box rect
extern NSString *keyColors[kNumColors];   // The colors to watch for
extern NSString *keyIgnoreSinglePixels;   // TRUE to ignore single pixels
                                          // when analyzing image
extern NSString *keyCloseColors;          // TRUE to consider close colors
                                          // the same when analyzing image

@interface MyDocument : NSDocument
{
    NSMutableDictionary *m_md; // MutableDictionary used to read and write data
    NSTimer             *m_timer;       // Timer used to reload image regularly

    IBOutlet RadarView    *m_radarView; // RadarView displays radar and watch box
    IBOutlet NSTextField     *m_statusTextField;     // "Last updated" status text
    IBOutlet NSTextField     *m_urlTextField;        // URL to load
    IBOutlet NSTextField     *m_reloadSecondsTextField; // How many seconds to
                                                        // reload image
    IBOutlet NSButton        *m_startButton;         // Start button
    IBOutlet NSButton        *m_stopButton;          // Stop button
    IBOutlet NSButton        *m_reloadButton;        // Reload button
    IBOutlet NSButton        *m_resetButton; // Reset button (resets colors to
    100%
                                             // opaque - ie: clearColor)

    IBOutlet NSColorWell    *m_colorWell1;   // Color wells of colors to look for
    IBOutlet NSColorWell    *m_colorWell2;
    IBOutlet NSColorWell    *m_colorWell3;
    IBOutlet NSColorWell    *m_colorWell4;
    IBOutlet NSColorWell    *m_colorWell5;

    IBOutlet NSButton *m_ignoreSinglePixelsButton; // Ignore single pixels,
                                                   // only look for married ones
    IBOutlet NSButton *m_closeColorsButton; // Colors that are close to one
                                            // another are considered the same
}

- (void)updateUI;        // Called to update the UI components
- (void)destroyTimer;    // Called to destroy the current NSTimer
                         // before a new one is created

// Display an alert over the document window
- (void)displayAlert:(NSString*)title msg:
    (NSString*)msg defaultButton:(NSString*)defaultButton;

- (IBAction)refreshRadar:(id)sender;    // Called from Reload button
- (IBAction)startRadar:(id)sender;      // Called from Start button
- (IBAction)stopRadar:(id)sender;       // Called from Stop button
- (IBAction)resetColors:(id)sender;     // Called from Reset button
- (IBAction)changeColor:(id)sender;        // Called when a color is
                                           // changed in the color picker
- (IBAction)ignoreSinglePixels:(id)sender; // Called when the Ignore
                                           // Single Pixels checkbox is pressed
- (IBAction)closeColors:(id)sender;        // Called when the
                                           // Close Colors checkbox is pressed
@end

MyDocument.m


#import "MyDocument.h"
#import "PreferenceController.h"

// MutableDictionary keys
NSString *keyURL = @"keyURL";
NSString *keyReloadSeconds = @"keyReloadSeconds";
NSString *keyWatchBoxRect = @"keyWatchBoxRect";
NSString *keyColors[kNumColors] = {@"keyColors1", @"keyColors2",
    @"keyColors3", @"keyColors4", @"keyColors5"};
NSString *keyIgnoreSinglePixels = @"keyIgnoreSinglePixels";
NSString *keyCloseColors = @"keyCloseColors";

@implementation MyDocument

- (id)init
{
    [super init];
    if (self) {

        // Add your subclass-specific initialization here.
        // If an error occurs here, send a [self dealloc]
        // message and return nil.

        // Create the dictionary that contains our documents
        // data with default values
        m_md = [[NSMutableDictionary dictionary] retain];
        if (m_md) {
            [m_md setObject:kDefaultURL forKey:keyURL];
            [m_md setObject:[NSNumber numberWithInt:kDefaultReloadSeconds]
                       forKey:keyReloadSeconds];
            [m_md setObject:NSStringFromRect(NSMakeRect(0,0,0,0))
                forKey:keyWatchBoxRect];
            [m_md setObject:[NSArchiver archivedDataWithRootObject:
                [NSColor clearColor]]
                forKey:keyColors[0]];
            [m_md setObject:[NSArchiver archivedDataWithRootObject:
                [NSColor clearColor]]
                forKey:keyColors[1]];
            [m_md setObject:[NSArchiver archivedDataWithRootObject:
                [NSColor clearColor]]
                forKey:keyColors[2]];
            [m_md setObject:[NSArchiver archivedDataWithRootObject:
                [NSColor clearColor]]
                forKey:keyColors[3]];
            [m_md setObject:[NSArchiver archivedDataWithRootObject:
                [NSColor clearColor]]
                forKey:keyColors[4]];
            [m_md setObject:[NSNumber numberWithInt:NSOffState]
                forKey:keyIgnoreSinglePixels];
            [m_md setObject:[NSNumber numberWithInt:NSOffState]
                forKey:keyCloseColors];
       } else {
            NSRunAlertPanel(@"Error!",
                  @"There was an error allocating our NSMutableDictionary.",
                  @"OK", nil, nil);
            [self dealloc];
            return nil;
        }
    }
    return self;
}

- (void)dealloc {
    // Release the memory used by the dictionary
    [m_md release];
    m_md = nil;

    // Stop the timer
    [self destroyTimer];

    // Psst? BlahBlahBlah! Pass it on.
    [super dealloc];
}

- (NSString *)windowNibName
{
    // Override returning the nib file name of the document
    // If you need to use a subclass of NSWindowController
    // or if your document supports multiple NSWindowControllers,
    // you should remove this method and override –
    // makeWindowControllers instead.
    return @"MyDocument";
}

- (void)windowControllerDidLoadNib:(NSWindowController *) aController
{
    [super windowControllerDidLoadNib:aController];
    // Add any code here that need to be executed once the windowController
    // has loaded the document's window.

    // First time through we need to tell the RadarView who is the boss
    [m_radarView setDocument:self];

    // Update the UI fields
    [self updateUI];

    // First time through we want to blank out the status text
    [m_statusTextField setStringValue:@""];
}

// Validate our Document menu items
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
    NSString *selectorString;
    selectorString = NSStringFromSelector([menuItem action]);

    NSLog(@"validateMenuItem called for %@", selectorString);
    if ([menuItem action] == @selector(refreshRadar:))
        return YES;
    if ([menuItem action] == @selector(startRadar:))
        return (m_timer == nil);
    if ([menuItem action] == @selector(stopRadar:))
        return (m_timer != nil);

    return [super validateMenuItem:menuItem];
}

- (BOOL)readFromFile:(NSString *)fileName ofType:(NSString *)docType
{
    // Stop the current run if we are loading a new file into this document
    [NSApp sendAction:@selector(stopRadar:) to:self from:self];

    // Release the current dictionary
    [m_md release];
    m_md = nil;

    // Load the new data from the file
    m_md = [[NSMutableDictionary dictionaryWithContentsOfFile:fileName]
        retain];
    if (m_md) {
        // Update the UI in case this is a Revert
        [self updateUI];

        // Return a positive result
        return YES;
    }

    return NO; // Failure
}

- (BOOL)writeToFile:(NSString *)fileName ofType:(NSString *)type
{
    // Update data that needs special conversion

    // NSRect are best off saved as string representations
    // so the developer can edit them
    [m_md setObject:NSStringFromRect([m_radarView watchBoxRect])
        forKey:keyWatchBoxRect];

    // NSColor should save but do not, so we must archive
    // and unarchive them to get them to work
    [m_md setObject:[NSArchiver archivedDataWithRootObject:
        [m_colorWell1 color]]
        forKey:keyColors[0]];
    [m_md setObject:[NSArchiver archivedDataWithRootObject:
        [m_colorWell2 color]]
        forKey:keyColors[1]];
    [m_md setObject:[NSArchiver archivedDataWithRootObject:
        [m_colorWell3 color]]
        forKey:keyColors[2]];
    [m_md setObject:[NSArchiver archivedDataWithRootObject:
        [m_colorWell4 color]]
        forKey:keyColors[3]];
    [m_md setObject:[NSArchiver archivedDataWithRootObject:
        [m_colorWell5 color]]
        forKey:keyColors[4]];

    // Retrieve the state of the checkbox
    [m_md setObject:[NSNumber numberWithInt:
        [m_ignoreSinglePixelsButton state]]
        forKey:keyIgnoreSinglePixels];
    [m_md setObject:[NSNumber numberWithInt:[m_closeColorsButton state]]
        forKey:keyCloseColors];

    // Write the current dictionary to the file
    return [m_md writeToFile:fileName atomically:YES];
}

- (void)updateUI
{
    // Update the UI with the data from the latest file or revert action
    [m_urlTextField setStringValue:[m_md objectForKey:keyURL]];
    [m_reloadSecondsTextField setStringValue:
        [m_md objectForKey:keyReloadSeconds]];

    // NSColor should save but do not, so we must archive
    // and unarchive them to get them to work
    [m_colorWell1 setColor:[NSUnarchiver unarchiveObjectWithData:[m_md
               objectForKey:keyColors[0]]]];
    [m_colorWell2 setColor:[NSUnarchiver unarchiveObjectWithData:[m_md
               objectForKey:keyColors[1]]]];
    [m_colorWell3 setColor:[NSUnarchiver unarchiveObjectWithData:[m_md
               objectForKey:keyColors[2]]]];
    [m_colorWell4 setColor:[NSUnarchiver unarchiveObjectWithData:[m_md
               objectForKey:keyColors[3]]]];
    [m_colorWell5 setColor:[NSUnarchiver unarchiveObjectWithData:[m_md
               objectForKey:keyColors[4]]]];

    // Set the state of the checkbox
    [m_ignoreSinglePixelsButton setState:[[m_md objectForKey:
        keyIgnoreSinglePixels] intValue]];
    [m_closeColorsButton setState:[[m_md objectForKey:
        keyCloseColors] intValue]];

    // Tell the radar view what the current watch box
    // rectangle is so it can draw it properly
    [m_radarView setWatchBoxRect:NSRectFromString(
        [m_md objectForKey:keyWatchBoxRect])];
}

- (void)controlTextDidChange:(NSNotification*)aNotification
{
    // This function is called when the user types a character
    // in the URL or Reload Seconds text fields

    // A change has been made to the document
    [self updateChangeCount:NSChangeDone];

    // Update the proper dictionary entry depending on what changed
    id sender = [aNotification object];
    if (sender == m_urlTextField) {
        [m_md setObject:[m_urlTextField stringValue] forKey:keyURL];
    } else if (sender == m_reloadSecondsTextField) {
        [m_md setObject:[NSNumber numberWithInt:
            [m_reloadSecondsTextField intValue]]
            forKey:keyReloadSeconds];
    }
}

- (IBAction)changeColor:(id)sender
{
    // This function is called when the user changes a colorwell

    // A change has been made to the document
    [self updateChangeCount:NSChangeDone];
}

- (IBAction)refreshRadar:(id)sender
{
    // Load the current image from the URL and display it on the screen,
    // releasing any previous image
    // Note we must use this method and not [NSImage initWithContentsOfURL]
    // because it uses the cache
    NSData *data = [[NSURL URLWithString:[m_md objectForKey:keyURL]]
        resourceDataUsingCache:NO];
    NSImage *image = [[NSImage alloc] initWithData:data];

    // If we loaded an image...
    if (image) {

        // Pass it to the RadarView for display
        [m_radarView setImage:image];

        // We no longer need the image,
        // m_radarView has "retained" it at this point
        [image release];

        // Update the current time as the last time we loaded the image
        [m_statusTextField setStringValue:
            [NSString localizedStringWithFormat:@"Map last loaded %@",
            [NSCalendarDate date]]];

        // Check for colors
        BOOL ignoreSinglePixelsState =
            ([m_ignoreSinglePixelsButton state] == NSOnState);
        BOOL closeColorsState = ([m_closeColorsButton state] == NSOnState);
        if (([[m_colorWell1 color] alphaComponent] > 0) &&
            ([m_radarView isColorInImageInRect:[m_colorWell1 color]
            ignoreSinglePixels:ignoreSinglePixelsState
            closeColors:closeColorsState] == YES)) {
                [self displayAlert:@"Found Color 1!"
                    msg:@"A color was found in the watch box."
                    defaultButton:@"OK"];
        }
        if (([[m_colorWell2 color] alphaComponent] > 0) &&
            ([m_radarView isColorInImageInRect:[m_colorWell2 color]
            ignoreSinglePixels:ignoreSinglePixelsState
            closeColors:closeColorsState] == YES)) {
                [self displayAlert:@"Found Color 2!"
                    msg:@"A color was found in the watch box."
                    defaultButton:@"OK"];
        }
        if (([[m_colorWell3 color] alphaComponent] > 0) &&
            ([m_radarView isColorInImageInRect:[m_colorWell3 color]
            ignoreSinglePixels:ignoreSinglePixelsState
            closeColors:closeColorsState] == YES)) {
                [self displayAlert:@"Found Color 3!"
                    msg:@"A color was found in the watch box."
                    defaultButton:@"OK"];
        }
        if (([[m_colorWell4 color] alphaComponent] > 0) &&
            ([m_radarView isColorInImageInRect:[m_colorWell4 color]
            ignoreSinglePixels:ignoreSinglePixelsState
            closeColors:closeColorsState] == YES)) {
                [self displayAlert:@"Found Color 4!"
                    msg:@"A color was found in the watch box."
                    defaultButton:@"OK"];
        }
        if (([[m_colorWell5 color] alphaComponent] > 0) &&
            ([m_radarView isColorInImageInRect:[m_colorWell5 color]
             ignoreSinglePixels:ignoreSinglePixelsState
             closeColors:closeColorsState] == YES)) {
                [self displayAlert:@"Found Color 5!"
                    msg:@"A color was found in the watch box."
                    defaultButton:@"OK"];
        }

        } else {
            NSBeep();
        }
}

// The user pressed the Start button
- (IBAction)startRadar:(id)sender
{
    [m_stopButton setEnabled:TRUE];
    [m_startButton setEnabled:FALSE];
    [m_resetButton setEnabled:FALSE];
    [m_colorWell1 setEnabled:FALSE];
    [m_colorWell2 setEnabled:FALSE];
    [m_colorWell3 setEnabled:FALSE];
    [m_colorWell4 setEnabled:FALSE];
    [m_colorWell5 setEnabled:FALSE];
    [m_urlTextField setEnabled:FALSE];
    [m_reloadSecondsTextField setEnabled:FALSE];
    [m_ignoreSinglePixelsButton setEnabled:FALSE];
    [m_closeColorsButton setEnabled:FALSE];
    [m_radarView setLocked:TRUE];

    // Verify refresh time limits
    if ([[m_md objectForKey:keyReloadSeconds] intValue] <
        kMinimumReloadSeconds) {
            [m_md setObject:[NSNumber numberWithInt:kMinimumReloadSeconds]
                forKey:keyReloadSeconds];
            [m_reloadSecondsTextField setStringValue:
                [m_md objectForKey:keyReloadSeconds]];
            NSRunAlertPanel(@"Error!",
                @"Minimum refresh time is 10 seconds; I fixed it.",
                @"OK", nil, nil);
    }
    if ([[m_md objectForKey:keyReloadSeconds] intValue] >
        kMaximumReloadSeconds) {
            [m_md setObject:[NSNumber numberWithInt:kMaximumReloadSeconds]
                forKey:keyReloadSeconds];

            [m_reloadSecondsTextField setStringValue:
                [m_md objectForKey:keyReloadSeconds]];
            NSRunAlertPanel(@"Error!",
                @"Maximum refresh time is 1 days worth of seconds; I fixed it.",
                @"OK", nil, nil);
    }

    // Stop the timer
    [self destroyTimer];

    // Refresh the image immediately
    [NSApp sendAction:@selector(refreshRadar:) to:self from:self];

    // Set up our timer to execute every keyReloadSeconds seconds
    m_timer = [[NSTimer scheduledTimerWithTimeInterval:
       (NSTimeInterval)[[m_md objectForKey:keyReloadSeconds] doubleValue]
        target:self selector:@selector(refreshRadar:)
        userInfo:nil repeats: YES] retain];
    if (m_timer == nil) {
        NSRunAlertPanel(@"Error!",
            @"There was an error allocating our NSTimer.",
            @"OK", nil, nil);
    }
}

// The user pressed the Stop button
- (IBAction)stopRadar:(id)sender
{
    [m_stopButton setEnabled:FALSE];
    [m_startButton setEnabled:TRUE];
    [m_resetButton setEnabled:TRUE];
    [m_colorWell1 setEnabled:TRUE];
    [m_colorWell2 setEnabled:TRUE];
    [m_colorWell3 setEnabled:TRUE];
    [m_colorWell4 setEnabled:TRUE];
    [m_colorWell5 setEnabled:TRUE];
    [m_urlTextField setEnabled:TRUE];
    [m_reloadSecondsTextField setEnabled:TRUE];
    [m_ignoreSinglePixelsButton setEnabled:TRUE];
    [m_closeColorsButton setEnabled:TRUE];
    [m_radarView setLocked:FALSE];

    // Stop the timer
    [self destroyTimer];
}

// The user pressed the Reset button
- (IBAction)resetColors:(id)sender
{
    int i = NSRunAlertPanel(@"Reset Colors?",
        @"Reset all the colors to their default values?", @"No", @"Yes", nil);
    if (i == NSAlertAlternateReturn) {
        // Reset colors
        [m_colorWell1 setColor:[NSColor clearColor]];
        [m_colorWell2 setColor:[NSColor clearColor]];
        [m_colorWell3 setColor:[NSColor clearColor]];
        [m_colorWell4 setColor:[NSColor clearColor]];
        [m_colorWell5 setColor:[NSColor clearColor]];

        [self updateChangeCount:NSChangeDone];
    }
}

// Called when the Ignore Single Pixels checkbox is pressed
- (IBAction)ignoreSinglePixels:(id)sender
{
    [self updateChangeCount:NSChangeDone];
}

// Called when the Close Colors checkbox is pressed
- (IBAction)closeColors:(id)sender
{
    [self updateChangeCount:NSChangeDone];
}

// Shorthand to destroy the current timer
- (void)destroyTimer
{
    if (m_timer != nil) {
        [m_timer invalidate];
        [m_timer release];
        m_timer = nil;
    }
}

// Display a generic OK alert and play a sound
- (void)displayAlert:(NSString*)title msg:
    (NSString*)msg defaultButton:(NSString*)defaultButton
{
    // Play a sound if our preferences say so
    if ([[NSUserDefaults standardUserDefaults]
        integerForKey:pkeyPlaySound] == NSOnState)
            [[NSSound soundNamed:kSoundName] play];

    // Show an alert no matter what
    if ([[NSUserDefaults standardUserDefaults]
        integerForKey:pkeyShowAlert] == NSOnState)
            NSBeginAlertSheet(title, defaultButton, nil, nil,
            [[[self windowControllers] objectAtIndex:0] window],
            self, NULL, NULL, NULL, msg);
}

@end

PreferenceController.h


#import <AppKit/AppKit.h>

extern NSString *pkeyInitialized;
extern NSString *pkeyOpenNewWindow;
extern NSString *pkeyPlaySound;
extern NSString *pkeyShowAlert;

@interface PreferenceController : NSWindowController {
    IBOutlet NSButton *m_newWindowButton;
    IBOutlet NSButton *m_playSoundButton;
    IBOutlet NSButton *m_showAlertButton;
}
- (IBAction)changeNewWindowButton:(id)sender;
- (IBAction)changePlaySoundButton:(id)sender;
- (IBAction)changeShowAlertButton:(id)sender;

@end

PreferenceController.m


#import "PreferenceController.h"

NSString *pkeyInitialized = @"pkeyInitialized";
NSString *pkeyOpenNewWindow = @"pkeyOpenNewWindow";
NSString *pkeyPlaySound = @"pkeyPlaySound";
NSString *pkeyShowAlert = @"pkeyShowAlert";

@implementation PreferenceController

- (id)init
{
    // Load the Preferences.nib file
    self = [super initWithWindowNibName:@"Preferences"];
    return self;
}

- (void)windowDidLoad
{
    NSUserDefaults *defaults;

    // Load our default values and set the preference controls accordingly
    defaults = [NSUserDefaults standardUserDefaults];
    [m_newWindowButton setState:[defaults integerForKey:pkeyOpenNewWindow]];
    [m_playSoundButton setState:[defaults integerForKey:pkeyPlaySound]];
    [m_showAlertButton setState:[defaults integerForKey:pkeyShowAlert]];
}

- (IBAction)changeNewWindowButton:(id)sender
{
    // Save back the new value of this control to the defaults
    [[NSUserDefaults standardUserDefaults] setInteger:[sender state]
        forKey:pkeyOpenNewWindow];
}

- (IBAction)changePlaySoundButton:(id)sender
{
    // Save back the new value of this control to the defaults
    [[NSUserDefaults standardUserDefaults] setInteger:[sender state]
        forKey:pkeyPlaySound];
}

- (IBAction)changeShowAlertButton:(id)sender
{
    // Save back the new value of this control to the defaults
    [[NSUserDefaults standardUserDefaults] setInteger:[sender state]
        forKey:pkeyShowAlert];
}

@end

RadarView.h


#import <Cocoa/Cocoa.h>

@interface RadarView : NSView
{
    NSImage    *m_image;        // The radar image
    NSRect      m_watchBoxRect; // The watch box
    NSPoint     m_downPoint;    // The last mouse down-point
    NSPoint     m_currentPoint; // The last mouse current-point
    NSDocument *m_document;     // The document that owns us
    BOOL        m_locked;       // TRUE if we should disallow watch box drawing
}

- (BOOL)isColorInImageInRect:(NSColor *)color
    ignoreSinglePixels:(BOOL)ignore
    closeColors:(BOOL)close;
- (void)setDocument:(NSDocument *)document;
- (void)setImage:(NSImage *) newImage;
- (NSImage*)image;
- (void)setWatchBoxRect:(NSRect) rect;
- (NSRect)watchBoxRect;
- (void)setLocked:(BOOL)locked;
- (BOOL)locked;
@end

RadarView.m


#import "RadarView.h"

@implementation RadarView

- (id)initWithFrame:(NSRect)frameRect
{
    [super initWithFrame:frameRect];

    m_image = nil;
    m_watchBoxRect = NSMakeRect(0,0,0,0);
    m_downPoint = NSMakePoint(0, 0);
    m_currentPoint = NSMakePoint(0, 0);
    m_document = nil;
    m_locked = FALSE;

    return self;
}

- (void)dealloc
{
    [m_image release];
    [super dealloc];
}

- (void)mouseDown:(NSEvent *)event
{
    if (m_locked) {
        NSRunAlertPanel(@"Locked!",

        @"Please press the Stop button before attempting to draw the watch box.",
            @"OK", nil, nil);
    } else if (m_image) {
        NSPoint p = [event locationInWindow];
        m_downPoint = [self convertPoint:p fromView:nil];
        m_currentPoint = m_downPoint;
        [self setNeedsDisplay:YES];
        [m_document updateChangeCount:NSChangeDone];
    } else {
        NSRunAlertPanel(@"No Image!",
            @"Please press the Reload button once to load the image before
            attempting to draw the watch box.",
            @"OK", nil, nil);
    }
}

- (void)mouseDragged:(NSEvent *)event
{
    if (m_image && !m_locked) {
        NSPoint p = [event locationInWindow];
        m_currentPoint = [self convertPoint:p fromView:nil];
        [[self superview] autoscroll:event];
        [self setNeedsDisplay:YES];
    }
}

- (void)mouseUp:(NSEvent *)event
{
    if (m_image && !m_locked) {
        NSPoint p = [event locationInWindow];
        m_currentPoint = [self convertPoint:p fromView:nil];
        [self setNeedsDisplay:YES];
    }
}

- (NSRect)currentRect
{
    // Calculate the current watch box rectangle based on the mouse points
    float minX = MIN(m_downPoint.x, m_currentPoint.x);
    float maxX = MAX(m_downPoint.x, m_currentPoint.x);
    float minY = MIN(m_downPoint.y, m_currentPoint.y);
    float maxY = MAX(m_downPoint.y, m_currentPoint.y);
    return NSMakeRect(minX, minY, maxX-minX, maxY-minY);
}

- (void)drawRect:(NSRect)rect
{

    if (m_image) {
        // Resize view to be the size of the image
        // to avoid unnecessary scrolling
        [self setFrameSize:[m_image size]];

        // Draw image
        NSRect bounds = [self bounds];
        NSPoint p = bounds.origin;
        [m_image dissolveToPoint:p fraction:1.0];

        // Draw watch box
        m_watchBoxRect = [self currentRect];
        if (!NSIsEmptyRect(m_watchBoxRect)) {
        #ifdef DRAW_WITH_TINT
            [[[NSColor lightGrayColor] colorWithAlphaComponent:0.2] set];
            NSRectFillUsingOperation(m_watchBoxRect, NSCompositeSourceOver);
        #endif
            [[NSColor grayColor] set];
            NSFrameRectWithWidth(m_watchBoxRect, 2);
            [[NSColor blackColor] set];
            NSFrameRectWithWidth(m_watchBoxRect, 1);
        }

        // Force an update
        [self setNeedsDisplay:YES];
    }
}

// InRange function returns true if a and b are within r of one another
// TODO: implement "wrap" where range is 0-359 and 360=0, for example.
FOUNDATION_STATIC_INLINE BOOL InRange(float a, float b, float r) {
    return (((b <= a+r) && (b >= a-r)) &&
            ((a <= b+r) && (a >= b-r))) ;
}

- (BOOL)isColorInImageInRect:(NSColor *)color
    ignoreSinglePixels:(BOOL)ignore
    closeColors:(BOOL)close
// Walk each pixel in the watch box and see
// if the passed in color exists within it
// ignoreSinglePixels - if TRUE, must be more
// than one pixel of a color in the watch box,
// does not need to be adjacent to each other
// closeColors - if TRUE, colors match if they
// are "close" (hueComponent (0-359) within
// 10 degrees either way, brightnessComponent (0-100) within 20%)
{

    int     x, y;
    long    found = 0;

    // Lock the image focus so we can perform direct pixel reads
    [m_image lockFocus];

    // Update the watch box rect
    m_watchBoxRect = [self currentRect];

    // For each pixel within the watch box rect,
    // see if the color matches the color
    // we passed into this function
    for (x = m_watchBoxRect.origin.x;
         x < m_watchBoxRect.origin.x + m_watchBoxRect.size.width; ++x) {
        for (y = m_watchBoxRect.origin.y;
            y < m_watchBoxRect.origin.y + m_watchBoxRect.size.height;
            ++y) {
                NSColor *newColor = NSReadPixel(NSMakePoint(x, y));
                if (close) {
                    if ((InRange([color hueComponent],
                    [newColor hueComponent], .036)) &&
                    (InRange([color brightnessComponent],
                    [newColor brightnessComponent], .05))) {
                        found++;
                }
            } else {
                if (([color redComponent] == [newColor redComponent]) &&
                    ([color greenComponent] == [newColor greenComponent]) &&
                    ([color blueComponent] == [newColor blueComponent])) {
                    found++;
                }
            }
        }
    }

    // Unlock the image focus and return the result of our search
    [m_image unlockFocus];
    return (ignore ? (found>1) : (found>0));
}

- (void)setDocument:(NSDocument *)document
{
    m_document = document;
}

- (void)setImage:(NSImage *) newImage
{

    [newImage retain];
    [m_image release];
    m_image = newImage;
    [self setNeedsDisplay:YES];
}

- (NSImage*)image
{
    return m_image;
}

- (void)setWatchBoxRect:(NSRect) rect
{
    m_downPoint.x = rect.origin.x;
    m_downPoint.y = rect.origin.y;
    m_currentPoint.x = rect.origin.x + rect.size.width;
    m_currentPoint.y = rect.origin.y + rect.size.height;
    m_watchBoxRect = [self currentRect];
}

- (NSRect)watchBoxRect
{
    return m_watchBoxRect;
}

- (void)setLocked:(BOOL)locked
{
    m_locked = locked;
}

- (BOOL)locked
{
    return m_locked;
}

@end

Chapter 4—MyNSBP_App

MyNSBP_Protocol.h


#import <Cocoa/Cocoa.h>

@protocol MyNSBP_Protocol

+ (BOOL)initializePluginClass:(NSBundle*)theBundle;
+ (void)terminatePluginClass;
+ (id)instantiatePlugin;
- (NSImage*)doPlugin:(NSImage*)theImage;

@end

AppController.h


#import <Cocoa/Cocoa.h>

@interface AppController : NSObject
{
}
@end

AppController.m


#import "AppController.h"

@implementation AppController

- (id)init
{
    [super init];
    if (self) {

    }
    return self;
}

- (void)dealloc
{
    [super dealloc];
}

// Causes the application to quit when the one and only window is closed
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
    return TRUE;
}

@end

ImageWindowController.h


#import <Cocoa/Cocoa.h>

@interface ImageWindowController : NSObject
{
    IBOutlet NSMenuItem     *m_filterMenuItem;
    IBOutlet NSImageView    *m_imageView;

    NSMutableArray    * m_pluginClasses;
    NSMutableArray    * m_pluginInstances;

}
- (IBAction)doFilter:(id)sender;
- (IBAction)doResetImage:(id)sender;
@end

ImageWindowController.m


#import "ImageWindowController.h"
#import "MyNSBP_Protocol.h"

@implementation ImageWindowController

-(id)init
{
    self = [super init];
    if (self) {
        // Allocate our arrays
        m_pluginClasses = [[NSMutableArray alloc] init];
        m_pluginInstances = [[NSMutableArray alloc] init];
    }
    return self;
}

-(void)dealloc
{
    // Release our arrays
    [m_pluginClasses release];
    [m_pluginInstances release];
    [super dealloc];
}

// Called to initialize our fields if we need to –
// this is where we will load our plugins
- (void)awakeFromNib
{

    NSMenu        *filterMenu = [m_filterMenuItem submenu];
    NSString    *folderPath;

    // Locate the plugins directory within our app package
    folderPath = [[NSBundle mainBundle] builtInPlugInsPath];
    if (folderPath) {

        // Enumerate through each plugin
        NSEnumerator    *enumerator =
            [[NSBundle pathsForResourcesOfType:@"plugin"
            inDirectory:folderPath] objectEnumerator];
        NSString        *pluginPath;
        int             count = 0;

        while ((pluginPath = [enumerator nextObject])) {

            // Get the bundle that goes along with the plugin
            NSBundle* pluginBundle = [NSBundle bundleWithPath:pluginPath];
            if (pluginBundle) {

                // Load the plist into a dictionary
                NSDictionary* dictionary = [pluginBundle infoDictionary];

                // Pull useful information from the plist dictionary
                NSString* pluginName =
                    [dictionary objectForKey:@"NSPrincipalClass"];
                NSString* menuItemName =
                    [dictionary objectForKey:@"NSMenuItemName"];

                if (pluginName && menuItemName) {

                    // See if the class is already loaded, if not, load
                    Class pluginClass = NSClassFromString(pluginName);

                    if (!pluginClass) {
                        NSObject<MyNSBP_Protocol>* thePlugin;

                        // The Principal Class of the Bundle is
                        // the plugin class
                        pluginClass = [pluginBundle principalClass];

                        // Make sure it conforms to our protocol
                        // and attempt to initialize and instantiate it
                        if ([pluginClass conformsToProtocol:
                            @protocol(MyNSBP_Protocol)] &&
                            [pluginClass isKindOfClass:[NSObject class]] &&
                            [pluginClass initializePluginClass:pluginBundle] &&
                            (thePlugin = [pluginClass instantiatePlugin])) {

                            // Add a menu item for this plugin
                            NSMenuItem *item;
                            item = [[NSMenuItem alloc] init];
                            [item setTitle:menuItemName];
                            [item setTag:count++];
                            [item setTarget:self];
                            [item setAction:@selector(doFilter:)];
                            [filterMenu addItem:item];
                            [item release];

                            // Add the class to our array
                            [m_pluginClasses addObject:pluginClass];

                            // Add the instance to our array
                            [m_pluginInstances addObject:thePlugin];
                        }
                    }
                }
            }
        }
    }

    // Update our self image
    [self doResetImage:self];
}

// Called when the window is about to close
- (void)windowWillClose:(NSNotification*)notification
{
    Class                     pluginClass;
    NSEnumerator              *enumerator;
    NSObject<MyNSBP_Protocol> *thePlugin;

    // Enumerate through the instances and release each
    enumerator = [m_pluginInstances objectEnumerator];
    while ((thePlugin = [enumerator nextObject])) {
        [thePlugin release];
        thePlugin = nil;
    }

    // Enumerate through the classes and terminate each
    enumerator = [m_pluginClasses objectEnumerator];
    while ((pluginClass = [enumerator nextObject])) {

        [pluginClass terminatePluginClass];
        pluginClass = nil;
    }
}

// Called when one of the plugins is selected from the Filter menu
- (IBAction)doFilter:(id)sender
{
    NSObject<MyNSBP_Protocol>* thePlugin;

    // Get the proper plugin instance from the array based on tag value
    thePlugin = [m_pluginInstances objectAtIndex:[sender tag]];
    if (thePlugin) {
        // Pass the image to the plugin, which will alter it and pass it back
        NSImage *theAlteredImage = [thePlugin doPlugin:[m_imageView image]];

        // Set the altered image to be the current image
        [m_imageView setImage:theAlteredImage];

        // Update the view
        [m_imageView setNeedsDisplay:YES];
    }
}

// Called when the user pressed the Reset Image
// button, restores the image in the view
// We must make a copy otherwise the actual
// picture.tiff tends to get "edited"
- (IBAction)doResetImage:(id)sender
{
    NSImage *theImage = [[[NSImage imageNamed:
        @"picture.tiff"] copy] autorelease];
    [m_imageView setImage:theImage];
//  [m_imageView setNeedsDisplay:YES];
}

@end

main.m


#import <Cocoa/Cocoa.h>

int main(int argc, const char *argv[])
{
    return NSApplicationMain(argc, argv);
}

Chapter 4—MyNSBP_Desaturate

MyNSBP_Desaturate.h


#import <Foundation/Foundation.h>
#import "MyNSBP_Protocol.h"

@interface MyNSBP_Desaturate : NSObject<MyNSBP_Protocol> {

}
@end

MyNSBP_Desaturate.m


#import <Foundation/Foundation.h>
#import "MyNSBP_Desaturate.h"

// Our bundle is kept static so it can be accessed via class methods
static NSBundle* g_pluginBundle = nil;

@implementation MyNSBP_Desaturate

// Initialize
- (id)init
{
    self = [super init];
    return self;
}

// Deallocate
- (void)dealloc
{
    [super dealloc];
}

// Called to initialize our class, currently just to save our bundle
+ (BOOL)initializePluginClass:(NSBundle*)theBundle
{
    if (g_pluginBundle) {
        return NO;
    }
    g_pluginBundle = [theBundle retain];
    return YES;
}

// Called to terminate our class, currently just to release our bundle

+ (void)terminatePluginClass
{
    if (g_pluginBundle) {
        [g_pluginBundle release];
        g_pluginBundle = nil;
    }
}

// Called to instantiate our plugin, currently to alloc, init and load the nib
+ (id)instantiatePlugin
{
    MyNSBP_Desaturate* instance =
        [[[MyNSBP_Desaturate alloc] init] autorelease];
    if (instance && [NSBundle loadNibNamed:@"Desaturate" owner:instance]) {
        return instance;
    }
    return nil;
}

// Do anything here to create UI, etc.
- (void)awakeFromNib
{
}

// Called to alter the image
- (NSImage*)doPlugin:(NSImage*)theImage
{
    NSRect theRect;

    // Calculate theRect of the image
    theRect.origin.x = theRect.origin.y = 0;
    theRect.size = [theImage size];

    // Lock the focus, draw, and unlock
    [theImage lockFocus];
    [[[NSColor lightGrayColor] colorWithAlphaComponent:0.1] set];
    NSRectFillUsingOperation(theRect, NSCompositeSourceOver);
    [theImage unlockFocus];

    // Return the image to the caller
    return theImage;
}

@end

Chapter 4—MyNSBP_RemoveColor

MyNSBP_RemoveColor.h


#import <Foundation/Foundation.h>
#import "MyNSBP_Protocol.h"

@interface MyNSBP_RemoveColor : NSObject<MyNSBP_Protocol> {

}
- (int)askUserWhatColorToRemove;
@end

MyNSBP_RemoveColor.m


#import <Foundation/Foundation.h>
#import "MyNSBP_RemoveColor.h"
#import "SettingsController.h"

// Our bundle is kept static so it can be accessed via class methods
static NSBundle* g_pluginBundle = nil;

@implementation MyNSBP_RemoveColor

// Initialize
- (id)init
{
    self = [super init];
    return self;
}

// Deallocate
- (void)dealloc
{
    [super dealloc];
}

// Called to initialize our class, currently just to save our bundle
+ (BOOL)initializePluginClass:(NSBundle*)theBundle
{
    if (g_pluginBundle) {
        return NO;
    }
    g_pluginBundle = [theBundle retain];
    return YES;
}

// Called to terminate our class, currently just to release our bundle
+ (void)terminatePluginClass
{
    if (g_pluginBundle) {
        [g_pluginBundle release];
        g_pluginBundle = nil;
    }
}

// Called to instantiate our plugin, currently to alloc, init and load the nib
+ (id)instantiatePlugin
{
    MyNSBP_RemoveColor* instance =
        [[[MyNSBP_RemoveColor alloc] init] autorelease];
    if (instance && [NSBundle loadNibNamed:@"RemoveColor" owner:instance]) {
        return instance;
    }
    return nil;
}

// Do anything here to create UI, etc.
- (void)awakeFromNib
{
}

// Ask the user what color to remove
- (int)askUserWhatColorToRemove
{
    int whichColor = 0;

    // Allocate our settings dialog box
    SettingsController    *sc = [[SettingsController alloc] init];

    if (sc) {

        // Run the dialog box modal. The SettingsController will call
        // stopModalWithCode with the tag of the button that was pressed
        // to end the modal loop.
        [sc showWindow:self];
        whichColor = [NSApp runModalForWindow:[sc window]];

        // Deallocate the preference controller, we no longer need it
        [sc release];
        sc = nil;

    }

    // return the chosen color, or 0 (none)
    return whichColor;
}

// Called to alter the image
- (NSImage*)doPlugin:(NSImage*)theImage
{
    int whichColor = [self askUserWhatColorToRemove];

    // If the user chose a color (other than None)
    if (whichColor) {
        NSRect                 theRect;

        // Calculate theRect of the image
        theRect.origin.x = theRect.origin.y = 0;
        theRect.size = [theImage size];

        // Lock the focus, draw, and unlock
        [theImage lockFocus];

        // Set the proper color
        if (whichColor == 1) {        // red
            [[NSColor redColor] set];
        } else if (whichColor == 2) {    // green
            [[NSColor greenColor] set];
        } else if (whichColor == 3) {    // blue
            [[NSColor blueColor] set];
        }

        // Fill the rect with the NSCompositePlusDarker mode to remove the color
        NSRectFillUsingOperation(theRect, NSCompositePlusDarker);

        // Unlock the focus
        [theImage unlockFocus];
    }

    // Return the image to the caller (altered or not)
    return theImage;
}

@end

SettingsController.h


#import <Cocoa/Cocoa.h>

@interface SettingsController : NSWindowController
{
}
- (IBAction)myDoneAction:(id)sender;
@end

SettingsController.m


#import "SettingsController.h"

@implementation SettingsController

- (id)init
{
    // Load the Settings.nib file
    self = [super initWithWindowNibName:@"Settings"];
    return self;
}

// Do stuff here if we need to once the window has loaded
- (void)windowDidLoad
{
}

- (IBAction)myDoneAction:(id)sender
{
    [NSApp stopModalWithCode:[sender tag]];
}

@end

Chapter 4—plistPlugin

AppController.h


#import <Cocoa/Cocoa.h>

@interface AppController : NSObject
{
}
@end

AppController.m


#import "AppController.h"

@implementation AppController

- (id)init
{
    [super init];
    if (self) {

    }
    return self;
}

- (void)dealloc
{
    [super dealloc];
}

// Causes the application to quit when the one and only window is closed
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
    return TRUE;
}

@end

main.m


#import <Cocoa/Cocoa.h>

int main(int argc, const char *argv[])
{
    return NSApplicationMain(argc, argv);
}

SearchWindowController.h


#import <Cocoa/Cocoa.h>

@interface SearchWindowController : NSObject
{
        IBOutlet NSProgressIndicator *m_progressIndicator;
        IBOutlet NSTextView          *m_resultsTextView;
        IBOutlet NSButton            *m_searchButton;
        IBOutlet NSPopUpButton       *m_searchEnginePopUpButton;
        IBOutlet NSTextField         *m_searchTextField;
        NSMutableArray               *m_searchEngineArray;
}
- (IBAction)doSearch:(id)sender;
@end

SearchWindowController.m


#import "SearchWindowController.h"

@implementation SearchWindowController

- (id)init
{
    [super init];
    if (self) {
        m_searchEngineArray = [[NSMutableArray alloc] init];
    }
    return self;
}

- (void)dealloc
{
    [m_searchEngineArray release];
    [super dealloc];
}

// Called to initialize our fields if we need to –
// this is where we will load our plugins
- (void)awakeFromNib
{
    NSString* folderPath;

    // Remove all items from the popup button
    [m_searchEnginePopUpButton removeAllItems];

    // Locate the plugins directory within our app package
    folderPath = [[NSBundle mainBundle] builtInPlugInsPath];
    if (folderPath) {
        // Enumerate through each plugin
        NSEnumerator* enumerator = [[NSBundle pathsForResourcesOfType:@"plist"
                      inDirectory:folderPath] objectEnumerator];
        NSString* pluginPath;
        while ((pluginPath = [enumerator nextObject])) {
            // Load the plist into a dictionary for each plugin
            NSDictionary* dictionary = [NSDictionary
                          dictionaryWithContentsOfFile:pluginPath];
            if (dictionary) {
                // Read the items in the plist
                NSString* searchEngineURL = [dictionary
                                   objectForKey:@"SearchEngineURL"];
                NSString* searchEngineName = [dictionary
                                   objectForKey:@"SearchEngineName"];
                if (searchEngineName && searchEngineURL) {
                    // Add the string the array
                    [m_searchEngineArray addObject:searchEngineURL];
                    // and add a menu item (menu items and
                    // array items will have the same index,
                    // which makes it easy to match them up
                    // when the user presses the search button)
                    [m_searchEnginePopUpButton addItemWithTitle:searchEngineName];
                }
            }
        }
    }
}

// This action is called when the user clicks the Search button
- (IBAction)doSearch:(id)sender
{
    NSString *results;

    // Since the menu items and array items both
    // have the same index, we can easily load
    // the URL based on the menu item that is selected
    NSString *searchEngineURL = [m_searchEngineArray objectAtIndex:
              [m_searchEnginePopUpButton indexOfSelectedItem]];

    // We must then encode the string that the user
    // typed in (ie: replacing spaces with %20)
    NSString *encodedSearchText = (NSString*)
        CFURLCreateStringByAddingPercentEscapes(NULL,
               (CFStringRef)[m_searchTextField stringValue],
        NULL, NULL, kCFStringEncodingUTF8);

    // Once encoded, we concatenate the two strings,
    // the URL and the search text
    NSString *completeSearchURL = [NSString stringWithFormat:@"%@%@",
             searchEngineURL, encodedSearchText];

    // Begin user feedback

    [m_progressIndicator startAnimation:self];
    [m_searchButton setEnabled:NO];

    // We then attempt to load the URL and save the results in a string
    results = [NSString stringWithContentsOfURL:
        [NSURL URLWithString:completeSearchURL]];
    if (results) {
        // If we have results we display them in the text view
        NSRange theFullRange = NSMakeRange(0,
            [[m_resultsTextView string] length]);
        [m_resultsTextView replaceCharactersInRange:
            theFullRange withString:results];
    } else {
        NSRunAlertPanel(@"Error", @"NSString
            stringWithContentsOfURL returned nil",
            @"OK", @"", @"");
    }

    // End user feedback
    [m_searchButton setEnabled:YES];
    [m_progressIndicator stopAnimation:self];
}

@end

Google.plist


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>SearchEngineName</key>
    <string>Google</string>
    <key>SearchEngineURL</key>
    <string>http://www.google.com/search?q=</string>
</dict>
</plist>

Overture.plist


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>SearchEngineName</key>
    <string>Overture</string>
    <key>SearchEngineURL</key>
    <string>http://www.overture.com/d/search/?Keywords=</string>
</dict>
</plist>

Yahoo.plist


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>SearchEngineName</key>
    <string>Yahoo</string>
    <key>SearchEngineURL</key>
    <string>http://google.yahoo.com/bin/query?p=</string>
</dict>
</plist>

Chapter 5—CFPlugin

main.c


#include <Carbon/Carbon.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFPlugin.h>
#include <CoreFoundation/CFPlugInCOM.h>
#include "../MyCFPluginInterface.h"

// The layout for an instance of MyType
typedef struct _MyType {
    MyInterfaceStruct*    _myInterface;
    CFUUIDRef             _factoryID;
    UInt32                 _refCount;
} MyType;

//
// Forward declarations for all functions
//

// IUnknown functions
static HRESULT myQueryInterface(void *this, REFIID iid, LPVOID *ppv);
static ULONG myAddRef(void *this);
static ULONG myRelease(void *this);

// My interface functions
static void myPluginFunction(void *this, Boolean flag);

// Utility functions
static MyType *_allocMyType(CFUUIDRef factoryID);
static void _deallocMyType(MyType *this);

// My factory function
void *myFactoryFunction(CFAllocatorRef allocator, CFUUIDRef typeID);

//
// IUnknown interface functions
//

// Implementation of the IUnknown QueryInterface function
static HRESULT myQueryInterface(void *this, REFIID iid, LPVOID *ppv)
{
    HRESULT hResult = E_NOINTERFACE;

    printf("myQueryInterface ");

    // Create a CoreFoundation UUIDRef for the requested interface
    CFUUIDRef interfaceID = CFUUIDCreateFromUUIDBytes(NULL, iid);

    // Test the requested ID against the valid interfaces
    if (CFEqual(interfaceID, kMyInterfaceID)) {

        // If the MyInterface was requested, bump the ref count,
        // set the ppv parameter equal to the instance, and return good status
        // This calls through to myAddRef
        ((MyType*)this)->_myInterface->AddRef(this);
        *ppv = this;
        hResult = S_OK;

    } else if (CFEqual(interfaceID, IUnknownUUID)) {

        // If the IUnknown interface was requested, bump the ref count,
        // set the ppv parameter equal to the instance, and return good status
        // This calls through to myAddRef
        ((MyType*)this)->_myInterface->AddRef(this);
        *ppv = this;
        hResult = S_OK;

    } else {

        // Requested interface unknown, bail with error
        *ppv = NULL;
        hResult = E_NOINTERFACE;

    }

    // Release interface
    CFRelease(interfaceID);
    return hResult;
}


// Implementation of reference counting for this type
// Whenever an interface is requested, bump the refCount for
// the instance NOTE: returning the refcount is a convention
// but is not required so don't rely on it
static ULONG myAddRef(void *this)
{
    printf("myAddRef ");

    return ((MyType*)this)->_refCount++;
}


// When an interface is released, decrement the refCount
// If the refCount goes to zero, deallocate the instance
static ULONG myRelease(void *this)
{
    printf("myRelease ");

    ((MyType*)this)->_refCount—;
        if (((MyType*)this)->_refCount == 0) {
            _deallocMyType((MyType*)this);
            return 0;
        } else
            return ((MyType*)this)->_refCount;
    }

//
// Functions specific to my plugin
//

// The implementation of the MyInterface function
static void myPluginFunction(void *this, Boolean flag)
{
    SInt16 outItemHit;
    StandardAlert(kAlertNoteAlert,
        flag ? "pCFPlugin (flag = YES)" : "pCFPlugin (flag = NO)",
        "pThis alert is being called from myPluginFunction in CFPlugin.",
        NULL, &outItemHit);
}

//
// Static definition of the MyInterface function table
//

// The MyInterface function table
static MyInterfaceStruct myInterfaceFtbl = {
        NULL,               // Required padding for COM
        myQueryInterface,   // These three are the required COM functions
        myAddRef,
        myRelease,
        myPluginFunction }; // Interface implementation (specific to my plugin)


//
// Utility functions for allocation and deallocation
//

// Utility function that allocates a new instance
static MyType *_allocMyType(CFUUIDRef factoryID)
{
    // Allocate memory for the new instance
    MyType *newOne = (MyType*)malloc(sizeof(MyType));

    // Point to the function table
    newOne->_myInterface = &myInterfaceFtbl;

    // Retain and keep an open instance refcount for each factory
    newOne->_factoryID = CFRetain(factoryID);
    CFPlugInAddInstanceForFactory(factoryID);

    // This function returns the IUnknown interface so set the refCount to one
    newOne->_refCount = 1;
    return newOne;
}

// Utility function that deallocates the instance
// when the refCount goes to zero
static void _deallocMyType(MyType *this)
{
    CFUUIDRef factoryID = this->_factoryID;
    free(this);
    if (factoryID) {
        CFPlugInRemoveInstanceForFactory(factoryID);
        CFRelease(factoryID);
    }
}

//
// Factory function
//

// Implementation of the factory function for this type
void *myFactoryFunction(CFAllocatorRef allocator, CFUUIDRef typeID)
{
    printf("myFactoryFunction ");

    // If correct type is being requested,
    // allocate an instance of MyType and return the IUnknown interface
    if (CFEqual(typeID, kMyTypeID)) {
        MyType *result = _allocMyType(kMyFactoryID);
        return result;
    } else {
        // If the requested type is incorrect, return NULL
        return NULL;
    }
}

Chapter 5—CFPluginCarbonApp

main.c


#include <Carbon/Carbon.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFPlugin.h>
#include <CoreFoundation/CFPlugInCOM.h>
#include "../MyCFPluginInterface.h"
#include "../MyCFCallPlugin.h"

int main(int argc, char* argv[])
{
    IBNibRef          nibRef;
    WindowRef         window;
    OSStatus          err;

    // Create a Nib reference passing the name of the nib file
    // (without the .nib extension)
    // CreateNibReference only searches in the application bundle.
    err = CreateNibReference(CFSTR("main"), &nibRef);
    require_noerr(err, CantGetNibRef);

    // Once the nib reference is created, set the menu bar.
    // "MainMenu" is the name of the menu bar object.
    // This name is set in InterfaceBuilder when the nib is created.
    err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar"));
    require_noerr(err, CantSetMenuBar);

    // Then create a window.
    // "MainWindow" is the name of the window object.
    // This name is set in InterfaceBuilder when the nib is created.
    err = CreateWindowFromNib(nibRef, CFSTR("MainWindow"), &window);
    require_noerr(err, CantCreateWindow);

    // We don't need the nib reference anymore.
    DisposeNibReference(nibRef);

    // The window was created hidden so show it.
    ShowWindow(window);

    // Call the plugin
    CallPlugin();

    // Call the event loop
    RunApplicationEventLoop();

CantCreateWindow:
CantSetMenuBar:
CantGetNibRef:
    return err;
}

Chapter 5—CFPluginCocoaApp

AppController.h


#import <Cocoa/Cocoa.h>

@interface AppController : NSObject
{
}
- (IBAction)doCallPlugin:(id)sender;
@end

AppController.m


#import "AppController.h"
#import "../MyCFPluginInterface.h"
#import "../MyCFCallPlugin.h"

@implementation AppController

- (id)init
{
    [super init];
    if (self) {

    }
    return self;
}

- (void)dealloc
{
    [super dealloc];
}

// Causes the application to quit when the one and only window is closed
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
    return TRUE;
}

- (void)awakeFromNib
{
//    CallPlugin();
}

- (IBAction)doCallPlugin:(id)sender
{
    CallPlugin();
}

@end

main.m


#import <Cocoa/Cocoa.h>

int main(int argc, const char *argv[])
{
    return NSApplicationMain(argc, argv);
}

Chapter 5—Shared

MyCFCallPlugin.c


#include <Carbon/Carbon.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFPlugin.h>
#include <CoreFoundation/CFPlugInCOM.h>
#include "MyCFPluginInterface.h"
#include "MyCFCallPlugin.h"

void CallPlugin(void)
{

    // Create a URL that points to the plug-in using a hard-coded path
    // (You will need to change this to point to the plugin)
    CFURLRef url = CFURLCreateWithFileSystemPath(NULL,
        CFSTR("/Users/zobkiw/Documents/OSXBook/_SOURCE_/5. Carbon
                       Plugins/CFPlugin/build/CFPlugin.plugin"),
        kCFURLPOSIXPathStyle, TRUE);

    // Create a CFPlugin using the URL
    // This step causes the plug-in's types and
    // factories to be registered with the system
    // Note that the plug-in's code is not loaded
    // unless the plug-in is using dynamic registration
    CFPlugInRef plugin = CFPlugInCreate(NULL, url);
    if (plugin) {
        // See if this plug-in implements the "My" type
        CFArrayRef factories = CFPlugInFindFactoriesForPlugInType(kMyTypeID);

        // If there are factories for the requested type,
        // attempt to get the IUnknown interface
        if ((factories != NULL) && (CFArrayGetCount(factories) > 0)) {

            // Get the factory ID for the first location in the array of IDs
            CFUUIDRef factoryID = CFArrayGetValueAtIndex(factories, 0);

            // Use the factory ID to get an IUnknown interface
            // Here the code for the PlugIn is loaded
            // IUnknownVTbl is a struct containing the IUNKNOWN_C_GUTS
            // CFPlugin::MyFactoryFunction is called here
            IUnknownVTbl **iunknown =
                CFPlugInInstanceCreate(NULL, factoryID, kMyTypeID);

            // If this is an IUnknown interface, query for the "My" interface
            if (iunknown) {
                MyInterfaceStruct **interface = NULL;

                // CFPlugin::myQueryInterface is called here
                // CFPlugin::myAddRef is called here
                (*iunknown)->QueryInterface(iunknown,
                            CFUUIDGetUUIDBytes(kMyInterfaceID),
                            (LPVOID *)(&interface));

                // Done with IUnknown
                // CFPlugin::myRelease is called here
                (*iunknown)->Release(iunknown);

                // If this is a "My" interface, try to call its function
                if (interface) {
                    (*interface)->myPluginFunction(interface, TRUE);
                    (*interface)->myPluginFunction(interface, FALSE);

                    // Done with interface
                    // This causes the plug-in's code to be unloaded
                    // CFPlugin::myRelease is called here
                    (*interface)->Release(interface);
                } else printf( "Failed to get interface. " );
            } else printf( "Failed to create instance. " );
        } else  printf( "Could not find any factories. " );

        // Release the CFPlugin memory
        CFRelease(plugin);

    } else printf( "Could not create CFPluginRef. " );

}

MyCFCallPlugin.h


void CallPlugin(void);

MyCFPluginInterface.h


/*
TYPE
DC83B5C8-DD18-11D6-B3D0-0003930EDB36
0xDC, 0x83, 0xB5, 0xC8, 0xDD, 0x18, 0x11, 0xD6,
0xB3, 0xD0, 0x00, 0x03, 0x93, 0x0E, 0xDB, 0x36

FACTORY
DC3F0D89-DD19-11D6-B3D0-0003930EDB36
0xDC, 0x3F, 0x0D, 0x89, 0xDD, 0x19, 0x11, 0xD6,
0xB3, 0xD0, 0x00, 0x03, 0x93, 0x0E, 0xDB, 0x36

INTERFACE
071D881D-DD1A-11D6-B3D0-0003930EDB36
0x07, 0x1D, 0x88, 0x1D, 0xDD, 0x1A, 0x11, 0xD6,
0xB3, 0xD0, 0x00, 0x03, 0x93, 0x0E, 0xDB, 0x36
*/

//#include <Carbon/Carbon.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFPlugin.h>
#include <CoreFoundation/CFPlugInCOM.h>

// Define the UUID for the type
#define kMyTypeID (CFUUIDGetConstantUUIDWithBytes(
NULL, 0xDC, 0x83, 0xB5, 0xC8, 0xDD, 0x18,
0x11, 0xD6, 0xB3, 0xD0, 0x00, 0x03, 0x93, 0x0E, 0xDB, 0x36))

// The UUID for the factory function
#define kMyFactoryID (CFUUIDGetConstantUUIDWithBytes(
NULL, 0xDC, 0x3F, 0x0D, 0x89, 0xDD, 0x19,
0x11, 0xD6, 0xB3, 0xD0, 0x00, 0x03, 0x93, 0x0E, 0xDB, 0x36))

// Define the UUID for the interface
// MyType objects must implement MyInterface
#define kMyInterfaceID (CFUUIDGetConstantUUIDWithBytes(
NULL, 0x07, 0x1D, 0x88, 0x1D, 0xDD, 0x1A,
0x11, 0xD6, 0xB3, 0xD0, 0x00, 0x03, 0x93, 0x0E, 0xDB, 0x36))

// The function table for the interface
typedef struct MyInterfaceStruct {
    IUNKNOWN_C_GUTS;
    void (*myPluginFunction)(void *this, Boolean flag);
} MyInterfaceStruct;

Chapter 6—MyCarbonFramework

MyCarbonFramework.c


#include "MyCarbonFramework.h"

int doAddition(int a, int b)
{
    return a+b;
}

MyCarbonFramework.h


#include <Carbon/Carbon.h>

int doAddition(int a, int b);

Chapter 6—MyCocoaFramework

MyCocoaFramework.h


#import <Cocoa/Cocoa.h>

@interface MyCocoaObject : NSObject
{
}
+ (int)doAddition:(int)a plus:(int)b;
@end

MyCocoaFramework.m


#import "MyCocoaFramework.h"

@implementation MyCocoaObject

+ (int)doAddition:(int)a plus:(int)b
{
    return a+b;
}

@end

Chapter 6—MyCocoaApp

AppController.h


#import <Cocoa/Cocoa.h>

@interface AppController : NSObject
{
    IBOutlet NSTextField *m_a;
    IBOutlet NSTextField *m_b;
}
-(IBAction)doCarbon:(id)sender;
-(IBAction)doCocoa:(id)sender;
@end

AppController.m


#import "AppController.h"
#import "../MyCocoaFramework/MyCocoaFramework.h"
#import "../MyCarbonFramework/MyCarbonFramework.h"

@implementation AppController

- (void)awakeFromNib
{
    [m_a setIntValue:7];
    [m_b setIntValue:3];
}

-(IBAction)doCarbon:(id)sender
{
    if (NSRunAlertPanel(@"Carbon", @"Call the Carbon framework?",
             @"OK", @"Cancel", @"") == NSAlertDefaultReturn) {
        int result = doAddition([m_a intValue], [m_b intValue]);
        NSRunAlertPanel(@"Carbon Result",
            @"The result of the computation is %d",
            @"OK", @"", @"", result);
    }
}

-(IBAction)doCocoa:(id)sender
{
    if (NSRunAlertPanel(@"Cocoa", @"Call the Cocoa framework?",
              @"OK", @"Cancel", @"") == NSAlertDefaultReturn) {
        int result = [MyCocoaObject doAddition:
            [m_a intValue] plus:[m_b intValue]];
        NSRunAlertPanel(@"Cocoa Result",
            @"The result of the computation is %d",
            @"OK", @"", @"", result);
    }
}

// Causes the application to quit when the one and only window is closed
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
    return TRUE;
}

@end

main.m


#import <Cocoa/Cocoa.h>

int main(int argc, const char *argv[])
{
    return NSApplicationMain(argc, argv);
}

Chapter 7—MyTextService

main.m


#import <Foundation/Foundation.h>
#import "MyTextService.h"

int main (int argc, const char *argv[]) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    // Allocate and initialize our service provider
    MyTextService *serviceProvider = [[MyTextService alloc] init];

    // Register the service provider as such
    NSRegisterServicesProvider(serviceProvider, @"MyTextService");

    NS_DURING
        // Configure this application to run as a server
        [[NSRunLoop currentRunLoop] configureAsServer];

        // Run the application, use runUntilDate to make your
        // application auto-quit
        [[NSRunLoop currentRunLoop] run];
    NS_HANDLER
        NSLog(@"%@", localException);
    NS_ENDHANDLER

    // Release the service provider
    [serviceProvider release];

    [pool release];

    exit(0);       // insure the process exit status is 0
    return 0;      // ...and make main fit the ANSI spec.
}

MyTextService.h


#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>

@interface MyTextService : NSObject {

}
- (void)changeCase:(NSPasteboard *)pboard
            userData:(NSString *)userData
            error:(NSString **)error;
@end

MyTextService.m


#import "MyTextService.h"


@implementation MyTextService

- (void)changeCase:(NSPasteboard *)pboard
            userData:(NSString *)userData
            error:(NSString **)error
{
    NSString *pboardString;
    NSString *newString;
    NSArray *types;
    Boolean success;

    // Verify the types currently on the pasteboard
    types = [pboard types];
    if (![types containsObject:NSStringPboardType]) {
        *error = NSLocalizedString(@"Error: couldn't edit text.",
            @"pboard doesn't have a string.");
        return;
    }
    pboardString = [pboard stringForType:NSStringPboardType];
    if (!pboardString) {
        *error = NSLocalizedString(@"Error: couldn't edit text.",
            @"pboard couldn't give string.");
        return;
    }

    // Compare the mode so we do the correct thing
    if ([userData isEqualToString:@"upper"]) {
        newString = [pboardString uppercaseString];
    } else if ([userData isEqualToString:@"lower"]) {
        newString = [pboardString lowercaseString];
    } else if ([userData isEqualToString:@"cap"]) {
        newString = [pboardString capitalizedString];
        }
    if (!newString) {
        *error = NSLocalizedString(@"Error: couldn't edit text.",
            @"pboardString couldn't edit letters.");
        return;
    }
    types = [NSArray arrayWithObject:NSStringPboardType];

    // Load the value of the checkbox from the Preference Pane example
    // Force a synchronization so we get the latest "live" preference
    success = CFPreferencesAppSynchronize
        (CFSTR("com.triplesoft.mypreferencepane"));
    if (success && CFPreferencesGetAppBooleanValue
        (CFSTR("Boolean Value Key"),
        CFSTR("com.triplesoft.mypreferencepane"), NULL)) {
            // Use [NSPasteboard generalPasteboard] instead of pboard
            // to send the results to the clipboard
            [[NSPasteboard generalPasteboard] declareTypes:types owner:nil];
            [[NSPasteboard generalPasteboard] setString:newString
                forType:NSStringPboardType];
    } else {
        // Send the results directly back to the app who
        // requested it if set or if key does not exist
        [pboard declareTypes:types owner:nil];
        [pboard setString:newString forType:NSStringPboardType];
    }

    return;
}

@end

Chapter 8—MyPreferencePane

MyPreferencePanePref.h


#import <PreferencePanes/PreferencePanes.h>


@interface MyPreferencePanePref : NSPreferencePane
{
    CFStringRef m_appID;       // application ID (this application)

    // Tab 1
    IBOutlet    NSButton       *m_checkBox;
    IBOutlet    NSTextField    *m_textField;

    // Tab 2
    IBOutlet    NSButton       *m_pushButton;
}

- (IBAction)checkboxClicked:(id)sender;
- (IBAction)buttonClicked:(id)sender;

@end

MyPreferencePanePref.m


#import "MyPreferencePanePref.h"


@implementation MyPreferencePanePref

// This function is called as we are being initialized
- (id)initWithBundle:(NSBundle *)bundle
{
    // Initialize the location of our preferences
    if ((self = [super initWithBundle:bundle]) != nil) {
        m_appID = CFSTR("com.triplesoft.mypreferencepane");
    }

    return self;
}

// This function is called once the Nib is loaded
// and our windows are ready to be initialized.
- (void)mainViewDidLoad
{

    CFPropertyListRef value;

    // Load the value of the checkbox as a BOOLEAN
    value = CFPreferencesCopyAppValue(CFSTR("Boolean Value Key"), m_appID);
    if (value && CFGetTypeID(value) == CFBooleanGetTypeID()) {
        [m_checkBox setState:CFBooleanGetValue(value)];
    } else {
        [m_checkBox setState:NO];    // Default value of the checkbox
    }
    if (value) CFRelease(value);

    // Load the value of the text field as a STRING
    value = CFPreferencesCopyAppValue(CFSTR("String Value Key"), m_appID);
    if (value && CFGetTypeID(value) == CFStringGetTypeID()) {
        [m_textField setStringValue:(NSString *)value];
    } else {
        [m_textField setStringValue:@""];    // Default value of the text field
    }
    if (value) CFRelease(value);
}

// This action is called when our checkbox is
// clicked, we save the value immediately
- (IBAction)checkboxClicked:(id)sender
{
    CFPreferencesSetAppValue( CFSTR("Boolean Value Key"),
        [sender state] ? kCFBooleanTrue : kCFBooleanFalse, m_appID);

    // Force a synchronization so our preference is "live"
    CFPreferencesAppSynchronize(m_appID);
}

// This function is called when our preference
// pane is being deselected. That is, either
// another preference pane is being selected or
// the System Preferences application is
// shutting down. In this case we will save our text
// field, which is not saved at any other
// time. The checkbox is saved as it is clicked but
// just as easily can be saved here.
- (void)didUnselect
{
    CFNotificationCenterRef center;

    // Save text field
    CFPreferencesSetAppValue(CFSTR("String Value Key"),
        [m_textField stringValue], m_appID);

    // Write out all the changes that have been made for this application
    CFPreferencesAppSynchronize(m_appID);

    // Post a notification that the preferences
    // for this application have changed,
    // any observers will then become the first to know that this has occurred.
    center = CFNotificationCenterGetDistributedCenter();
    CFNotificationCenterPostNotification(center, CFSTR("Preferences Changed"),
        m_appID, NULL, TRUE);
}

// This action is called when the button on the second tab is clicked
- (IBAction)buttonClicked:(id)sender
{
    NSBeep();
}

@end

Chapter 9—MyStatusItem

AppController.h


#import <Cocoa/Cocoa.h>

@interface AppController : NSObject
{
    NSStatusItem     *m_item;
    NSMenu         *m_menu;
}

- (void)selItemOne:(id)sender;
- (void)selItemTwo:(id)sender;
- (void)selItemIP:(id)sender;
- (void)selItemQuit:(id)sender;

@end

AppController.m


#import "AppController.h"

@implementation AppController

- (id)init
{
    [super init];
    if (self) {
        // Get the system status bar
        NSStatusBar *bar = [NSStatusBar systemStatusBar];

        // #define USE_TEXT_TITLE 1
        // Add an item to the status bar
        #ifdef USE_TEXT_TITLE
        m_item = [[bar statusItemWithLength:
            NSVariableStatusItemLength] retain];
        [m_item setTitle: NSLocalizedString(@"My",@"")];
        #else
        m_item = [[bar statusItemWithLength:NSSquareStatusItemLength] retain];
        [m_item setImage:[NSImage imageNamed:@"x"]];
        #endif

        [m_item setHighlightMode:YES];

        // Create a menu to be added to the item
        m_menu = [[[NSMenu alloc] init] retain];
        [m_menu setAutoenablesItems:YES];
        [m_menu addItem: [[NSMenuItem alloc] initWithTitle:
                      NSLocalizedString(@"Item One",@"")
                      action:@selector(selItemOne:) keyEquivalent:@""]];
        [m_menu addItem: [[NSMenuItem alloc] initWithTitle:
                      NSLocalizedString(@"Item Two",@"")
                      action:@selector(selItemTwo:) keyEquivalent:@""]];
        [m_menu addItem: [NSMenuItem separatorItem]];
        [m_menu addItem: [[NSMenuItem alloc] initWithTitle:
                      NSLocalizedString(@"My IP",@"")
                      action:@selector(selItemIP:) keyEquivalent:@""]];
        [m_menu addItem: [NSMenuItem separatorItem]];
        [m_menu addItem: [[NSMenuItem alloc] initWithTitle:
                      NSLocalizedString(@"Quit MyStatusItem",@"")
                      action:@selector(selItemQuit:) keyEquivalent:@""]];

        // And add the menu to the item
        [m_item setMenu:m_menu];
    }
    return self;
}

- (void)dealloc
{

    [m_item release];
    [m_menu release];
    [super dealloc];
}

- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
    NSString *selectorString;
    selectorString = NSStringFromSelector([menuItem action]);

    NSLog(@"validateMenuItem called for %@", selectorString);
    if ([menuItem action] == @selector(selItemOne:))
        return YES;
    if ([menuItem action] == @selector(selItemTwo:))
        return YES;
    if ([menuItem action] == @selector(selItemIP:))
        return YES;
    if ([menuItem action] == @selector(selItemQuit:))
        return YES;

    return NO;
}

- (void)selItemOne:(id)sender
{
    int result;
    NSBeep();
    result = NSRunAlertPanel(@"MyStatusItem",
        @"Thank you for selecting menu item one.",
        @"OK", @"", @"");
}

- (void)selItemTwo:(id)sender
{
    int result;
    NSBeep();
    result = NSRunAlertPanel(@"MyStatusItem",
        @"Thank you for selecting menu item two.",
        @"OK", @"", @"");
}

- (void)selItemIP:(id)sender
{
    int result;
    NSString *theString = [NSString
        localizedStringWithFormat:@"IP Address: %@ DNS Name: %@",
        [[NSHost currentHost] address], [[NSHost currentHost] name]];
    NSBeep();
    result = NSRunAlertPanel(@"MyStatusItem", theString, @"OK", @"", @"");
}

- (void)selItemQuit:(id)sender
{
    int result;
    NSBeep();
    result = NSRunAlertPanel(@"MyStatusItem",
        @"Quit the application and remove this menu?", @"Yes", @"No", @"");
    if (result == NSAlertDefaultReturn) {
        [[NSApplication sharedApplication] terminate:self];
        // Respond to applicationWillTerminate for cleanup
    }
}

@end

main.m


#import <Cocoa/Cocoa.h>

int main(int argc, const char *argv[])
{
    return NSApplicationMain(argc, argv);
}

Chapter 10—MyScreenEffect

MyScreenSaverView.h


#import <ScreenSaver/ScreenSaver.h>

@interface MyScreenSaverView : ScreenSaverView
{
    int m_version;        // Used to tell the version of our
                          // preferences (and if they loaded)
    int m_useTransparency;// Use transparency when drawing the stars
    int m_useColor;       // Use color when drawing the stars

    IBOutlet id            m_configureSheet;
    IBOutlet NSButton     *m_useTransparencyCheckbox;
    IBOutlet NSButton     *m_useColorCheckbox;
}

- (IBAction) closeSheet_save:(id) sender;
- (IBAction) closeSheet_cancel:(id) sender;

@end

MyScreenSaverView.m


#import "MyScreenSaverView.h"

// Define what defines our bundle, this can be done a variety of ways include
// creating a global NSString
#define kBundleID @"com.triplesoft.myscreensaver"

@implementation MyScreenSaverView

- (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
{
    // Initialize our super - the ScreenSaverView
    self = [super initWithFrame:frame isPreview:isPreview];

    if (self) { // If all went well...

        // Load the defaults for this screensaver
        ScreenSaverDefaults *defaults = [ScreenSaverDefaults
                   defaultsForModuleWithName:kBundleID];

        // Try to load the version information to
        // see if we have any saved settings
        m_version = [defaults floatForKey:@"version"];
        if (!m_version) {
            // No saved setting, define our defaults
            m_version = 1;
            m_useTransparency = YES;
            m_useColor = YES;

            // Now write out the defaults to the defaults database
            [defaults setInteger:m_version forKey:@"version"];
            [defaults setInteger:m_useTransparency forKey:@"useTransparency"];
            [defaults setInteger:m_useColor forKey:@"useColor"];

            // And synchronize
            [defaults synchronize];
        }

        // Now, although this is overkill if version == 0,
        // we load the defaults from the
        // defaults database into our member variables
        m_useTransparency = [defaults integerForKey:@"useTransparency"];
        m_useColor = [defaults integerForKey:@"useColor"];

        // And set the animation time interval
        // (how often animateOneFrame is called)
        [self setAnimationTimeInterval:1/30.0];
    }

    return self;
}

- (void)startAnimation
{
    [super startAnimation];
}

- (void)stopAnimation
{
    [super stopAnimation];
}

- (void)drawRect:(NSRect)rect
{
    // We could draw here, but instead we draw in animateOneFrame
    [super drawRect:rect];
}

- (void)animateOneFrame
{
    NSRect    rect;
    NSColor   *color;
    int       x;
    float     r;

    // Calculate the star size
    x = SSRandomIntBetween(1, 3);

    // Make a rectangle at a random location the size of the star
    rect.origin = SSRandomPointForSizeWithinRect
        (NSMakeSize(x, x), [self bounds]);
        rect.size = NSMakeSize(x, x);

    // Calculate a random color (or gray), with or without transparency,
    // depending on the user preference
    r = SSRandomFloatBetween(0.0, 1.0);
    color = [NSColor colorWithCalibratedRed:r     // use red
        // use new color or same as red if not using color
        green:m_useColor ? SSRandomFloatBetween(0.0, 1.0) : r
            // use new color or same as red if not using color
            blue:m_useColor ? SSRandomFloatBetween(0.0, 1.0) : r
            // use transparency if the user says so
            alpha:m_useTransparency ?
                SSRandomFloatBetween(0.0, 1.0) : 1.0];
    [color set];

    // Draw the star
    [[NSBezierPath bezierPathWithOvalInRect:rect] fill];

    return;
}

- (BOOL)hasConfigureSheet
{
    return YES;
}

// Display the configuration sheet for the user to choose their settings
- (NSWindow*)configureSheet
{
    // If we have yet to load our configure sheet,
    // load the nib named MyScreenSaver.nib
    if (!m_configureSheet)
        [NSBundle loadNibNamed:@"MyScreenSaver" owner:self];

    // Set the state of our UI components
    [m_useTransparencyCheckbox setState:m_useTransparency];
    [m_useColorCheckbox setState:m_useColor];

    return m_configureSheet;
}

// The user clicked the SAVE button in the configuration sheet
- (IBAction) closeSheet_save:(id) sender
{
    // Get our defaults
    ScreenSaverDefaults *defaults =
        [ScreenSaverDefaults defaultsForModuleWithName:kBundleID];

    // Save the state of our UI components
    m_useTransparency = [m_useTransparencyCheckbox state];
    m_useColor = [m_useColorCheckbox state];

    // Write them to the defaults database
    [defaults setInteger:m_useTransparency forKey:@"useTransparency"];
    [defaults setInteger:m_useColor forKey:@"useColor"];

    // Synchronize
    [defaults synchronize];

    // The sheet has ended, go in peace
    [NSApp endSheet:m_configureSheet];
}

// The user clicked the CANCEL button in the configuration sheet
- (IBAction) closeSheet_cancel:(id) sender
{
    // Nothing to do! The sheet has ended, go in peace
    [NSApp endSheet:m_configureSheet];
}

@end

Chapter 11—MyColorPicker

ThePicker.h


#import <Cocoa/Cocoa.h>

@interface ThePicker : NSColorPicker <NSColorPickingCustom>
{
    IBOutlet    NSWindow     *m_window;
    IBOutlet    NSBox        *m_box;
    IBOutlet    NSTextView   *m_textView;
    IBOutlet    NSColorWell  *m_colorWell;

    NSColor                  *m_color;
}

-(IBAction)doRandomColor:(id)sender;
-(void)logText:(NSString *)theString;

@end

ThePicker.m


#import "ThePicker.h"

#define ThePickerMode        100

@implementation ThePicker

// --------------------------------------------------- NSColorPickingDefault

- (id)initWithPickerMask:(int)mask colorPanel:(NSColorPanel *)owningColorPanel
{
//  [self logText:@"initWithPickerMask "];
    if (mask & NSColorPanelRGBModeMask) { // we only support RGB mode
        [super initWithPickerMask:mask colorPanel:owningColorPanel];
    }
    srandom(time(0)); // init random number seed
    return self;
}

- (NSImage *)provideNewButtonImage
{
//    [self logText:@"provideNewButtonImage "];
    return [[NSImage alloc] initWithContentsOfFile:[[NSBundle bundleForClass:
               [self class]] pathForImageResource:@"ThePicker"]];
}

// --------------------------------------------------- NSColorPickingCustom

- (void)setColor:(NSColor *)color
{
    [self logText:@"setColor "];
    [color retain];
    [m_color release];
    m_color = color;
    [m_colorWell setColor:m_color];
}

- (int)currentMode
{
    [self logText:@"currentMode "];
    return ThePickerMode;
}

- (BOOL)supportsMode:(int)mode
{
    [self logText:@"supportsMode "];
    return (mode == ThePickerMode);
}

- (NSView *)provideNewView:(BOOL)initialRequest
{
    if (initialRequest) {
        if ([NSBundle loadNibNamed:@"ThePicker" owner:self]) {
            [self logText:@"provideNewView "];
            return m_box;
        } else {
            NSBeep();
            NSRunAlertPanel(@"Error", @"Couldn't load nib.", @"OK", @"", @"");
        }
    }
    return m_box;
}

// --------------------------------------------------- Actions

static __inline__ float SSRandomFloatBetween(float a, float b)
{
    return a + (b - a) * ((float)random() / (float) LONG_MAX);
}

-(IBAction)doRandomColor:(id)sender
{
    NSColor *theColor;
    [self logText:@"doRandomColor "];
    theColor = [NSColor colorWithCalibratedRed:SSRandomFloatBetween(0.0, 1.0)
        green:SSRandomFloatBetween(0.0, 1.0)
        blue:SSRandomFloatBetween(0.0, 1.0)
        alpha:SSRandomFloatBetween(0.0, 1.0)];
    [[self colorPanel] setColor:theColor];
}

-(void)logText:(NSString *)theString
{
    // Append the text to the end of the text view and scroll it into view
    NSRange theEnd = NSMakeRange([[m_textView string] length], 0);
    [m_textView replaceCharactersInRange:theEnd withString:theString];
    theEnd.location += [theString length];
    [m_textView scrollRangeToVisible:theEnd];
}

@end

Chapter 12—MyThread

AppController.h


#import <Cocoa/Cocoa.h>

@interface AppController : NSObject
{
    IBOutlet NSButton            *m_startButton;
    IBOutlet NSButton            *m_stopButton;
    IBOutlet NSProgressIndicator *m_indicator1;
    IBOutlet NSProgressIndicator *m_indicator2;
    IBOutlet NSProgressIndicator *m_indicator3;

    NSConditionLock              *m_conditionLock;
    BOOL                          m_stop;
}
- (IBAction)start:(id)sender;
- (IBAction)stop:(id)sender;

- (void)thread1:(id)owner;
- (void)thread2:(id)owner;
- (void)thread3:(id)owner;

- (void)cleanupThread:(id)owner;

@end

AppController.m


#import "AppController.h"

#define CONDITION_TASK_ONE        1
#define CONDITION_TASK_TWO        2
#define CONDITION_TASK_THREE    3
#define CONDITION_TASK_CLEANUP    4

@implementation AppController

// This action is called when the Start button is pressed, it begins everything
- (IBAction)start:(id)sender
{
    if (m_conditionLock == nil) {
        // Start out in Condition 1 so things start up right away
        m_conditionLock = [[NSConditionLock alloc]
            initWithCondition:CONDITION_TASK_ONE];

        // Set stop flag
        m_stop = NO;

        // Create all the threads that will eventually run based on Condition
        [NSThread detachNewThreadSelector:@selector(thread1:)
            toTarget:self withObject:self];
        [NSThread detachNewThreadSelector:@selector(thread2:)
            toTarget:self withObject:self];
        [NSThread detachNewThreadSelector:@selector(thread3:)
            toTarget:self withObject:self];

        // Create a final, cleanup thread
        [NSThread detachNewThreadSelector:@selector(cleanupThread:)
            toTarget:self withObject:self];

        [m_startButton setEnabled:NO];
        [m_stopButton setEnabled:YES];
    } else {
        NSRunAlertPanel(@"Error", @"m_conditionLock is not nil",
            @"OK", @"", @"");
    }
}

// This action is called when the Stop button is pressed,
// it sets a flag that the threads check
- (IBAction)stop:(id)sender
{
    [m_stopButton setEnabled:NO];
    m_stop = YES;
}

// This is the first thread to run, it starts as soon as
// the Start button is pressed
- (void)thread1:(id)owner
{
    int x;

    // Threads are responsible to manage their own autorelease pools
    NSAutoreleasePool *thePool = [[NSAutoreleasePool alloc] init];

    // Reset the progress indicator
    [m_indicator1 setDoubleValue:0];

    // Wait until Condition 1
    [m_conditionLock lockWhenCondition:CONDITION_TASK_ONE];

    // Loop, checking the stop flag
    for (x=1; x<=100; ++x) {
        if (m_stop) break;
        [m_indicator1 setDoubleValue:x];
        [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:
            (NSTimeInterval)0.05]];
    }

    // Change to Condition 2
    [m_conditionLock unlockWithCondition:CONDITION_TASK_TWO];

    // Release the autoreleasepool
    [thePool release];

    // Exit this thread
    [NSThread exit];
}

// This is the second thread to run, it starts as soon as the Start
// button is pressed, then waits until the first thread is finished
- (void)thread2:(id)owner
{
    int x;

    // Threads are responsible to manage their own autorelease pools
    NSAutoreleasePool *thePool = [[NSAutoreleasePool alloc] init];

    // Reset the progress indicator
    [m_indicator2 setDoubleValue:0];

    // Wait until Condition 2
    [m_conditionLock lockWhenCondition:CONDITION_TASK_TWO];

    // Loop, checking the stop flag
    for (x=1; x<=100; ++x) {
        if (m_stop) break;
        [m_indicator2 setDoubleValue:x];
        [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:
            (NSTimeInterval)0.05]];
    }

    // Change to Condition 3
    [m_conditionLock unlockWithCondition:CONDITION_TASK_THREE];

    // Release the autoreleasepool
    [thePool release];

    // Exit this thread
    [NSThread exit];
}

// This is the third thread to run, it starts as soon as the Start
// button is pressed, then waits until the second thread is finished
- (void)thread3:(id)owner
{
    int x;

    // Threads are responsible to manage their own autorelease pools
    NSAutoreleasePool *thePool = [[NSAutoreleasePool alloc] init];

    // Reset the progress indicator
    [m_indicator3 setDoubleValue:0];

    // Wait until Condition 3
    [m_conditionLock lockWhenCondition:CONDITION_TASK_THREE];

    // Loop, checking the stop flag
    for (x=1; x<=100; ++x) {
        if (m_stop) break;
        [m_indicator3 setDoubleValue:x];
        [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:
            (NSTimeInterval)0.05]];
    }

    // Change to Condition 4
    [m_conditionLock unlockWithCondition:CONDITION_TASK_CLEANUP];

    // Release the autoreleasepool
    [thePool release];

    // Exit this thread
    [NSThread exit];
}

// This is the cleanup thread, it starts as soon as the Start
// button is pressed, then waits until the third thread is finished
- (void)cleanupThread:(id)owner
{
    // Threads are responsible for manage their own autorelease pools
    NSAutoreleasePool *thePool = [[NSAutoreleasePool alloc] init];

    // Wait until Condition 4
    [m_conditionLock lockWhenCondition:CONDITION_TASK_CLEANUP];

    // Last stop, clean up
    [m_conditionLock unlock];
    [m_conditionLock release];
    m_conditionLock = nil;

    // Update the UI
    [m_stopButton setEnabled:NO];
    [m_startButton setEnabled:YES];

    // Play done sound if the user didn't stop prematurely
    if (!m_stop)
        [[NSSound soundNamed:@"done"] play];

    // Release the autoreleasepool
    [thePool release];

    // Exit this thread
    [NSThread exit];
}

// Causes the application to quit when the one and only window is closed
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
    return TRUE;
}

- (void)windowWillClose:(NSNotification *)aNotification
{
    [self stop:self];
}

@end

main.m


#import <Cocoa/Cocoa.h>

int main(int argc, const char *argv[])
{
    return NSApplicationMain(argc, argv);
}

Chapter 13—MyTerminal

AppController.h


#import <Cocoa/Cocoa.h>

@interface AppController : NSObject
{
}
@end

AppController.m


#import "AppController.h"

@implementation AppController

- (id)init
{
    [super init];
    if (self) {

    }
    return self;
}

- (void)dealloc
{
    [super dealloc];
}

// Causes the application to quit when the one and only window is closed
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
    return TRUE;
}

@end

main.m


#import <Cocoa/Cocoa.h>

int main(int argc, const char *argv[])
{
    return NSApplicationMain(argc, argv);
}

MyTerminalController.h


#import <Cocoa/Cocoa.h>

@interface MyTerminalController : NSObject
{
    IBOutlet NSTextView          *m_textView;
    IBOutlet NSButton            *m_pingButton;
    IBOutlet NSProgressIndicator *m_pingIndicator;

    IBOutlet NSTextField         *m_textField;
    IBOutlet NSButton            *m_uptimeButton;

    NSTask                       *m_pingTask;
    NSPipe                       *m_pingPipe;
    BOOL                          m_pingIsRunning;
}
- (IBAction)ping:(id)sender;
- (IBAction)uptime:(id)sender;

- (void)displayPingData:(NSFileHandle*) theFileHandle;

@end

MyTerminalController.m


#import "MyTerminalController.h"

@implementation MyTerminalController

- (id)init
{
    [super init];
    if (self) {
        m_pingIsRunning = FALSE;
        m_pingPipe = nil;
        m_pingTask = nil;
    }
    return self;
}

- (void)dealloc
{
    if (m_pingPipe) { [m_pingPipe release]; m_pingPipe = nil; }
    if (m_pingTask) { [m_pingTask release]; m_pingTask = nil; }
    [super dealloc];
}

// Called to initialize our fields if we need to
- (void)awakeFromNib
{
    [m_textView setString:@"Welcome to our pingy little world!"];
}

/*
    ping is called when the Start Ping button is pressed.
    It is also called to stop a ping in progress.
    Essentially, this function sets things up for a separate
    thread that handles actually reading the data.
    When the users presses the Stop Ping button, the function
    sets a flag that triggers the thread to
    deallocate the task and stop the pinging.
*/
- (IBAction)ping:(id)sender
{
    if (m_pingIsRunning) {
        // If we are currently running and this is called,
        // we want to stop, so set a flag...
        m_pingIsRunning = FALSE;
        // ...and disable the button so it can't be clicked
        // again until we have terminated the ping
        [m_pingButton setEnabled:NO];
    } else {
        NSFileHandle     *theFileHandle;

        // Otherwise we are currently not pinging so we want to start...
        // allocate a task, a pipe and the file handle
        m_pingTask = [[NSTask alloc] init];
        m_pingPipe = [[NSPipe alloc] init];
        theFileHandle = [m_pingPipe fileHandleForReading];

        if (m_pingTask && m_pingPipe && theFileHandle) {
            // If we get this far we are pretty safe so we set the global flag
            // that we are pinging...
            m_pingIsRunning = TRUE;
            // ...and begin some animation for the user to see activity
            // on the screen
            [m_pingIndicator startAnimation:self];

            // Tell the task what command (program) to execute
            [m_pingTask setLaunchPath:@"/sbin/ping"];

            // Pass some arguments to the program,
            // in this case the domain name to ping 5 times
            [m_pingTask setArguments:[NSArray arrayWithObjects:@"-c 5",
                @"triplesoft.com", nil]];

            // Set m_pingPipe as the standard output so we can see the results
            [m_pingTask setStandardOutput:m_pingPipe];

            // Launch the task
            [m_pingTask launch];

            // Clear the text we placed in the text view in awakeFromNib
            [m_textView setString:@""];

            // Create the thread that will handle reading data from
            // ping and pass theFileHandle from thePipe
            [NSThread detachNewThreadSelector:@selector(displayPingData:)
                toTarget:self withObject:theFileHandle];

            // Change the title of the button to Stop Ping to reflect
            // the change in state
            [m_pingButton setTitle:@"Stop Ping"];
        } else {
            // If there was an error, tell the user
            if (m_pingPipe) { [m_pingPipe release]; m_pingPipe = nil; }
            if (m_pingTask) { [m_pingTask release]; m_pingTask = nil; }
            NSRunAlertPanel(@"MyTerminal",
                @"An error occurred trying to allocate the task, pipe or file
                  handle.",
                @"OK", @"", @"");
        }
    }
}

/*
    This is the thread that handles reading the data from the ping program
*/
- (void)displayPingData:(NSFileHandle*) theFileHandle
{
    // Threads are responsible for manage their own autorelease pools
    NSAutoreleasePool *thePool = [[NSAutoreleasePool alloc] init];

    // While the flag is set (the user has yet to tell us to stop pinging)
    while (m_pingIsRunning) {
        // Read in any available data from the file handle
        // passed into the thread
        NSData *theData = [theFileHandle availableData];

        // If there is data...
        if ([theData length]) {
            // Extract a string from the data returned in
            // the pipe's file handle
            NSString *theString = [[NSString alloc]
                initWithData:theData encoding:NSASCIIStringEncoding];

            // Append the text to the end of the text view
            // and scroll it into view
            NSRange theEnd = NSMakeRange([[m_textView string] length], 0);
            [m_textView replaceCharactersInRange:theEnd withString:theString];
            theEnd.location += [theString length];
            [m_textView scrollRangeToVisible:theEnd];

            // Release the string
            [theString release];
        }

        // Sleep this thread for 100ms so as to allow
        // other threads time (ie: UI)
        [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:
            (NSTimeInterval)0.1]];

        // Check if the task has completed and the data is gone
        if (([m_pingTask isRunning] == NO) && ([theData length] == 0))
            m_pingIsRunning = NO;
    }

    // Once the flag has been set to FALSE, we need to clean things up...

    // Terminate the ping task and wait for it to exit
    [m_pingTask terminate];
    [m_pingTask waitUntilExit];

    // Check the termination status of ping, 15 or 0 means it exited successfully
    // 15 = user cancelled, 0 = normal termination
        if (([m_pingTask terminationStatus] != 15) &&
           ([m_pingTask terminationStatus] != 0))
        NSRunAlertPanel(@"MyTerminal", @"An error occurred trying to quit the
                                         task. (%d)",
                        @"OK", @"", @"", [m_pingTask terminationStatus]);

    // Release the pipe and task
    if (m_pingPipe) { [m_pingPipe release]; m_pingPipe = nil; }
    if (m_pingTask) { [m_pingTask release]; m_pingTask = nil; }

    // Update the UI
    [m_pingIndicator stopAnimation:self];
    [m_pingButton setEnabled:YES];
    [m_pingButton setTitle:@"Start Ping"];

    // Release the autoreleasepool
    [thePool release];

    // Exit this thread
    [NSThread exit];
}

/*
    uptime is a simple, fast command. It only
    returns one line of text so we quickly
    are able to execute it and read the results
    from the pipe. No need for fancy threads
    or multiple reads here.
*/
- (IBAction)uptime:(id)sender
{
    // Allocate the task to execute and the pipe to send the output to
    NSTask             *theTask = [[NSTask alloc] init];
    NSPipe             *thePipe = [[NSPipe alloc] init];

    // Get the file handle from the pipe (assumes thePipe was allocated!)
    NSFileHandle     *theFileHandle = [thePipe fileHandleForReading];

    // Tell the task what command (program) to execute
    [theTask setLaunchPath:@"/usr/bin/uptime"];

    // Set thePipe as the standard output so we can see the results
    [theTask setStandardOutput:thePipe];

    // Launch the task
    [theTask launch];

    // Wait until the task exits, we know uptime exits immediately
    [theTask waitUntilExit];

    // Verify that the program completed without error
    if ([theTask terminationStatus] == 0) {
        NSString     *theString;

        // Extract a string from the data returned in the pipe's file handle
        theString = [[NSString alloc]
            initWithData:[theFileHandle readDataToEndOfFile]
            encoding:NSASCIIStringEncoding];

        // Set the text field to the value of the string
        [m_textField setStringValue:theString];

        // Release what we create, theFileHandle is automatically
        // released by thePipe
        [theString release];
    } else {
        // Set the text field to the value of the error
        [m_textField setIntValue:[theTask terminationStatus]];
    }

    [thePipe release];
    [theTask release];
}

@end

Chapter 14—MyXMLRPC

AppController.h


#import <Cocoa/Cocoa.h>

@interface AppController : NSObject
{
    IBOutlet NSPopUpButton       *m_product;
    IBOutlet NSPopUpButton       *m_color;
    IBOutlet NSTextField         *m_price;
    IBOutlet NSTextField         *m_stock;
    IBOutlet NSTextField         *m_result;
}
- (IBAction)getProductInformation:(id)sender;
@end

AppController.m


#import "AppController.h"
#import <CoreServices/CoreServices.h>

@implementation AppController

- (id)init
{
    [super init];
    if (self) {

    }
    return self;
}

- (void)dealloc
{
    [super dealloc];
}

// This is the XML RPC method that does all the work
- (IBAction)getProductInformation:(id)sender
{
    WSMethodInvocationRef      rpcCall;
    NSURL                     *rpcURL;
    NSString                  *methodName;
    NSMutableDictionary       *params;
    NSArray                   *paramsOrder;
    NSDictionary              *result;
    NSString                  *selectedProduct;
    NSString                  *selectedColor;

    //
    //    1. Define the location of the RPC service and method
    //

    // Create the URL to the RPC service
    rpcURL = [NSURL URLWithString:
             @"http://www.triplesoft.com/xmlrpc/product_server.php"];

    // Assign the method name to call on the RPC service
    methodName = @"example.getProductInfo";

    // Create a method invocation
    // First parameter is the URL to the RPC service
    // Second parameter is the name of the RPC method to call
    // Third parameter is a constant to specify the XML-RPC protocol
    rpcCall = WSMethodInvocationCreate((CFURLRef)rpcURL,
              (CFStringRef)methodName, kWSXMLRPCProtocol);

    //
    //     2. Set up the parameters to be passed to the RPC method
    //

    // Get the users choices
    selectedProduct = [m_product titleOfSelectedItem];
    selectedColor = [m_color titleOfSelectedItem];

    // Add the users choices to the dictionary to be passed as parameters
    params = [NSMutableDictionary dictionaryWithCapacity:2];
    [params setObject:selectedProduct forKey:selectedProduct];
    [params setObject:selectedColor forKey:selectedColor];

    // Create the array to specify the order of the parameter values
    paramsOrder = [NSArray arrayWithObjects:selectedProduct,
        selectedColor, nil];

    // Set the method invocation parameters
    // First parameter is the method invocation created above
    // Second parameter is a dictionary containing the parameters themselves
    // Third parameter is an array specifying the order of the parameters
    WSMethodInvocationSetParameters(rpcCall, (CFDictionaryRef)params,
        (CFArrayRef)paramsOrder);

    //
    //    3. Make the call and parse the results!
    //

    // Invoke the method which returns a dictionary of results
    result = (NSDictionary*)WSMethodInvocationInvoke(rpcCall);

    // If the results are a fault, display an error to the user with the
    // fault code and descriptive string
    if (WSMethodResultIsFault((CFDictionaryRef)result)) {
        NSRunAlertPanel([NSString stringWithFormat:@"Error %@",
            [result objectForKey: (NSString*)kWSFaultCode]],
            [result objectForKey: (NSString*)kWSFaultString], @"OK", @"", @"");
    } else {
        // Otherwise, pull the results from the dictionary as an array
        NSArray *array = [result objectForKey:
            (NSString*)kWSMethodInvocationResult];

        // Display the entire array result from the server
        [m_result setStringValue: [array description]];

        // Display the specific fields we are interested in (price, stock)
        [m_price setStringValue: [array objectAtIndex: 2]];
        [m_stock setStringValue: [array objectAtIndex: 3]];
    }

    // Release those items that need to be released
    [params release];
    params = nil;
    [paramsOrder release];
    paramsOrder = nil;
    [result release];
    result = nil;
}


// Causes the application to quit when the one and only window is closed
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
    return TRUE;
}

@end

main.m


#import <Cocoa/Cocoa.h>

int main(int argc, const char *argv[])
{
    return NSApplicationMain(argc, argv);
}

product_client.php


<html>
<head><title>PHP XML RPC Example</title></head>
<body>
<basefont size="2" face="Verdana,Arial">

<?php
include("xmlrpc.inc");

// Show the results of the previous query if the
// parameter there are results to show
if (($HTTP_POST_VARS["product_name"] != "") &&
    ($HTTP_POST_VARS["product_color"] != "")) {

    // Create the message object using the RPC name, parameter and its type
    $message = new xmlrpcmsg("example.getProductInfo",
        array(new xmlrpcval($HTTP_POST_VARS["product_name"], "string"),
              new xmlrpcval($HTTP_POST_VARS["product_color"], "string")));

    // Display the message detail
    print "<pre><b>Message Detail:<br /></b>" .
        htmlentities($message->serialize()) . "</pre>";

    // Create the client object which points to the server and PHP script to
    // contact
    $client = new xmlrpc_client("/xmlrpc/product_server.php",
        "www.triplesoft.com", 80);

    // Enable debug mode so we see all there is to see coming and going
//    $client->setDebug(1);
//    print "<pre><b>From Server:<br /></b></pre>";

    // Send the message to the server for processing
    $response = $client->send($message);

    // If no response was returned there was a fatal error
    // (no network connection, no server, etc.)
    if (!$response) { die("Send failed."); }

    // If no error, display the results
    if (!$response->faultCode()) {

        // Extract the values from the response
        $result_array = $response->value();
        if ($result_array->arraysize() == 4) {
            $product_name = $result_array->arraymem(0);
            $product_color = $result_array->arraymem(1);
            $product_price = $result_array->arraymem(2);
            $product_stock = $result_array->arraymem(3);

            print "<br />";
            print "<br />Product: <b>" . $product_name->scalarval() . "</b>";
            print "<br />Color: <b>" . $product_color->scalarval() . "</b>";
            print "<br />Price: <b>$" . $product_price->scalarval() . "</b>";
            print "<br />In-Stock: <b>" . $product_stock->scalarval() . "</b>";
            print "<br />";

        } else {

            print "<br />";
            print "<br />Incorrect number of items in array.
                Should be 4 but there are <b>"
                . $result_array->arraysize() . "</b>.";
            print "<br />";
        }

        // Display the response detail
          print "<pre><b>Response Detail:<br /></b>" .
                  htmlentities($response->serialize()) . "</pre>";

    } else {
        print "<br />";
        print "<br /><b>Fault Code:</b> " . $response->faultCode();
        print "<br /><b>Fault String:</b> " . $response->faultString();
        print "<br />";
    }

    print "<hr>";

}
?>

    <form action="<?php echo "$PHP_SELF";?>" method="POST">
        <select name="product_name">
            <option label="Book Bag"
                value="Book Bag" selected>Book Bag</option>
            <option label="Garment Bag"
                value="Garment Bag">Garment Bag</option>
            <option label="Grocery Bag"
                value="Grocery Bag">Grocery Bag</option>
            <option label="Hand Bag"
                value="Hand Bag">Hand Bag</option>
            <option label="Old Bag"
                value="Old Bag">Old Bag</option>
        </select>
        in
        <select name="product_color">
            <option label="Black" value="Black" selected>Black</option>
            <option label="Blue" value="Blue">Blue</option>
            <option label="Brown" value="Brown">Brown</option>
            <option label="Green" value="Green">Green</option>
            <option label="Red" value="Red">Red</option>
            <option label="White" value="White">White</option>
        </select>
        <button type="submit" name="Get Product Information"
            value="Get Product Information">Get Product Information</button>
    </form>

</body>
</html>

product_server.php


<?php
include("xmlrpc.inc");
include("xmlrpcs.inc");

// Array of products and prices, in real-life you might use a SQL database
$products = array (
    "Grocery Bag" => "9.00",
    "Book Bag" => "18.00",
    "Hand Bag" => "32.00",
    "Garment Bag" => "48.00",
    "Old Bag" => "0.31"
    );

// Signature of this RPC
$products_signature = array(array($xmlrpcArray,
    // Returns a product price and quantity
    $xmlrpcString,    // Accepts a product name
    $xmlrpcString));  // Accepts a color

// Documentation string of this RPC
$products_docstring = "When passed valid product info, more info is returned.";

// RPC Main
function get_product_info($input)
{
    global $xmlrpcerruser, $products;
    $err = "";

    // Get the parameters
    $param0 = $input->getParam(0);
    $param1 = $input->getParam(1);

    // Verify value and type
    if ((isset($param0) && ($param0->scalartyp() == "string")) &&
           (isset($param1) && ($param1->scalartyp() == "string"))) {

        // Extract parameter values
        $product_name = $param0->scalarval();
        $product_color = $param1->scalarval();

        // Attempt to find the product and price
        foreach ($products as $key => $element) {
            if ($key == $product_name) {
                $product_price = $element;
            }
        }

        // For this demo, all Browns are out of stock and all other colors
        // produce a random in-stock quantity - probably wouldn't work like
        // this in real-life, you would probably use a SQL database instead
        if ($product_color == "Brown") {
            $product_stock = 0;
        } else {
            $product_stock = rand(1, 100);
        }

        // If not found, report error
        if (!isset($product_price)) {
            $err = "No product named '" . $product_name . "' found.";
        }

    } else {
        $err = "Two string parameters (product name and color) are required.";
    }

    // If an error was generated, return it, otherwise return the proper data
    if ($err) {
        return new xmlrpcresp(0, $xmlrpcerruser, $err);
    } else {
        // Create an array and fill it with the data to return
        $result_array = new xmlrpcval(array(), "array");
        $result_array->addScalar($product_name, "string");
        $result_array->addScalar($product_color, "string");
        $result_array->addScalar($product_price, "string");
        $result_array->addScalar($product_stock, "string");
        return new xmlrpcresp($result_array);
    }

}

// Create the server
$server = new xmlrpc_server(
    array("example.getProductInfo" =>
        array("function" => "get_product_info",
              "signature" => $products_signature,
              "docstring" => $products_docstring),
         )
     );
?>

Chapter 15—MySOAP

AppController.h


#import <Cocoa/Cocoa.h>

@interface AppController : NSObject
{
    IBOutlet NSPopUpButton *m_product;
    IBOutlet NSPopUpButton *m_color;
    IBOutlet NSTextField *m_price;
    IBOutlet NSTextField *m_stock;
    IBOutlet NSTextField *m_result;
}
- (IBAction)getProductInformation:(id)sender;
@end

AppController.m


#import "AppController.h"
#import <CoreServices/CoreServices.h>

@implementation AppController

- (id)init
{
    [super init];
    if (self) {

    }
    return self;
}

- (void)dealloc
{
    [super dealloc];
}

// This is the SOAP method that does all the work
- (IBAction)getProductInformation:(id)sender
{
    WSMethodInvocationRef      soapCall;
    NSURL                     *soapURL;
    NSString                  *methodName;
    NSMutableDictionary       *params;
    NSArray                   *paramsOrder;
    NSDictionary              *result;
    NSString                  *selectedProduct;
    NSString                  *selectedColor;

    //
    //    1. Define the location of the SOAP service and method
    //

    // Create the URL to the SOAP service
    soapURL = [NSURL URLWithString:
        @"http://www.triplesoft.com/soap/product_server.php"];

    // Assign the method name to call on the SOAP service
    methodName = @"get_product_info";

    // Create a method invocation
    // First parameter is the URL to the SOAP service
    // Second parameter is the name of the SOAP method to call
    // Third parameter is a constant to specify the SOAP2001 protocol
    soapCall = WSMethodInvocationCreate((CFURLRef)soapURL,
        (CFStringRef)methodName, kWSSOAP2001Protocol);

    //
    //     2. Set up the parameters to be passed to the SOAP method
    //

    // Get the users choices
    selectedProduct = [m_product titleOfSelectedItem];
    selectedColor = [m_color titleOfSelectedItem];

    // Add the users choices to the dictionary to be passed as parameters
    params = [NSMutableDictionary dictionaryWithCapacity:2];
    [params setObject:selectedProduct forKey:@"product_name"];
    [params setObject:selectedColor forKey:@"product_color"];

    // Create the array to specify the order of the parameter values
    paramsOrder = [NSArray arrayWithObjects:@"product_name",
        @"product_color", nil];

    // Set the method invocation parameters
    // First parameter is the method invocation created above
    // Second parameter is a dictionary containing the parameters themselves
    // Third parameter is an array specifying the order of the parameters,
    // sometimes optional for SOAP
    WSMethodInvocationSetParameters(soapCall, (CFDictionaryRef)params,
        (CFArrayRef)paramsOrder);

    //
    //    3. Make the call and parse the results!
    //

    // Invoke the method which returns a dictionary of results
    result = (NSDictionary*)WSMethodInvocationInvoke(soapCall);

    // If the results are a fault, display an
    // error to the user with the fault code
    // and descriptive string
    if (WSMethodResultIsFault((CFDictionaryRef)result)) {
        NSRunAlertPanel([NSString stringWithFormat:@"Error %@",
            [result objectForKey: (NSString*)kWSFaultCode]],
            [result objectForKey: (NSString*)kWSFaultString], @"OK", @"", @"");
    } else {
        // Otherwise, pull the results from the dictionary, held as another
        // dictionary named "soapVal"
        NSDictionary *dictionary = [result objectForKey:
            (NSString*)kWSMethodInvocationResult];
        NSDictionary *soapVal = [dictionary objectForKey:@"soapVal"];

        // Display the entire dictionary result from the server
        [m_result setStringValue: [dictionary description]];

        // Display the specific fields we are interested in (price, stock)
        [m_price setStringValue: [soapVal objectForKey:@"product_price"]];
        [m_stock setStringValue: [soapVal objectForKey:@"product_stock"]];
    }

    // Release those items that need to be released
    [params release];
    params = nil;
    [paramsOrder release];
    paramsOrder = nil;
    [result release];
    result = nil;
}


// Causes the application to quit when the one and only window is closed
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
    return TRUE;
}

@end

main.m


#import <Cocoa/Cocoa.h>

int main(int argc, const char *argv[])
{
    return NSApplicationMain(argc, argv);
}

product_client.php


<html>
<head><title>PHP SOAP Example</title></head>
<body>
<basefont size="2" face="Verdana,Arial">

<?php
include("nusoap.php");

// Show the results of the previous query if
// the parameter there are results to show
if (($HTTP_POST_VARS["product_name"] != "") &&
    ($HTTP_POST_VARS["product_color"] != "")) {

    // Create the parameters array
    $params = array("product_name" => $HTTP_POST_VARS["product_name"],
            "product_color" => $HTTP_POST_VARS["product_color"]);

    // Create a new soap client endpoint specifying the server location
    $client = new
        soapclient("http://www.triplesoft.com/soap/product_server.php");

    // Enable debugging
    $client->debug_flag = true;

    // Call the function, passing the parameter array
    $response = $client->call("get_product_info", $params);

    // Echo request detail
    echo "<pre><b>Request Detail:<br /></b><xmp>".
        $client->request."</xmp></pre>";

    // If no response was returned there was a fatal error
    // (no network connection, no server, etc.)
    // You might prefer to use the $client->getError() method
    // for more detailed information.
    if (!$response) { die("Send failed."); }

    // If no error, display the results
    if (!$client->fault) {

        // Extract the values from the response
        if (count($response) == 4) {

            foreach ($response as $key => $element) {
                $$key = $element;
            }

            print "<br />";
            print "<br />Product: <b>" . $product_name . "</b>";
            print "<br />Color: <b>" . $product_color . "</b>";
            print "<br />Price: <b>$" . $product_price . "</b>";
            print "<br />In-Stock: <b>" . $product_stock . "</b>";
            print "<br />";

        } else {

            print "<br />";
            print "<br />Incorrect number of items in array.
                Should be 4 but there are <b>"
                . count($response) . "</b>.";
            print "<br />";
        }

        // Echo response detail
        echo "<pre><b>Response Detail:<br /></b><xmp>".
            $client->response."</xmp></pre>";

    } else {
        print "<br />";
        print "<br /><b>Fault Code:</b> " . $client->faultcode;
        print "<br /><b>Fault Actor:</b> " . $client->faultactor;
        print "<br /><b>Fault String:</b> " . $client->faultstring;
        print "<br /><b>Fault Detail:</b> " . $client->faultdetail;
        // serialized?
        print "<br />";
    }

    // Echo debug log at the bottom since it can be large
    echo "<pre><b>Debug log:<br /></b>".$client->debug_str."</pre>";

    print "<hr>";

}
?>

    <form action="<?php echo "$PHP_SELF";?>" method="POST">
        <select name="product_name">
            <option label="Book Bag"
                value="Book Bag" selected>Book Bag</option>
            <option label="Garment Bag"
                value="Garment Bag">Garment Bag</option>
            <option label="Grocery Bag"
                value="Grocery Bag">Grocery Bag</option>
            <option label="Hand Bag" value="Hand Bag">Hand Bag</option>
            <option label="Old Bag" value="Old Bag">Old Bag</option>
        </select>
        in
        <select name="product_color">
            <option label="Black" value="Black" selected>Black</option>
            <option label="Blue" value="Blue">Blue</option>
            <option label="Brown" value="Brown">Brown</option>
            <option label="Green" value="Green">Green</option>
            <option label="Red" value="Red">Red</option>
            <option label="White" value="White">White</option>
        </select>
        <button type="submit" name="Get Product Information"
            value="Get Product Information">Get Product Information</button>
    </form>

</body>
</html>

product_server.php


<?php
include("nusoap.php");

// Array of products and prices, in real-life you might use a SQL database
$products = array (
    "Grocery Bag" => "9.00",
    "Book Bag" => "18.00",
    "Hand Bag" => "32.00",
    "Garment Bag" => "48.00",
    "Old Bag" => "0.31"
    );

// Create a new SOAP server instance
$server = new soap_server;

// Enable debugging, to be echoed via client
$server->debug_flag = true;

// Register the get_product_info function for publication
$server->register("get_product_info");

// Begin the HTTP listener service and exit
$server->service($HTTP_RAW_POST_DATA);

// Our published service
function get_product_info($product_name, $product_color)
{
    global $products;
    $err = "";

    // Verify values
    if (isset($product_name) && isset($product_color)) {

        // Attempt to find the product and price
        foreach ($products as $key => $element) {
            if ($key == $product_name) {
                $product_price = $element;
            }
        }

        // For this demo, all Browns are out of stock and all other colors
        // produce a random in-stock quantity - probably wouldn't work like
        // this in real-life, you would probably use a SQL database instead
        if ($product_color == "Brown") {
            $product_stock = 0;
        } else {
            $product_stock = rand(1, 100);
        }

        // If not found, report error
        if (!isset($product_price)) {
            $err = "No product named '" . $product_name . "' found.";
        }

    } else {
        $err = "Two string parameters (product name and color) are required.";
    }

    // If an error was generated, return it, otherwise return the proper data
    if ($err) {
        return new soap_fault("Client", "product_server.php", $err, $err);
    } else {
        // Create an array and fill it with the data to return

        $result_array = array("product_name" => $product_name,
                    "product_color" => $product_color,
                    "product_price" => $product_price,
                    "product_stock" => $product_stock);

        return $result_array;
    }

}

exit();

?>

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.134.102.182