img_5183In the words of Homer Simpsons, “forward not backwards, upwards not forwards and always twirling, twirling, twirling towards freedom”. With that, we’re back for part III of my HotCocoa tutorial. For those of you just joining the party, you’ll probably want to take a look at part I and part II.

If you’d like to grab a copy of the code, it’s getting a bit big to post all of it in one go, you can grab the tar ball here. The file contains all of the HotCocoa files along with the sprite image that I’ve shamelessly niced from PostRank.

picture-12When we last left off we’d created the basic layout for our application with our button and table views setup. With this installment, we’re going to go a step further and get a fully working application. We’re going to use the feed entered into the text field to query PostRank to get the current posts in the feed along with there PostRank and metric information. I’m going to be skipping over sections of code that haven’t changed from part I or part II for the sake of brevity.

Note, we’re going to be using JSON when working with the PostRank APIs. There is a bug in MacRuby, as of revision 1594, where JSON.parse would crash MacRuby. You’ll need to apply the patch attached to ticket 257 in order run this application.

OK, let’s go.

APPKEY = "appkey=Postie"

All of our calls to PostRank will use the same URL prefix and we’ll need to provide our appkey. I’ve placed both of these into constants.

vert << scroll_view(:layout => {:expand => [:width, :height]}) do |scroll|

  pr_column = column(:id => :postrank, :title => '')

  info_column = column(:id => :data, :title => '')

  scroll << @table = table_view(:columns => [pr_column, info_column],
                                :data => []) do |table|

I’ve made one layout modification which was to add an extra column to our table to display the PostRank for each post. The PostRank column and post data columns use custom cell formatters so we can get the layout we want. I also wanted to constrain the PostRank column to a set size, 34 pixels seemed to look good. In order to use my custom formatters I use setDataCell on the column objects. The parameter to setDataCell is an instantiated instance of our formatter class. I have two classes, PostRankCell and PostCell for the PostRank and post columns respectively.

Along with the column changes we’re also setting a default height on the table rows as defined in the PostCell class. We set the Postie instance as the delegate for the table so we can receive the tableView(table, heightOfRow:row) callback (thanks @macruby for the pointer). The last addition to the table is to hookup the double click action with @table.setDoubleAction(:table_clicked). The parameter is the name of the method that will be called, as a symbol.

def table_clicked
  url = NSURL.URLWithString([@table.clickedRow][:data][:link])

When a table row is double clicked we want to open the corresponding posts page in the users brower. We’ll be storing the link in the data attached to our table. We can get the clicked row with @table.clickedRow and access the link with[@table.clickedRow][:data][:link]. We then create a NSURL with this string. The created URL object is passed to NSWorkspace.sharedWorkspace.openURL(url) causing the page to open in the browser.

def tableView(table, heightOfRow:row)
  metrics =[row][:data][:metrics].keys.length

  num_rows = (metrics / PostCell::NUM_METRICS_PER_ROW) + 1
  num_rows -= 1 if metrics > 0 && (metrics % PostCell::NUM_METRICS_PER_ROW) == 0
  num_rows = 0 if metrics == 0

  num_rows * PostCell::ROW_HEIGHT + PostCell::ROW_HEIGHT  # 2nd row height for the title

picture-2The tableView(table, heightOfRow:row) callback is triggered each time the table is rendered out to determine the height for a given row. In the case of Postie we’re going to display the post title on the first line and the metrics on subsequent lines. I’ve constrained the metrics to allow a maximum of 6 metrics on each line. All of the metrics are stored in a :metrics key of the data attached to our table. Both the number of metrics and a row and the row height are constants stored in the PostCell class.

def load_feed = []

  str = @feed_field.stringValue
  unless str.nil? || str =~ /^s*$/

load_feed has been updated to empty our tables data by assigning a new array and, assuming we’ve received a valid feed, call fetch_feed to start retrieving the feed data.

There are a few different ways we could go about querying the data from the PostRank APIs. We could use Net::HTTP, Curb, NSXMLDocument or, as I’ve done, NSURLConnection. The reason I used NSURLConnection is so that I can have the requests run asynchronously. As well, the UI won’t block as we’re off fetching the data. A handy feature when you want things to remain responsive.

Let’s take a quick look at the wrapper class I’ve put around NSURLConnection before looking at fetch_feed. The reason I created a wrapper is that NSURLConnection, because it’s asynchronous, works through callbacks. I’m going to need to query three different PostRank APIs and take different actions for each query. Instead of trying to do some magic in the callbacks, I’ve created a wrapper class that accepts a block. The block is called when the data has been successfully retrieved. (The wrapper just spits out an error if something goes wrong, the block is never called.) For example, to download the homepage we could do:"") do |data|
  NSLog "Data: #{data}"

