Sunday, February 24, 2008

Building a Cocoa Calculator Application with XCode 3 for Mac (Part 3)

The long awaited part 3 of my Cocoa Calculator series is complete and has arrived in high style, or should I say resolution? This is by far the longest in the series, coming in at a whopping 25 minutes. I am also providing the full source code so that you guys don't have to feverishly copy code from the video but can refer to it afterwards for reference if you need help building out your very own!

Edit (1/6/09): Thanks to one of my readers, we now have a secondary mirror site for downloading the video. Download locations: Mirror 1 Mirror 2

//
//  CalcModel.h
//  SimpleCalc
//
//  Created by Chris Ball (chris.m.ball@gmail.com) on 1/17/08.
//  Copyright 2008, Chris Ball, Strainthebrain.Blogspot.Com. 
//  All rights reserved.
//

#import <Cocoa/Cocoa.h>

@interface CalcModel : NSObject {
    float running_total;
    char  sign_state;
    bool  first_call;
}

//Establishing some public-facing properties.
@property(readwrite) float running_total;
@property(readwrite) char  sign_state;
@property(readwrite) bool  first_call;

//Used for computing and maintaing the model data.
- (void) computeNewDisplayVal: (float) currDisplayVal;

@end


//
//  CalcModel.m
//  SimpleCalc
//
//  Created by Chris Ball (chris.m.ball@gmail.com) on 1/17/08.
//  Copyright 2008, Chris Ball, Strainthebrain.Blogspot.Com. 
//  All rights reserved.
//

#import "CalcModel.h"

@implementation CalcModel

//Implicity declaring the setters/getters for our properties.
@synthesize running_total, sign_state, first_call;

- (void) computeNewDisplayVal: (float) currDisplayVal {
    if (self.first_call) {
        self.running_total = currDisplayVal;
        self.first_call = NO;
    }
    else {
        switch (self.sign_state) {
            case 'a':
                self.running_total = self.running_total + currDisplayVal;
                break;
            case 'd':
                self.running_total = self.running_total / currDisplayVal;
                break;
            case 'm':
                self.running_total = self.running_total * currDisplayVal;
                break;
            case 's':
                self.running_total = self.running_total - currDisplayVal;
                break;
        }
    }
}

@end


//
//  CalcController.h
//  SimpleCalc
//
//  Created by Chris Ball (chris.m.ball@gmail.com) on 1/17/08.
//  Copyright 2008, Chris Ball, Strainthebrain.Blogspot.Com. 
//  All rights reserved.
//

#import <Cocoa/Cocoa.h>
#import "CalcModel.h"

@interface CalcController : NSObject {
    IBOutlet id calc_display;
    IBOutlet id a_button;
    IBOutlet id s_button;
    IBOutlet id m_button;
    IBOutlet id d_button;
    CalcModel*    calc_model;
}

//Mathematical button events.
- (IBAction) push_add:        (id) sender;
- (IBAction) push_divide:    (id) sender;
- (IBAction) push_multiply:    (id) sender;
- (IBAction) push_subtract:    (id) sender;

//Numeric button events.
- (IBAction) push_zero:        (id) sender;
- (IBAction) push_one:        (id) sender;
- (IBAction) push_two:        (id) sender;
- (IBAction) push_three:    (id) sender;
- (IBAction) push_four:        (id) sender;
- (IBAction) push_five:        (id) sender;
- (IBAction) push_six:        (id) sender;
- (IBAction) push_seven:    (id) sender;
- (IBAction) push_eight:    (id) sender;
- (IBAction) push_nine:        (id) sender;

//Internal functions.
- (void)     push_number:    (id) sender;
- (BOOL)     sign_pushed;
- (void)     check_calc_model;

//Other button events.
- (IBAction) push_clear:    (id) sender;
- (IBAction) push_decimal:    (id) sender;
- (IBAction) push_equal:    (id) sender;

@end


