Putting It All Together

In the remainder of this chapter, we’ll implement the MathAnimation view, as well as a special surprise.

  1. Insert the statements shown here in bold into the MathAnimation.h file:

    #import <Cocoa/Cocoa.h>
    
    @interface MathAnimation : NSView
    {    float theta;       // Current rotation for the star
                             float fraction;    // Current intensity for the pulsing icon
                             float ddelta;      // Density delta for icon
                             NSMutableAttributedString *str;   // "MathPaper" string
                             NSImage *image;
                             NSTimer *timer;                   // Our timer
    }
    - (IBAction)tick:(id)sender;
    
    @end

The purpose of these six instance variables is easier to understand with the About panel from Figure 14-1 in mind, so you might want to go back and take a quick look at it. The first three variables, theta, fraction, and ddelta, will be used to keep track of the animation’s rotation, intensity, and density, respectively. They will be updated by NSTimer methods as they are invoked. The NSMutableAttributedString variable str will be used to hold the attributed “MathPaper” text. We’ll create this string when the MathAnimation view is first initialized, and then draw it on the view each time that we are asked to draw the view. The same is true for the image held in the NSImage instance variable, image. Finally, the NSTimer variable timer will be used to keep track of the timer. This timer is created when the window is exposed. We need to keep the instance variable so we can invalidate the timer when the window is closed.

  1. Edit the file MathAnimation.m and add the #define statements and the awakeFromNib method shown here:

    @implementation MathAnimation#define FPS 30.0    // Frames per second
                         - (void)awakeFromNib
                         {
                             // Set up the attributed text
                             NSFont *font = [NSFont fontWithName:@"Helvetica" size:36.0];
                             NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
                             [attrs setObject:font forKey:NSFontAttributeName];
                             [attrs setObject:[NSColor greenColor]
                                       forKey:NSForegroundColorAttributeName];
                             str = [ [NSMutableAttributedString alloc]
                                       initWithString:@"MathPaper"
                                           attributes:attrs];
                             ddelta = (1.0 / FPS) / 5.0;
                             theta = 0.0;
                             image = [[NSImage imageNamed:@"PaperIcon"] retain];
                             [ [NSNotificationCenter defaultCenter]
                               addObserver:self selector:@selector(start:)
                               name:NSWindowDidBecomeKeyNotification object:[self window]];
                             [ [NSNotificationCenter defaultCenter]
                                addObserver:self selector:@selector(stop:)
                                name:NSWindowWillCloseNotification object:[self window] ];
                         }

The awakeFromNib method is automatically invoked when the MathAnimation view is first unpacked from AboutPanel.nib. The first half of the method sets up the attributed text that will draw “MathPaper” in the window. The text is drawn in 36-point green Helvetica, which is positively ugly! The variable ddelta is the increment that will be added to the variable fraction each time the timer clicks. We initialize theta to the initial rotation angle for the star. The NSImage object image is set to be the same image that we use for the document icon. Finally, we add two “observers” for the default notification center. The first observer will be self — that is, the MathAnimation view — and will receive the start: message when the window in which it resides becomes the key window. The second observer will cause the stop: message to be sent to the MathAnimation view when the window is closed. Notifications are similar to delegate messages, except that any number of objects can receive the same notification. This completes the initialization logic.

Next, we implement the start: and stop: methods that were referenced earlier:

  1. Insert the start: and stop: methods in MathAnimation.m:

                         - (void)start:(void *)userInfo
                         {
                             if (!timer) {
                                 timer = [NSTimer scheduledTimerWithTimeInterval:1.0/FPS
                                                    target:self
                                                  selector:@selector(tick:)
                                                  userInfo:0
                                                   repeats:YES];
                             }  
                         }
                         - (void)stop:(void *)userInfo
                         {
                             if (timer) {
                                 [timer invalidate];
                                 timer = nil;  // No need to release; we did not retain the
                                               // NSTimer object because scheduled timers are
                                               // automatically retained by the AppKit
                             }
                         }

The start: method starts the NSTimer if it does not already exist. The stop: method stops the timer and then resets the timer instance variable to 0. This is necessary so that the timer will be recreated if the window is exposed again.

  1. Insert the following updated drawRect: method near the end of MathAnimation.m:

                         - (void)drawRect:(NSRect)rect
                         {
                             float x,y,t2;
                             NSBezierPath *oval;
                         
        // Paint the background white
                             [ [NSColor whiteColor] set];
                             NSRectFill([self bounds]);
                         
        // Draw the name "MathPaper"; str was set in awakeFromNib
                             [str drawAtPoint:NSMakePoint(20,50)];
                         
        // Draw those cool straight black lines
                             [ [NSColor blackColor] set];
                             for (x=0; x<50; x+=10) {
                                 [NSBezierPath setDefaultLineWidth:(50-x)/10.0];
                                 [NSBezierPath strokeLineFromPoint:NSMakePoint(20+x,50-x)
                                                           toPoint:NSMakePoint(300.0,50-x)];
                             }
                         
        // Put the PaperIcon in the upper-left corner of the panel
                             [image compositeToPoint:
                                    NSMakePoint(10.0, [self bounds].size.height-128.0)
                                           operation:NSCompositeSourceOver fraction:fraction];
                         
        // Make a path for the star
                             x = [self bounds].size.width * .75;
                             y = [self bounds].size.height * .75;
                             oval = [NSBezierPath bezierPath];
                         
        [oval moveToPoint:
                                   NSMakePoint(x + cos(theta)*50, y + sin(theta) * 50)];
                             for (t2=0; t2<=2*M_PI+.1; t2+=M_PI*.5) {
                                 [oval curveToPoint:NSMakePoint(x + cos(theta+t2)*50,
                                                                y + sin(theta+t2)*50)
                                       controlPoint1:NSMakePoint(x,y)
                                       controlPoint2:NSMakePoint(x,y)];
                             }
                             [ [NSColor blackColor] set];
                             [oval stroke];
                         }