class DataRequest
  def get(url, &blk)
    @buf =
    @blk = blk
    req = NSURLRequest.requestWithURL(NSURL.URLWithString(url))
    NSURLConnection.alloc.initWithRequest(req, delegate:self)

  def connection(conn, didReceiveResponse:resp)

  def connection(conn, didReceiveData:data)

  def connection(conn, didFailWithError:err)
    NSLog "Request failed"

  def connectionDidFinishLoading(conn) @buf, encoding:NSUTF8StringEncoding)

As NSURLConnection executes it returns data to the application. We’re storing this data in a NSMutableData object called @buf. The callbacks we’re interested in are:

``connection(conn, didReceiveResponse:resp)``
Called when we receive a response from the server. This can be called multiple times if there are any server redirects in place. We reset the length of our data buffer each time this callback is called.
``connection(conn, didReceiveData:data)``
Called each time data is received from the server. This can be called multiple times and we just append the data to our buffer each time.
``connection(conn, didFailWithError:err)``
Called if there is an error retrieving the data. We, basically, just ignore the error. You'd probably want to do something sane in your application.
Called when all of the data has been retrieved from the remote server. Since we're not working with binary data I convert the ``NSMutableData`` to an ``NSString`` using the ``initWithData:encoding`` method. Note the use of ``alloc`` on the ``NSString``. If you try to use ``new`` you'll, like me, spend the next 30 minutes trying to figure out why your application is crashing.
def fetch_feed(url)"#{POSTRANK_URL_BASE}/feed/info?id=#{url}&#{APPKEY}") do |data|
    feed_info = JSON.parse(data)
    unless feed_info.has_key?('error')"#{POSTRANK_URL_BASE}/feed/#{feed_info['id']}?#{APPKEY}") do |data|
        feed = JSON.parse(data)
        feed['items'].each do |item|
          post_data = {:title => item['title'], :link => item['original_link'], :metrics => {}}
 << {:data => post_data,
                                     :postrank => {:value => item['postrank'],
                                                   :color => item['postrank_color']}}
"#{POSTRANK_URL_BASE}/entry/#{item['id']}/metrics?#{APPKEY}") do |data|
            metrics = JSON.parse(data)
            metrics[item['id']].each_pair do |key, value|
              next if key == 'friendfeed_comm' || key == 'friendfeed_like'
              post_data[:metrics][key.to_sym] = value

Most of the code in fetch_feed should probably be refactored into Feed, Post and Metrics classes, but, for the tutorial, I’m not going to bother.

You can see we’re doing three successive data requests. The first is to the Feed Info API. From this call we can retrieve the feed_hash which allows us to uniquely identify our feed in the PostRank system. By default all the PostRank API calls will return the data in JSON format. We could, and I did this initially use format=xml and NSXMLDocument.initWithContentsOfURL to pullback and parse all the data (the problem being, metrics only responds in JSON).

Now, as long as the query to Feed Info didn’t return an error we use the id to access the Feed API. The Feed API will return the posts in the given feed. The default is to return 10 posts which works for our purposes. We could, if we wished, add a button to retrieve the next set of posts from the API using the start and num parameters.

With the feed in hand we’re interested in the items attribute. This is an array of the posts in the feed. Using these items we can start to create our table data. For each item we’re going to create two hashes of data, one for each column of our table. The PostRank column will contain the :postrank and :postrank_color and the post column will contain the :title, :link and :metrics.

Finally, we query the metrics API for each post to retrieve the metrics data. The metrics API will provide us with a hash with a single key based on our post’s ID. Under this key we receive a hash containing the metric source names and the values. We’re skipping friendfeed_comm and friendfeed_like as they’ve been renamed to ff_comments and ff_links and only remain as legacy.

Once we’ve got all the metrics source information packed into our post_data hash we call @table.reloadData so everything gets rendered properly.

Since the calls to DataRequest are asynchronous, we have to call reload inside the metrics block. This guarantees the table will be reloaded after we’ve received our data.

With that out of the way, we’re onto our formatting cells. In order to get our custom table display we need to subclass NSCell and override the drawInteriorWithFrame(frame, inView:view) where we can layout our cell as desired.