//
//  CalcController.m
//  SimpleCalc
//
//  Created by Chris Ball (chris.m.ball@gmail.com) on 1/17/08.
//  Copyright 2008, Chris Ball, Strainthebrain.Blogspot.Com. 
//  All rights reserved.
//

#import "CalcController.h"
#import <RegexKit/RegexKit.h>

@implementation CalcController

- (IBAction) push_add:        (id) sender {
    if ([m_button state] == NSOnState || [s_button state] == NSOnState || [d_button state] == NSOnState) {
        [a_button setState:NSOffState];
    }
    else {
        [self check_calc_model];
        
        if (!calc_model.first_call)
            [self push_equal:0];
        
        calc_model.sign_state = 'a';
        [calc_model computeNewDisplayVal:[calc_display floatValue]];
        [calc_display setFloatValue:calc_model.running_total];
    }
}
- (IBAction) push_divide:    (id) sender {
    if ([a_button state] == NSOnState || [s_button state] == NSOnState || [m_button state] == NSOnState) {
        [d_button setState:NSOffState];
    }
    else {
        [self check_calc_model];
        
        if (!calc_model.first_call)
            [self push_equal:0];

        calc_model.sign_state = 'd';
        [calc_model computeNewDisplayVal:[calc_display floatValue]];
        [calc_display setFloatValue:calc_model.running_total];
    }
}
- (IBAction) push_multiply:    (id) sender {
    if ([a_button state] == NSOnState || [s_button state] == NSOnState || [d_button state] == NSOnState)
        [m_button setState:NSOffState];
    else {
        [self check_calc_model];

        if (!calc_model.first_call)
            [self push_equal:0];

        calc_model.sign_state = 'm';
        [calc_model computeNewDisplayVal:[calc_display floatValue]];
        [calc_display setFloatValue:calc_model.running_total];
    }
}
- (IBAction) push_subtract:    (id) sender {
    if ([a_button state] == NSOnState || [m_button state] == NSOnState || [d_button state] == NSOnState)
        [s_button setState:NSOffState];
    else {
        [self check_calc_model];

        if (!calc_model.first_call)
            [self push_equal:0];

        calc_model.sign_state = 's';
        [calc_model computeNewDisplayVal:[calc_display floatValue]];
        [calc_display setFloatValue:calc_model.running_total];
    }
}

- (IBAction) push_zero:        (id) sender {
    [self push_number:sender];
}
- (IBAction) push_one:        (id) sender {
    [self push_number:sender];
}
- (IBAction) push_two:        (id) sender {
    [self push_number:sender];
}
- (IBAction) push_three:    (id) sender {
    [self push_number:sender];
}
- (IBAction) push_four:        (id) sender {
    [self push_number:sender];
}
- (IBAction) push_five:        (id) sender {
    [self push_number:sender];
}
- (IBAction) push_six:        (id) sender {
    [self push_number:sender];
}
- (IBAction) push_seven:    (id) sender {
    [self push_number:sender];
}
- (IBAction) push_eight:    (id) sender {
    [self push_number:sender];
}
- (IBAction) push_nine:        (id) sender {
    [self push_number:sender];
}

- (void)     push_number:    (id) sender {
    if ([self sign_pushed]) {
        [a_button setState:0];
        [d_button setState:0];
        [m_button setState:0];
        [s_button setState:0];
        [calc_display setStringValue:@""];
    }
    
    if ([[calc_display stringValue] compare:@"0"] && !calc_model.first_call)
        [calc_display setStringValue:[[calc_display stringValue] stringByAppendingString:[sender title]]];
    else 
        [calc_display setStringValue:[sender title]];
}
- (BOOL)     sign_pushed {
    if ([a_button state] == NSOnState || [s_button state] == NSOnState || [m_button state] == NSOnState || [d_button state] == NSOnState)
        return YES;
    else return NO;
}
- (void)     check_calc_model {
    @try {
        if (calc_model == nil) {
            calc_model = [[CalcModel alloc] init];
            calc_model.first_call = YES;
        }
    }
    @catch (NSException *ex) {
        NSLog(@"check_calc_model: Caught %@: %@", [ex name], [ex reason]);
    }
}

