Jul 31 2008

The first day of Reckoning

Categories: Computers, Programming
Tags: ,

I’ve been poking at Cocoa development for quite a while now. That said, I haven’t finished anything I’ve started as of yet. I start a lot of things and then move to something else. Stacy thinks I have a problem. To that end, I’ve started a small application for Stacy. Nothing special, it’s just designed to do simple tracking of invoices and payments for her freelance business.

Since I’ve actually got someone who wants to use the application I’ve actually got a reason to finish it. I’ve imported the initial application, Reckoning, into GitHub for all the world to marvel over my ability to click icons in Xcode and Interface Builder.

Since I’m working on the application I may as well write about it a bit as well. Hm, I think I mentioned something about not being able to do just one thing?

With that in mind, let’s begin.

As mentioned, Reckoning is a simple application to track invoices and payments. There are four main items that will be tracked: Clients, Invoices, Line Items and Payments. To get this party started fire up Xcode and create a new Core Data Application. (This maybe better as a Core Data Document based Application but I picked Core Data Application.)

I got started by developing the data model (well, developing the data model after reformatting the default code to match my personal style). Double click on the Reckoning_DataModel.xcdatamodel file to launch the data modeling tool.

Before we get into the data model tool I guess I should tell you a bit more about my data. The system will have the following structure:

  • Client

    • name :: string (default: New Client)
    • invoice_period :: integer (default: 30)
    • invoice_notes :: string
    • contact_name :: string
    • contact_email :: string
    • address :: string
    • city :: string (default: Toronto)
    • province :: string (default: Ontario)
    • postal_code :: string
    • country :: string (default: Canada)
    • invoices :: to-many relationship to the Invoice table
  • Invoice

    • date :: date (default: “today”) [note the quotes matter for the date fields]
    • due_date :: date (default: “today”)
    • notes :: string
    • taxes_saved :: float
    • total_invoiced :: float
    • total_received :: float
    • client :: relationship to the Client table
    • line_items :: to-many relationship to the LineItem table
    • payments :: to-many relationship to the Payment table
  • Line Items

    • date :: date (default “today”)
    • description :: string
    • hours_worked :: float
    • rate :: float
    • invoice :: relationship to the Invoice table
  • Payment

    • amount :: float
    • date :: date (default: “today”)
    • invoice :: relationship to the Invoice table

You’ll end up with something similar to the figure below in the data modeler.

Reckoning Data Model

Reckoning Data Model

About the only item not mentioned in the above is the regular expression used to validate the email address. After a bit of Googling I found a great resource on matching email addresses and entered ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$ as the contact_email regular expression.

Simple enough. Although, writing this I realized I forgot the default_rate field in the Client table. Oh well, something to do tomorrow.

The next step in our application is to start the user interface. For the first revision I’ve created the ability to add clients, invoices and payments. You can’t edit many of the details yet but the basic creation is there. (I’ll actually have to write some code to get that part done.)

Closing the data modeler tool we can now click on the MainMenu.xib (or .nib if you’re using an earlier version of Xcode) to launch Interface Builder.

Currently everything happens in the main Reckoning window. I’ll need to add sheets and property dialogs and things as time goes on but for now, one window.

Main Reckoning Window

Main Reckoning Window

The three fields in the Income section are set to non-editable text fields with a number formatter attached. All three number formatters are set to the currency style. (These three boxes do nothing in the current version.) The Due Date and Date columns have a date formatter attached. The Amount column has a currency formatter similar to the Income fields.

About the only other thing that needs explanation is the 7 outstanding invoices. This line will dynamically update in the final version of the app to give information on the outstanding and overdue invoices for each client. I’m also hoping to be able to colour the table cell text differently depending on the invoice status.

The Edit buttons are currently unused. The + and - buttons are used to add and remove items from their section.

Ok, lets see how much of this thing we can hookup without diving into any code.

First up, we need some controllers. We have three Core Data tables we’re going to want to pull data from for this first iteration. To that end we’ll drag three array controllers from the library down to the information window. Set the name of one to Client Controller, one to Invoice Controller and one to Payment Controller.