This method may seem complicated, but it is actually quite straightforward. This is what it does:

  • Paints the entire view (background) white

  • Draws the word “MathPaper”

  • Draws those five black lines

  • Composites the PaperIcon in the upper-left corner

  • Creates an NSBezierPath for the star, sets the drawing color to black, and strokes (actually draws) the star

Tip

M_PI is the ANSI C-defined constant for pi, which is the ratio of a circumference of a circle to its radius.

There is only one method left to create — the tick: method that supports our animation:

  1. Insert the statements shown here in bold into the implementation of the tick: method in MathAnimation.m :

    - (IBAction)tick:(id)sender
    {    theta    += (2.0 * M_PI / FPS) / 2.0; // Spin every 2 seconds
                             fraction += ddelta;                   // Pulse every 5 seconds
                             if (fraction<0 || fraction>1) { // Do we need to reverse pulse?
                                 ddelta   = -ddelta;
                                 fraction += ddelta;
                             }
                             [self setNeedsDisplay:YES];
    }

All this method does is increment the theta and fraction variables and then display the updated view. If fraction is out of range, that means that it has gone too far, and it’s time to reverse direction. After the instance variables are updated, [self display] causes focus to be locked on the MathAnimation view and drawRect: to be called.

  1. Build and run MathPaper.

  2. Choose MathPaper About MathPaper and admire your animation.

  3. Quit MathPaper.

Pretty cool, eh? But you haven’t seen anything yet!

Adding an Easter Egg

What good would an About panel be without an Easter egg?[37] This Easter egg will show up when the MathPaper About MathPaper menu command is chosen with either the Shift or Option (Alt) modifier key held down. To implement this, we’ll need to modify our MathApplication class so that it can detect this menu/key combination and pass the information along to our MathAnimation class. Then we’ll need to modify the MathAnimation class to detect the fact that it should display an Easter Egg, and then to actually display it.

We will store an easterEgg flag in the MathApplication class to indicate whether a modifier key was held down when the user chose MathPaper About MathPaper.

  1. Insert the two lines shown here in bold into the MathApplication.h file:

    #import <Cocoa/Cocoa.h>
    
    @interface MathApplication : NSApplication
    {
        IBOutlet id aboutPanel;    BOOL easterEgg;
    }
    -(BOOL)doEasterEgg;
    
    @end

It’s actually fairly easy to find out if the Option key is down — just query the current event! We’ll do that in the following modification to the MathApplication implementation:

  1. Insert the lines shown here in bold into the MathApplication.m file:

    @implementation MathApplication
    
    - (void)orderFrontStandardAboutPanel:(id)sender
    {
        if (aboutPanel == nil) {
            [NSBundle loadNibNamed:@"AboutPanel" owner:self];
        }
        easterEgg =
                                    ([ [self currentEvent] modifierFlags]
                                     & (NSShiftKeyMask | NSAlternateKeyMask)) != 0;
        [aboutPanel makeKeyAndOrderFront:self];
    }
    -(BOOL)doEasterEgg
                            {
                                return easterEgg;
                            }
    
    @end

To finish this off, we need to modify the MathAnimation class to check the value of the easterEgg flag and act accordingly:

  1. Insert the following #import directive near the top of the MathAnimation.m file:

                            #import "MathApplication.h"
  2. Replace the first statement in the tick: method in the MathAnimation.m file with the statements shown here in bold:

    - (IBAction)tick:(id)sender
    {
        if ([ ((MathApplication *)NSApp) doEasterEgg] ) {
                                    theta -= (4.0 * M_PI / FPS) / 2.0; // Spin reverse faster
                                }
                                else {
                                    theta += (2.0 * M_PI / FPS) / 2.0; // Spin every 2 seconds
                                }
    
        fraction += ddelta; // Pulse every 5 seconds
        if (fraction<0 || fraction>1) {
            ddelta = -ddelta;
            fraction += ddelta;
        }
        [self display];
    }
  3. Build and run MathPaper.

  4. Hold down the Option key and choose MathPaper About MathPaper, then admire your Easter Egg!

  5. Quit MathPaper.



[37] An Easter egg is an undocumented frill inserted by playful programmers into shipping programs, often without the knowledge of their managers. In this context, it’s a surprise that users find in About panels by selecting key combinations or by other arcane methods. It has nothing to do with an egg!

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

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