class PostRankCell < NSCell
  def drawInteriorWithFrame(frame, inView:view)
    m = objectValue[:color].match(/#(..)(..)(..)/)
    NSColor.colorWithCalibratedRed(m[1].hex/ 255.0, green:m[2].hex/255.0, blue:m[3].hex/255.0, alpha:100).set

    rank_frame = NSMakeRect(frame.origin.x + (frame.size.width / 2) - 12,
                            frame.origin.y + (frame.size.height / 2) - 8, frame.size.width, 17)

    objectValue[:value].to_s.drawInRect(rank_frame, withAttributes:nil)

The PostRankCell is pretty simple. We parse the provided PostRank colour, which comes as #ffffff into separate red, green and blue values. These values are passed to NSColor.colorWithCalibratedRed(red, green:green, blue:blue, alpha:alpha) in order to create a NSColor object representing our PostRank colour. We need to divided each value by 255 as colorWithCalibratedRed:green:blue:alpha: expects a value between 0.0 and 1.0. Once we’ve got our colour we call set to make that colour active and, using NSRectFill we fill then entire frame with the provided postrank_color.

I’m, kinda, sorta, centering the PostRank values in the column so we need to create a NSRect to specify the box where we want to draw the numbers. This is done by calling NSMakeRect and providing the x, y, width and height values for the rectange. Once we’ve got our NSRect in hand we call drawInRect(rank_frame, withAttributes:nil) on the PostRank value. This will draw the string in the rectangle specified. We could set extra attributes on the string but, I don’t need any, so I just leave it nil.

You’ll notice I’m using objectValue in a few places. objectValue is a NSCell method that will return the value assigned to this cell as retrieved based on the column key from our table data source.

class PostCell < NSCell

  @@sprites = {:default => 0, :blogines => 16, :reddit => 32, :reddit_votes => 32,
      :technorati => 48, :magnolia => 64, :digg => 80, :twitter => 96, :comments => 112,
      :icerocket => 128, :delicious => 144, :google => 160, :pownce => 176, :views => 192,
      :bookmarks => 208, :clicks => 224, :jaiku => 240, :digg_comments => 256,
      :diigo => 272, :feecle => 288, :brightkite => 304, :furl => 320, :twitarmy => 336,
      :identica => 352, :ff_likes => 368, :blip => 384, :tumblr => 400,
      :reddit_comments => 416, :ff_comments => 432}
  @@sprite = nil

  def drawInteriorWithFrame(frame, inView:view)
    unless @@sprite
      bundle = NSBundle.mainBundle
      @@sprite = NSImage.alloc.initWithContentsOfFile(bundle.pathForResource("sprites", ofType:"png"))

    title_rect = NSMakeRect(frame.origin.x, frame.origin.y + 1, frame.size.width, 17)
    metrics_rect = NSMakeRect(frame.origin.x, frame.origin.y + ROW_HEIGHT, frame.size.width, 17)

    title_str = "#{objectValue[:title]}"
    title_str.drawInRect(title_rect, withAttributes:nil)

    count = 0
    orig_x_orign = metrics_rect.origin.x

    objectValue[:metrics].each_pair do |key, value|
      s = metrics_rect.size.width
      metrics_rect.size.width = SPRITE_SIZE

      y = if @@sprites.has_key?(key)
      r = NSMakeRect(0, y, SPRITE_SIZE, SPRITE_SIZE)
      @@sprite.drawInRect(metrics_rect, fromRect:r,
                          operation:NSCompositeSourceOver, fraction:1.0)
      metrics_rect.origin.x += 21
      metrics_rect.size.width = s - 21

      "#{value}".drawInRect(metrics_rect, withAttributes:nil)
      s = "#{value}".sizeWithAttributes(nil)
      metrics_rect.origin.x += s.width + 15

      count += 1
      if count == NUM_METRICS_PER_ROW
        metrics_rect.origin.y += ROW_HEIGHT
        metrics_rect.origin.x = orig_x_orign
        count = 0

PostRankCell is similar to PostCell in that we’re basically creating bounding rectangles and drawing into them. The extra little bit we’re doing here is loading up a NSImage which is our sprite set and using that to pull out all of the individual service icons. NSImage makes it easy to work with our sprite image by providing drawInRect(rect, fromRect:from_rect, operation:op, fraction:val). drawInRect:fromRect:operation:fraction: draws into the rectangle defined by rect retrieving the pixels in your NSImage that are inside the from_rect. I’m using NSCompositeSourceOver because some of my images are semi-transparent. The fraction parameter is a the alpha setting for the image.

With that, well, you’ll probably need to download the source code to see it all in one file, you should have a working application that will query PostRank for a feed and display the posts and metrics for the feed.

As for the next installment. I’ve got a few things I still want to do, including: tabbing between widgets, submitting the text field on return, a progress indicator as the feed information is being retrieved and adding a tabbed interface to allow showing feed, post and top post information. I’m not sure which of these things I’ll tackle next. There are a few helper methods I want to try to add to HotCocoa from this article that I’ll probably do first. So, until next time.