Information Window

Information Window

In the inspector set the mode of all three to Entity and set the Entity Name to the corresponding entity name in our data model (Client, Invoice and Payment respectively).

Next up are the bindings for our array controllers. We’ll start with the Client Controller.

Client Controller

Client Controller Bindings

This is the simplest controller to hookup. We just have to tell the controller that it will be getting its Managed Object Context from the Reckoning_AppDelegate using the mangedObjectContext Model Key Path. The Reckoning_AppDelegate was created for us when we created the application.

Next up is the Invoice Controller.

Invoice Controller Bindings

Invoice Controller Bindings

You’ll notice an extra section to our bindings for this one. The bottom, Managed Object Context binding, is setup exactly the same as our Client Controller. The extra binding, the Content Set binding, allows us to bind the content of the Invoice Controller to the currently selected item in the Client Controller. This way we’ll only show the invoices for the selected client. So, you can see we’ve bound to the Client Controller with a Controller Key of selection and a Model Key Path of invoices. What this is doing is taking the current selection from the Client Controller and retrieving the array returned from the invoices method. The invoices method was created for us by Core Data based on the invoices relationship we created in our model.

Finally the Payment Controller.

Payment Controller Binding

Payment Controller Binding

This one is very similar to the Invoice Controller binding. We just bind to the Invoice Controller instead of Client Controller and we want the payments key path in the invoice model.

Sweet, our arrays are now hooked into our Core Data model and are ready to populate our tables. We’ll bind the Clients table first.

Client Table Bindings

Client Table Bindings

Nothing too complicated in there. We want to bind the Value binding for the Table Column (make sure you have the Table Column and not the Table View or Text Field Cell or the Scroll View). We bind to the Client Controller as it’s the client information we want to display. We use the Controller Key of arrangedObjects which will return the objects in the given controller. I setup the cells to display the name of the client so we use the name key path into the Client object. I’ve also checked the Continuously Updates Value field. I’m not sure what this does but one of the tutorials mentioned it so I use it. If you know, please let me know.

We now move onto the Invoicing table.

Invoicing Client Column Bindings

Invoicing Client Column Bindings

This is similar to our Client bindings from above. We want to bind to the Invoice Controller and I’m using a different model key. The model key is set to client.name. This allows me to retrieve the name field of the client associated with this invoice. (Remember we have a relationship from Invoice to Client in our model.) You can use the dot notation to chain these methods together to pull out the exact information you require.

The Due Date column is the same except I bind the model key to due_date.

Finally the Payment table is basically the same bindings. For the Client column I bind the model key to invoice.client.name to pull out the client name for the invoice associated to this payment. The other fields just bind to date and amount respectively.

Cool. Ok, we can now display the data stored in our Core Data model. I guess it would help if we could actually add some data. If you haven’t noticed, pretty much everything we’ve done has been fairly easy (assuming you know the right syntax for the model keys and the controller keys.) Hooking up add and remove is no different.

If you option-click on the + symbol in the Clients section you can drag (and a blue line will appear) down to the Client Controller. Releasing the mouse you’ll see a popup. Select the add: entry. Do the same option-click and drag from the - button and select the remove: entry.

Follow the same procedure for the buttons in the Invoicing and Payments sections.

Save the file in Interface Builder, switch back to Xcode and run the application. You should be able to add clients, invoices and payments and hitting apple-s will save the file. If you close Reckoning and re-open you should see your created data populating the tables.

Not bad for not actually writing a line of code, eh?

Next time we’ll be attempting to hookup the Edit buttons for the client and invoicing information.


May 12 2008

Cocoa, Core Animation, Text Size, a Lost 2 Hours

Categories: Programming
Tags: ,

So, as you can tell by the title I’ve been struggling with a font size issue when working for Core Animation. I’ve been trying to call [NSFont boldSystemFontOfSize:144.0f] to get a font of a given size and then setting it on the layer through the font property.

This hasn’t been working. I’ve been struggling, using other ways to query the font. Nothing was working.

Eventually, I figured out, if you’re working with a CALayer you have to set the size through the fontSize property. If you don’t set this property you seem to always get the default font size of 36.0.

