Cocoa and MySQL

While Core Data is able to use a SQLite database as its backing store, Cocoa sadly has no native support for full client-server style SQL. Third-party solutions are therefore the answer.

Looking around the web yesterday, I found a number of such solutions including a rather extensive document on OS X ODBC drivers, a rather slick-looking, but wallet-hurting ($700) SQL framework for Cocoa, called MacSQL (it even comes with an Interface Builder palette!) and the open source MySQL-Cocoa project.

ODBC is clearly the compatible, righteous and most open way to access a database, but is also probably the most “heavyweight” solution, which I didn’t really fancy tackling for my humble application. MacSQL looks very nice. The demo movies on their website are a little dated, but show that it really is a very polished solution that allows for a no-code approach to browsing a SQL database. However, it’s also $700 (they do offer a $149 version for hobbyists, but the resulting program can’t be distributed). So, the final option was MySQL-Cocoa.

First impressions of the MySQL-Cocoa project might suggest that it’s dead: the examples disk image link on the homepage is broken, the framework doesn’t compile out of the box on 10.4.10 and the latest news item was posted 18 months ago. However, a little searching reveals that a universal binary version of the framework is linked to from this page, hosted by the different, but similarly-named CocoaMySQL project. Excellent.

To use the framework, first download the bundled version and follow Dave Winter’s excellent guide on adding it to your XCode project.

Once that’s done, I found that the easiest way to actually use the framework was to also download the source for Lorenz Textor’s aforementioned CocoaMySQL program and copy these files into your own project:
Useful files from Cocoa MySQL

If the copying’s all gone well, you’re ready to go, so here’s some sample code to get you started. First of all, don’t forget to import the framework and the header files of Lorenz’s files:
#import <MCPKit_bundled/MCPKit_bundled.h>
#import "CMMCPConnection.h"
#import "CMMCPResult.h"

Once that’s done, you’ll probably want to set up a connection and a query result object, as follows:
CMMCPConnection *mySQLConnection;
CMMCPResult *queryResult;

Finally, you can now establish your connection to the database and start querying it, as demonstrated in the following (and, I think, fairly self-explanatory) code:
mySQLConnection = [[CMMCPConnection alloc] initToHost:@"127.0.0.1"
   withLogin:@"username"
   password:@"password"
   usingPort:3306];
if ([mySQLConnection isConnected])
{
   NSLog(@"Connected.");
   [mySQLConnection setDelegate:self];
   [mySQLConnection selectDB:@"databasename"];
   queryResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SELECT salary FROM table WHERE department = \"%@\" AND age BETWEEN %d AND %d ORDER BY age",departmentName,lowerAge,upperAge]];
   if (queryResult) {
      for (i = 0; i < [queryResult numOfRows]; i++) {          [queryResult dataSeek:i];          NSLog(@"%@",[[queryResult fetchRowAsArray] objectAtIndex:0]);       }    }    [mySQLConnection disconnect]; } else {    NSLog(@"Connection failed."); }

As with my last post, there's no real error checking here, but hopefully the sample code will at least help to get you up and running fairly quickly.

Quick and dirty NSURL request

I started looking at Apple’s NSURL* family of classes today, as I’m about to start interfacing my GFF viewer application with an online BLAT search tool (for in-house purposes only — this isn’t going to be available in the release version) and I was a little shocked to find this line in Apple’s documentation:

In order to download the contents of a URL, an application needs to provide a delegate object that, at a minimum, implements the following delegate methods: connection:didReceiveResponse:, connection:didReceiveData:, connection:didFailWithError: and connectionDidFinishLoading:

In other words, one would have to first establish an NSURLConnection (having already formed an NSURLRequest) and then implement four NSURLConnection delegate methods just to download a single HTML file. This all seems a little excessive for me just downloading a single HTML file.

After a little bit of playing around, I’ve found that the following code works just great for downloading a single HTML file:
NSURL *fileURL;
NSMutableString *fileContents;
fileURL = [NSURL URLWithString:@"http://127.0.0.1/index.html"];
fileContents = [[NSMutableString alloc] initWithContentsOfURL:fileURL];

A quick NSLog(@”%@”,fileContents); reveals that fileContents does indeed contain index.html from localhost. Good stuff.

As a piece of code, this is rather unwholesome on all fronts. There is no error handling anywhere to be seen. Not in the formation of the URL, not in the retrieval of the URL. Zilch. Errors are not handled. Anything could happen. It’s a mini adventure.

Bonus information!
Just out of interest, I checked my Apache access-log after this request to find that NSMutableString’s initWithContentsOfURL: method provides the user agent string: “CFNetwork/129.20” (as of 10.4.10).

Mouse tracking in Cocoa