- (IBAction) push_clear:    (id) sender {
    if (calc_model != nil) {
        [calc_model release];
        calc_model = nil;
    }
    
    [calc_display setIntValue:0];
    
    if ([self sign_pushed]) {
        [a_button setState:0];
        [d_button setState:0];
        [m_button setState:0];
        [s_button setState:0];
    }
}
- (IBAction) push_decimal:    (id) sender {
    if ([self sign_pushed]) {
        [a_button setState:0];
        [d_button setState:0];
        [m_button setState:0];
        [s_button setState:0];
        [calc_display setStringValue:@""];
    }
    
    if (![[calc_display stringValue] isMatchedByRegex:@"[.]"])
        [calc_display setStringValue:[[calc_display stringValue] stringByAppendingString:@"."]];
}
- (IBAction) push_equal:    (id) sender {
    [self check_calc_model];
    [calc_model computeNewDisplayVal:[calc_display floatValue]];
    [calc_display setFloatValue:calc_model.running_total];
    calc_model.first_call = YES;
}

@end


Saturday, February 9, 2008

Remote Desktop Protocol (RDP) From Mac OS Leopard to Vista Ultimate

I've been facing a dilemma at home. I am absolutely in love with my Mac and all its glory, but I also find myself needing to do Windows .NET development. Sure, there's boot camp, but that makes the assumption that I want to get into the game of running multiple operating systems on one physical machine. I'd prefer to be beat over the head repeatedly, given the choice.

There's also the option of Parallels for running a virtual machine. While that might sound great, assuming I don't mind paying more money for Parallels, Vista is such a big beast that it would likely be somewhat painful to go down this road, unless of course I'm willing to sit back and pop some popcorn in between application loads.

I then pondered if it would be possibly to RDP from my Mac to my Vista machine (in this case my laptop on the local home network is now the ugly stepchild that doesn't get the attention it deserves...Macs will undoubtedly cause this imbalance, sorry Windows). To my surprise, I was able to get this smoothly working without issue in a matter of minutes.

1. Enable RDP on your Vista machine. While I don't have concrete information, I have heard that the lower end version of Vista such as Vista Home may not support RDP inbound, so make sure your version supports this feature.

2. Install this lovely free RDP client from Microsoft, custom built for the good old Mac (http://www.microsoft.com/mac/products/remote-desktop/default.mspx).



3. Load the application once it's installed and head straight for the preferences - I made sure to change it so that it would go "full screen" on the display tab, and I also upped the colors to "millions".

4. Lastly, key in your LAN ip address (i.e. 192.168.x.x) and your windows credentials and you should be all set!

While this will vary from person to person, I was exceedingly pleased with this arrangement, as my screencasting process allows me to capture all of my Vista interactions, just as I capture my Mac interactions. I'm also now able to do both Objective-C / Cocoa development on my Mac as well as C# / 3.5 .NET on my PC. Talk about a streamlined multi-OS setup!

Saturday, February 2, 2008

Building a Cocoa Calculator Application with XCode 3 for Mac (Part 2)

Here it is, part 2 of my "Build a Calculator with XCode" series. Just as before, the technologies involved in this screencast include: XCode 3, Objective-C, the Cocoa Framework, and Interface Builder 3. This part covers some new concepts including the creation of controller classes, actions, outlets, and binding actions/outlets to different components of your GUI interface.

Once mastered, the knowledge gained from this part in the series will help viewers easily create their very own applications with event handling fully implemented.

I thoroughly enjoyed putting together this screencast. I have been getting more and more comfortable with video production lately and decided I wanted to try incorporating some soothing Chopin music in the background. Please let me know if you find it distracting or tastefully applied!



In the next part in the series, I will be taking you through the actual implementation code necessary to make this calculator come to life. Until next time...