I also discovered that you can pass a font name to the font property. So, doing something like layer.font = @"Helvetica" works.


May 09 2008

Going Fullscreen With Medium

Categories: Everburning, Programming
Tags: ,

So, I’ve started to write another Mac media application. This one is called Medium. I figure I might as well write some of this stuff down as I do it as I’m learning most of it along the way. If you’re looking for Medium you can get the source from the repository hosted at GitHub. There is also a bug tracker setup thanks to LightHouse.

Given that I’ve started this on about 4 separate occasions I’ve got a bit of random code sitting around that I’ll probably recycle during this exercise. One of the bits is to switch between fullscreen and windowed mode for the application.

The first version of this I had was pretty complicated and convoluted. Thankfully, with the release of Leopard (or was it one of the Tiger point releases?) going fullscreen in a Cocoa application got a lot easier.

Here’s what we’re going to be doing. We’re going to create an application that can go fullscreen. We’ll use the defaults controller to store if it should start fullscreen or windowed. If the users resizes the window in windowed mode we’ll remember the window size for the next restart. When we switch to fullscreen or to windowed mode we’ll fade the screen to black and back again. We’ll also setup a default property for picking the screen the application should appear on (although, as I don’t have a second monitor pulled into my PowerBook at the moment this will remain untested).

With that, on with the show.

We’ll get started by creating a new XCode project. We’ll be using a Cocoa Application as the basis for the project. I’m going to call mine MediumFS, you’re free to use what you want.

We’re going to be using three objects for this application:

  1. MediumApplication — The NSApplication subclass
  2. MediumController — The controller
  3. MediumConfig — Our configuration class

Lets go ahead and ctrl-click on the classes item in the sidebar and select Add -> File …. We’ll then select the Objective-C class for each item.

With our files created we’re going to setup the application to use our MediumApplication as the Principle Class. The first thing we need to do is change the parent class of MediumApplication to be NSApplication instead of NSObject. That’s actually the only change we’ll need to make in MediumApplication.h. You should end up with something similar to:

#import 

@interface MediumApplication : NSApplication
{
}
@end

Now, in the Resourses section open the Info.plist file. Under the key NSPrincipleClass change the string value to be MediumApplication instead of NSApplication.

With that we’ve setup our MediumApplication class to be the main class in Principle Class for the application. We’ll now fill in some application code to handle processing key events so we can pull out the apple-f key press.

There are two methods we’ll implement in MediumApplication: - (void)sendEvent:(NSEvent *)ev and - (BOOL)handleKeyDownEvent:(NSEvent *)ev. The sendEvent will be overriding the parent class implemention. handleKeyDownEvent is a private helper method.

- (void)sendEvent:(NSEvent *)ev
{
    int handled = FALSE;
    if (([ev type] == NSKeyDown) && ([self handleKeyDownEvent:ev]))
        handled = TRUE;

    if (!handled)
        [super sendEvent:ev];
}

sendEvent is pretty simple. If we’re dealing with a NSKeyDown event we’ll pass the event to our handleKeyDownEvent method. If handleKeyDownEvent processes the event it will return TRUE. If this isn’t the case we’ll pass the event up to our parent, NSApplication in this case.

- (BOOL)handleKeyDownEvent:(NSEvent *)ev
{
    BOOL handled = FALSE;
    NSString *notice = nil;
    unichar key = [[ev charactersIgnoringModifiers] characterAtIndex:0];

    /* apple-f for fullscreen */
    if ((key == 0x66) && ([ev modifierFlags] & NSCommandKeyMask))
        notice = @"Medium/Notice/Fullscreen";

    if (notice)
    {
        [[NSNotificationCenter defaultCenter]
                            postNotificationName:notice
                            object:nil
                            userInfo:nil];
        handled = TRUE;
    }
    return handled;
}

When handleKeyDownEvent is called we’ll pull the key pressed out of the event with charactersIgnoringModifiers: and we’ll grab the first character that’s returned. If that character is 0×66 or f and the NSCommandKeyMask modifier is set in the event we’ve got our apple-f.