I’ve just implemented mouse tracking in my (tentatively named) GFF Viewer app and thought I’d share my experiences doing so. Hopefully this will make life a bit easier for others trying to implement similar functionality, since Apple’s documentation on the matter is spread across a few different documents.

Tracking rectangles
By default, the Application Kit doesn’t provide pixel-by-pixel notifications of the cursor’s onscreen location. This is because the frequency of “mouse-moved” events is so high that constantly tracking them would clog up the event queue (as noted here). Instead, AppKit puts NSWindow in charge of a lighter-weight system that utilises tracking rectangles.

A tracking rectangle is an area onscreen owned by an object (most commonly the NSView subclass in which the rectangle resides) that causes NSMouseEntered and NSMouseExited events to be sent to the owning object when the cursor moves into and out of the area.

Adding a tracking rectangle
Tracking rectangles are added using the NSView method addTrackingRect:owner:userData:assumeInside:. For example, from within an NSView object, you might call [self addTrackingRect:[self visibleRect] owner:self userData:nil assumeInside:NO]
to add a tracking rectangle that covers the whole visible area of the view.

The addTrackingRect: owner: userData: assumeInside: method should be called from NSView’s viewDidMoveToWindow: method rather than initWithFrame:. Although the tracking rectangles are added and removed by NSView, NSWindow actually manages the current list of rectangles and, when initWithFrame: is called, NSView doesn’t yet have a parent window.

addTrackingRect: owner: userData: assumeInside: returns an NSTrackingRectTag (actually just an integer) to uniquely identify the tracking rectangle, allowing for its removal by calling NSView’s – (void)removeTrackingRect:(NSTrackingRectTag)tag method. As recommended here, this should be called as such (from the NSView object):
- (void)viewWillMoveToWindow:(NSWindow *)newWindow {
   if ( [self window] && trackingRect ) {
      [self removeTrackingRect:trackingRect];
   }
}

This ensures that the tracking rectangle is removed when the view is deallocated (or, as the method name implies, is moved to another window).

Keeping tracking rectangles up to date
A side-effect of tracking rectangles being overseen by NSWindow is that they are “static” and will not update as your NSView subclass resizes, scrolls, transforms itself etc. As such, Apple recommends that tracking rectangles are removed and then re-established in NSView’s setBounds: and setFrame: methods. However, I’ve found that using resetCursorRects: to achieve the same thing works very well. My NSView subclass’s resetCursorRects: looks like this:
-(void)resetCursorRects
{
   [super resetCursorRects];
   [self clearTrackingRect];
   [self setTrackingRect];
}

where clearTrackingRect: calls [self removeTrackingRect: trackingTag] and setTrackingRect: calls trackingTag = [self addTrackingRect:[self visibleRect] owner:self ­userData:nil assumeInside:NO] respectively.

Implementing mouseMoved:
The way I’ve set mouse tracking up in GFF Viewer is to (as alluded to above) have one large tracking rectangle covering my entire view and activate “mouse-moved” events when the mouse moves into that tracking rectangle. To receive NSMouseMoved events, two conditions must first be met:

  1. the view responding to the events must be the first responder
  2. the parent window must told to accept NSMouseMoved events by sending it a setAcceptsMouseMovedEvents:YES

Both of these conditions can be put in place when the mouseEntered: event is received for the tracking rectangle covering the view.

I’m currently using the following mouseEntered: method in my NSView subclass:
- (void)mouseEntered:(NSEvent *)theEvent
{
   mouseInView = YES;
   windowWasAcceptingMouseEvents = [[self window] acceptsMouseMovedEvents];
   [[self window] setAcceptsMouseMovedEvents:YES];
   [[self window] makeFirstResponder:self];
   [self displayIfNeeded];
}

where windowWasAcceptingMouseEvents is just a BOOL to note whether or not the window was accepting mouse events before this method was called. mouseInView is also a BOOL for my own use in my drawRect method. My mouseExited: method effectively does the reverse (although it leaves the view as the first responder until another object claims that status):
- (void)mouseExited:(NSEvent *)theEvent
{
   mouseInView = NO;
   [[self window] setAcceptsMouseMovedEvents:windowWasAcceptingMouseEvents];
   [self setNeedsDisplay:YES];
}

Actually following the mouse
With the above methods in place, the time between mouseEntered: and mouseExited: is spent actually tracking mouse movements in the mouseMoved:. My implementation of this method reads simply:
- (void)mouseMoved:(NSEvent *)theEvent
{
   cursorLocation = [self convertPoint:[theEvent locationInWindow] fromView:nil];
   [self setNeedsDisplay:YES];
}

The convertPoint:fromView: method is included because the point returned is in window coordinates, rather than view coordinates, which is usually undesirable. cursorLocation is an NSPoint instance variable that I use in drawRect: to draw a translucent blue bar that follows the mouse left and right across my NSView, like this:
GFF Viewer with mouse tracking
Nice.