With that, if we’ve found something to send a notification about we’ll post it to the NSNotificationCenter for anyone that’s interesting to pick up on.

That’s it for our application class.

Moving on we’ll setup or configuration object, MediumConfig. MediumConfig is basically a wrapper around NSUserDefaults which allows me to get the defaults with function calls instead of having the keys everywhere in my application.

#import 

@interface MediumConfig : NSObject
{
    NSUserDefaults *defaults;
}

+(MediumConfig *)instance;

-(int)screen;
-(void)setScreen:(int)screen;

-(BOOL)fullscreen;
-(void)setFullscreen:(BOOL)fullscreen;

-(void)windowDimensionsWidth:(int *)width height:(int *)height;
-(void)setWindowDimensionsWidth:(int)width height:(int)height;
@end

The MediumConfig object is a singleton so we provide an instance: method to retrieve the object. The rest of the class are just the accessors for the different configuration variables we care about.

#import "MediumConfig.h"

#define MediumScreenKey @"Medium/Screen"
#define MediumFullscreenKey @"Medium/Fullscreen"
#define MediumWindowDimensionWidth @"Medium/Window/Dimension/Width"
#define MediumWindowDimensionHeight @"Medium/Window/Dimension/Height"

static MediumConfig *medium_config = nil;

@implementation MediumConfig

+(MediumConfig *)instance
{
    if (medium_config) return medium_config;
    return (medium_config = [[MediumConfig alloc] init]);
}

-(id) init
{
    if ((self = [super init]))
    {
        NSMutableDictionary *defaultValues = [NSMutableDictionary dictionary];

        [defaultValues setObject:[NSNumber numberWithBool:FALSE]
                          forKey:MediumFullscreenKey];
        [defaultValues setObject:[NSNumber numberWithInt:0]
                          forKey:MediumScreenKey];

        /* setting these to -1 will cause the code to calcuate a good window size */
        [defaultValues setObject:[NSNumber numberWithInt:-1]
                          forKey:MediumWindowDimensionWidth];
        [defaultValues setObject:[NSNumber numberWithInt:-1]
                          forKey:MediumWindowDimensionHeight];

        [[NSUserDefaults standardUserDefaults] registerDefaults:defaultValues];

        defaults = [NSUserDefaults standardUserDefaults];
    }
    return self;
}

-(int)screen
{
    return [defaults integerForKey:MediumScreenKey];
}

-(void)setScreen:(int)screen
{
    [defaults setInteger:screen forKey:MediumScreenKey];
}

-(BOOL)fullscreen
{
    return [defaults boolForKey:MediumFullscreenKey];
}

-(void)setFullscreen:(BOOL)fullscreen
{
    [defaults setBool:fullscreen forKey:MediumFullscreenKey];
}

-(void)windowDimensionsWidth:(int *)width height:(int *)height
{
    if (width) *width = [defaults integerForKey:MediumWindowDimensionWidth];
    if (height) *height = [defaults integerForKey:MediumWindowDimensionHeight];
}

-(void)setWindowDimensionsWidth:(int)width height:(int)height
{
    [defaults setInteger:width forKey:MediumWindowDimensionWidth];
    [defaults setInteger:height forKey:MediumWindowDimensionHeight];
}

@end

The implementation is also pretty simple. We set a few default values into our user defaults hash and the accessors are just wrappers around the calls into the NSUserDefaults object. Nothing special to see here. Oh, as a side note, I’m allowing the user to pass a nil into the windowDimensionsWidth:height: method. I wasn’t sure if you’d always need both values and this just seemed easier in the long run.

Finally, onto the interesting bit, MediumController. We’re going to store references to the main window, a CGDisplayFadeReservationToken which I’ll discuss later and to our configuration.

#import 
#import "MediumConfig.h"

@interface MediumController : NSObject
{
    NSWindow *win;
    CGDisplayFadeReservationToken tok;  /** Fade out/in token */

    MediumConfig *cfg;
}

@end

With the header file created we’re going to do some work in Interface Builder. Double click on the MainMenu.nib file in the Resources section.

When interface launches you’ll see a few windows popup. One of which will be the control window (at least, that’s what I call it).

We don’t actually need the Window (window) item so we can click on that and hit delete. We’re going to be creating the window manually in the controller.

The next step is to create an instance of our MediumController in Interface Builder. This is done by dragging an Object controller from the library to our control window.

If the inspector isn’t open already click on the new object and press shift-apple-i. In the inspector press apple-6 to make sure you’re in the identity section. Set the Class field to MediumController.

In the control window ctrl-click on the File’s Owner object and drag the mouse over the Medium Controller object. You should have a popup appear. Click on the delegate entry. We’ll now receive notifications in our controller as the application changes state.

That’s it for our work in Interface Builder. Save your changes and close.

Given that MediumController is the large part of our code I’m going to go through it piece meal instead of giving a full listing.

#import "MediumController.h"

@interface MediumController (ControllerDelegates)
- (void)applicationDidFinishLaunching:(NSNotification *)notice;
- (void)applicationWillTerminate:(NSNotification *)notice;
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app;
@end

@interface MediumController (WindowDelegates)
- (void)windowDidResize:(NSNotification *)notice;
@end

@interface MediumController (WindowManipulation)
- (void)fullscreenNotice:(NSNotification *)notice;
- (void)changeFullscreenState;
- (NSScreen *)screen;
- (void)fadeOut;
- (void)fadeIn;
@end

We start by creating a bunch of method declarations. I like to group my methods into categories as I think it keeps things cleaner. As you can see we’re going to implement some Controller Delegates, Window Delegates and Window Manipulation functions.

@implementation MediumController

- (id)init
{
    if ((self = [super init]))
    {
        cfg = [[MediumConfig instance] retain];

        /* catch the full screen events */
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
        [nc addObserver:self
               selector:@selector(fullscreenNotice:)
                   name:@"Medium/Notice/Fullscreen"
                 object:nil];

    }
    return self;
}

@end

Our init: method is pretty simple. We grab, and retain an instance of the MediumConfig singleton. We’ll be using this throughout our code later. We then add an observer for the Medium/Notice/Fullscreen event that we setup in our MediumApplication code. Whenever the given event is fired the fullscreenNotice: method will be executed.

#pragma mark Controller Delegates
@implementation MediumController (ControllerDelegates)

- (void)applicationDidFinishLaunching:(NSNotification *)notice
{
    NSRect screenFrame = [[self screen] frame];
    int width = -1, height = -1;

    [cfg windowDimensionsWidth:&width height:&height];
    if ((width < 0) || (height < 0))
    {
        width = screenFrame.size.width * 0.75;
        height = screenFrame.size.height * 0.75;
    }

    NSRect frame = NSMakeRect(((screenFrame.size.width - width) / 2),
                              ((screenFrame.size.height - height) / 2),
                              width, height);

    win = [[NSWindow alloc] initWithContentRect:frame
                styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)
                  backing:NSBackingStoreBuffered
                    defer:NO
                   screen:[self screen]];
    [win setDelegate:self];
    [win setTitle:@"MediumFS"];

    /* XXX make this our real view */
    NSView *view = [[NSView alloc] initWithFrame:[win frame]];
    [win setContentView:view];

    if ([cfg fullscreen]) [self changeFullscreenState];

    /* do this after we've gone fullscreen */
    [win makeKeyAndOrderFront:self];
}

Once the application has launched we’ll, as the delegate, get a applicationDidFinishLaunching: notification. We’ll use this to create our main window by hand.

Our window will be set to 75% of the screen width and height if the user hasn’t already site a width and height. To do this we’ll first need to get the screen frame. We’ll see the screen method later, just rest assured it returns an NSScreen object we can work with. We’ll then check in our configuration if the user has set a width and height previously. If not, we use the screen frame to calculate the width and height.

With that done we’ll create the frame for our window with NSMakeRect. We do some simple calculations to place the window in the middle of the screen and use the width and height we calculated earlier.

We can now create our window. We use the frame we just calculated and apply a style mask of NSTitledWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask. This way we can have a title, can minimize the window and allow the user to resize the window. We set the backing to NSBackingStoredBuffered and the screen to the screen returned from the screen method.

With the window created we set the controller to be the delegate of the window. This way we can catch certain window events which we’ll see in a bit. setTitle: just sets the window title to @”MediumFS”.

Since I don’t actually have anything to display in this window I just create an NSView and set it as the content view of my window. You can add your actual views as needed.

With the window created and our content in place we check if we should be fullscreen by querying the MediumConfig object. If we should be fullscreen we transition by calling changeFullscreenState. With all that done we give the window the focus with makeKeyAndOrderFront:.

Phew, OK, that was a lot of work right off the bat. Don’t worry, things will get shorter from here on out.

- (void)applicationWillTerminate:(NSNotification *)notice
{
    if ([cfg fullscreen]) [self fadeOut];

    /* hide the window so it doesn't flicker on fade in */
    [win orderOut:nil];
    [win release];
    if ([cfg fullscreen]) [self fadeIn];

    [cfg release];
}

When the user hits apple-q or otherwise terminates the application our applicationWillTerminate notification will be triggered. We first check if we’re in fullscreen mode. If so, we fade the screen out with fadeOut. We then use orderOut: to hide the window. We don’t want it to flicker back into existence when we fade the screen back in. With the window hidden we can release it as we no longer need it. Then, as before, if we’re fullscreen we fade back in with fadeIn. We then release the config object as we’re done with it.

- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app
{
    return YES;
}

@end

The final method in our controller delegates section is applicationShouldTerminateAfterLastWindowClosed we’re just returning a simple YES here so when our window gets closed the application will terminate. Nothing special, move along.

We’ll now move into the window delegate methods, of which there is only one.

#pragma mark Window Delegates
@implementation MediumController (WindowDelegates)

- (void)windowDidResize:(NSNotification *)notice
{
    NSRect frame = [win frame];
    [cfg setWindowDimensionsWidth:frame.size.width height:frame.size.height];
}

@end

After the window has been resized the windowDidResize: notification will be sent. We use this to update the window width and height in MediumConfig so we can open the window to the same size on the next launch. Simple enough.

Our last set of functions I’m put under the name Window Manipulation.

#pragma mark Window Manipulation
@implementation MediumController (WindowManipulation)

- (void)fullscreenNotice:(NSNotification *)notice
{
    [self changeFullscreenState];
    [cfg setFullscreen:(![cfg fullscreen])];
}

As we saw above when the Medium/Notice/Fullscreen is sent it will trigger the fullscreenNotice: method. We simply call changeFullscreenState and invert the current fullscreen flag in MediumConfig. Short and sweet.

Finally we get to what everyone actually wants to see, the fullscreen method.

- (void)changeFullscreenState
{
    NSView *view = [win contentView];
    NSDictionary *opts = [NSDictionary dictionaryWithObjectsAndKeys:
                            [NSNumber numberWithBool:NO], NSFullScreenModeAllScreens, nil];

    [self fadeOut];

    if ([view isInFullScreenMode])
        [view exitFullScreenModeWithOptions:opts];
    else
        [view enterFullScreenMode:[self screen] withOptions:opts];

    [self fadeIn];
}

As I mentioned earlier fullscreening got a lot simpler at some point. First thing we do is grab our content view that we set into the main window. It’s on this view that we’ll be changing the fullscreen state.

We’re going to be passing a dictionary of options into the fullscreen methods. In this case I’m just setting one key into the dictionary, NSFullScreenModeAllScreens which I’m setting to NO as I only want to go fullscreen on one screen.

With that created we’re calling fadeOut to fade the screen to make a nice transition.

The screen is blanked and we’re posed to go fullscreen. Since this function is just switching back and forth the first thing we do is query the current fullscreen state of the view with isInFullScreenMode if this is TRUE we’ll be going back to windowed mode. Switching to windowed mode is as simple as calling exitFullScreenModeWithOptions: on our view. Similarly, going to fullscreen mode is as simple as calling enterFullScreenModel:withOptions: and passing in the screen we want to be fullscreen on. That’s it. Pretty simple eh?

With the fullscreen mode changed we’ll use fadeIn: to fade the screen back from black.

Three more helper methods and we’re done.

/**
 * Return the screen that we should be displaying upon
 */
- (NSScreen *)screen
{
    NSArray *screens = [NSScreen screens];

    /* if we've only got one screen then return it */
    if ([screens count] < = 1) return [NSScreen mainScreen];

    /* try to use the screen set by the user */
    int screen = [cfg screen];
    if (screen > [screens count]) screen = [screens count] - 1;
    else if (screen < 0) screen = 0;

    return [screens objectAtIndex:screen];
}

The screen method will return a screen to display on. This will get all the screens by calling [NSScreen screens]. Then, if we only have one screen, it’ll return that. Otherwise we check if the user has configured a screen to display on or return the first screen in the list.

The fade methods are both pretty similar.

- (void)fadeOut
{
    CGAcquireDisplayFadeReservation(25, &tok);
    CGDisplayFade(tok, 1.5, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, TRUE);
}

- (void)fadeIn
{
    CGDisplayFade(tok, 1.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, TRUE);
    CGReleaseDisplayFadeReservation(tok);
}

@end

On fadeOut we acquire a reservation token and tell the screen to fade. We fade from kGCDisplayBlendNormal to kGCDisplayBlendSolidColor and provide the 0, 0, 0 black colour. fadeIn works in basically the opposite order. We fade from kCGDisplayBlendSolidColor to kCGDisplayBlendNormal and start from 0, 0, 0, black. Once faded we release our reservation token.

That, as they say, is that. If you build and run you should be able to hit apple-f to switch to and from fullscreen mode.

Cool, eh?

MediumFS contains the full source for the application.


Apr 19 2008

Cocoa is hot

Categories: Programming
Tags:

I was playing with Cocoa a bit more today, looking to parse and display information from the Apple Trailers feed. This involved a few things, parsing the XML, loading the images and playing the video. Let me just say, Cocoa makes this stupidly easy.

Pulling down the XML feed was a simple matter using the NSXMLDocument object. I made one extension from a mactech article here which was to add the childNamed: method. Using this it’s really easy to pull the nodes out of the XML tree and build my objects. I don’t have to deal with the actual parsing and storing string values as the nodes are processed.

With that out of the way we move onto the image loading. If I tried to load up all the images at the parsing of the document the application loading would be too slow (trust me, I did this originally to make sure everything worked). After a bit of poking this is also trivial to deal with in Cocoa. You can create a NSImage with a reference to a URL using initByReferencingURL: which will lazy load the image. In other words, only load the image when there is an attempt to draw it. This works nicely with the IKImageBrowserView as, I believe, it only loads images for what’s on screen. (If you didn’t want to lazy load you could use initWithContentsOfURL: which will load the image immediately.)

Displaying the images is trivial using the IKImageBrowserView as seen in my previous MyTube image wall article.

The last bit was displaying the movie. Again, this was pretty simple. I just added a QTMovieView to my application then it’s just a matter of loading the movie using the QTMovie movieWithURL:error: method. This will load the movie from the given URL. I can then set the movie into my view and play it. I can also inform the movie view to maintain the aspect ratio of the movie by calling setPreserveAspectRation: which is quite nice.

So, all in all, I must say, the more I play with Cocoa the more it seems they’ve wrapped a lot of the tedious work up into a nice package for you. I wonder at what point the bubble is going to burst for me?


Apr 18 2008

MyTube, searching for fun and profit

Categories: Programming
Tags: ,

On to our next installment of the MyTube saga. This time I’m taking a look at adding query capabilities to the application. The queries will run against all the videos but it should be pretty simple to update to make the queries run against the category selector as well. I’ll mention this when we get there.

Before I started into the query stuff I cleaned up a few of the loose ends that have started to dangle since the start of this series. The first one was my lame WebView bug. If you’ll remember in imageBrowser:cellWasDoubleClickedAtIndex: I had a [web reload]; call in there. You’ll want to remove that. What I ended up doing was setting the new video then reloading the old one. This was the problem where it wasn’t always showing the correct video.

The other change I made was to cleanup the init method of MyTubeIKBrowserItem. I updated the function to have a the initVideo:image: signature. A bit cleaner and simpler then before. When I need the image ID or title I just grab it from the video object with [[video title] stringValue].

Those were the main, minor, changes I made so let’s get this party started. First off add IBOutlet NSTextField *search_box; to our MyTubeAppController interface. We’ll be using this to get the search value entered into the interface.

Now, double click on MainMenu.nib to start Interface Builder. From the Views & Cells section of the Library drag an NSTextField to the application window. With the text field selected open up the Inspector (apple-i). On the Information pane (apple-1) change the Action to Send on Enter Only. This way our backend code will only be called when the user hits enter. (Don’t worry, we’re also going to work if they press the Get Videos button). Oh, and that was the other change I made, I renamed the button to Get Videos.

With the text field still selected go to the Attributes pane (apple-5) of the Inspector. In the Sent Actions section we want to grab the circle beside the selector entry. We’ll then drag the mouse to the My Tube object in the control window. When we release the mouse we’ll select grab: in the window that pops up. This hooks the text field up so when the user hits enter it will call our grab method.

Now, in the control window right click on the My Tube object and drag the circle beside search_box to our new text field. We can now save and close Interface Builder.

All of our code changes will be in the grab: method. We’ll be re-using the same selectors to display the images as the combo box feed methods use.

- (IBAction)grab:(id)sender
{
    GDataServiceGoogleYouTube *service = [self youTubeService];
    GDataServiceTicket *ticket;

    NSString *searchString = [search_box stringValue];
    if ((searchString != nil) && ([searchString length] > 0))
    {
        NSURL *feedURL = [GDataServiceGoogleYouTube youTubeURLForFeedID:nil];
        GDataQueryYouTube *query = [GDataQueryYouTube youTubeQueryWithFeedURL:feedURL];
        [query setVideoQuery:searchString];

        ticket = [service fetchYouTubeQuery:query
                                   delegate:self
                          didFinishSelector:@selector(entryListFetchTicket:finishedWithFeed:)
                            didFailSelector:@selector(entryListFetchTicket:failedWithError:)];
    }
    else
    {
        NSString *feedName = [[feed_type selectedItem] title];
        feedName = [feedName lowercaseString];
        feedName = [feedName stringByReplacingOccurrencesOfString:@" " withString:@"_"];

        NSURL *feedURL = [GDataServiceGoogleYouTube youTubeURLForFeedID:feedName];
        ticket = [service fetchYouTubeFeedWithURL:feedURL
                                         delegate:self
                                didFinishSelector:@selector(entryListFetchTicket:finishedWithFeed:)
                                  didFailSelector:@selector(entryListFetchTicket:failedWithError:)];
    }

    [imageList removeAllObjects];
}

The else section of the if statement is the same code as our previous examples. It will be used if there is no value in the search box and just retrieves the entries for a given video feed.

The interesting bit is the if itself. So, what are we doing. First we grab the string value of our search box and if it isn’t blank we’ll do a search query. The query itself is similar to the code we use to retrieve the feeds.

We first construct the URL that we want to query. In this case you’ll notice I’m passing nil to the [GDataServiceGoogleYouTube youTubeURLForFeedID:nil] call. Passing in nil will give me the general video feed instead of a specialized feed. This is also where we could put in a feed name to search the specific feed instead of all videos.

Now that we’ve got our query URL we create a GDataQueryYouTube object. This will contain all of our query information. I then use the setVideoQuery method to pass in the query string. There are a bunch of other query options that can be used to limit date ranges, allow racy videos and other things. Take a look at the query header to see the full set of methods. This will, I believe, return a maximum of 999 videos.

With the query constructed we then use fetchYouTubeQuery which is very similar to the fetchYouTubeFeedWithURL function we were using earlier.

That’s it. You should be able to run MyTube and query videos from YouTube.

You can grab the complete list of source code for MyTube at MyTube 0.4.


Next Page »