img_6399I’ve had my eye on giving HotCocoa a test run for a while now. Other things have conspired to come up over the last few months so I haven’t had a chance to give it a spin. That is, until now. I started poking at it the other day, a few things still confuse me, but I’m getting there.

I figured I’d write stuff down as I plow my way through the code and create a simple little application. The application is nothing fancy, I’m going to query PostRank and pull back engagement information on a feed entered into a text box. This will be a multi-part tutorial.

In the usual fashion, let’s start at the start. What is HotCocoa? Well, HotCocoa is a layer of Ruby code that sits on top of the Mac OS X frameworks including Cocoa. HotCocoa is part of the MacRuby distribution which will ship with figure versions of OS X. MacRuby is a port of Ruby 1.9 to run on top of Objective-C.

I’m going to assume you have MacRuby installed for this tutorial.

The the HotCocoa developers makes life a little easier to get started. There is a hotcocoa command that is installed when you install MacRuby. This will create the basic application structure and Rakefile to get you up and running.

titania:Development dj2$ hotcocoa Postie

hello_from_hotcocoa We can then execute our application by changing into the Postie directory and executing macrake. Note, this uses mac_rake and not regular rake. MacRuby installs alongside the normal Ruby runtime on OS X. You’ll need to make sure you use _macrake, macirb, macgem and macruby to work with the MacRuby versions. You should see a Hello from HotCooca window if everything worked correctly.

You’ll notice that you now have a Postie.app in your root directory. This application can be executed by double clicking like any other Mac application, although, you’ll need MacRuby installed for it to execute. You can also execute macruby lib/application.rb to execute the application. This allows passing flags to macruby for things like enabling debug mode.

Let’s take a quick look at the files generated by the hotcocoa command.

./Rakefile
./config/build.yml
./resources/HotCocoa.icns
./lib/application.rb
./lib/menu.rb

The build.yml file contains information used by hotcocoa to build your application. This includes things like the name, version, icon and source files. The icon, by default, is HotCocoa.icns. The main files we’re interested in are application.rb and menu.rb.

module HotCocoa
  def application_menu
    menu do |main|
      main.submenu :apple do |apple|
        apple.item :about, :title => "About #{NSApp.name}"
        apple.separator
        apple.item :preferences, :key => ","
        apple.separator
        apple.submenu :services
        apple.separator
        apple.item :hide, :title => "Hide #{NSApp.name}", :key => "h"
        apple.item :hide_others, :title => "Hide Others", :key => "h", :modifiers => [:command, :alt]
        apple.item :show_all, :title => "Show All"
        apple.separator
        apple.item :quit, :title => "Quit #{NSApp.name}", :key => "q"
      end
      main.submenu :file do |file|
        file.item :new, :key => "n"
        file.item :open, :key => "o"
      end
      main.submenu :window do |win|
        win.item :minimize, :key => "m"
        win.item :zoom
        win.separator
        win.item :bring_all_to_front, :title => "Bring All to Front", :key => "o"
      end
      main.submenu :help do |help|
        help.item :help, :title => "#{NSApp.name} Help"
      end
    end
  end
end

The menu.rb file contains information about the menu for our applcation. This includes the menu names, hot keys, modifiers and general layout.

The :apple submenu is special and will appear with a menu title based on your application name, as is typical for OS X applications. For the other submenus, by default, the menu title will be the capitalized version of the symbol name converted to a string. You can also provide a :title => 'foo' option to specify a different name. menu.submenu :postrank, :title => 'PostRank'.

The symbol provided to each submenu item, e.g. file.item :new will be used to form the name of the method invoked in your application delegate. The methods are named _on__. For our _:new_ item the _on\_new_ menu item will be invoked. If there is no _on\__ method the menu item will be disabled. As you can see above you can also specify _:modifiers_ and _:key_ equivalents for your items.

As with the menu titles, the items names will be formed from capitalizing the symbol provided unless a :title is provided.

In the case of Postie I’ve erased everything but the :apple submenu for now. I don’t need any extra menu items at the moment. This also means all of the on_* methods can be removed from application.rb.

The default application.rb provided by the hotcocoa command is pretty short.

require 'hotcocoa'

class Application
  include HotCocoa

  def start
    application(:name => "Postie") do |app|
      app.delegate = self
      window(:frame => [100, 100, 500, 500], :title => "Postie") do |win|
        win << label(:text => "Hello from HotCocoa", :layout => {:start => false})
        win.will_close { exit }
      end
    end
  end
end

Application.new.start

Let’s take a quick look and see if we can figure out what’s going on. We need to require hotcocoa' to get access to the needed HotCocoa classes. We then include HotCocoa into our Application class to make everything shorter. Feel free to rename Application just do it in the class definition and at the bottom of the file.

Jumping to the bottom, you can see we’re calling Application.new.start so the Application#start method will be invoked. It’s worth noting, the application will not return from Application#start.

Going back to Application#start we call application to create our application, setting the title as desired. We then set ourselves as the application delegate. This means that our class will receive all of the callbacks that are called on the Cocoa application. This includes the menu on_*callbacks we talked about earlier.

We then proceed to create a window. We’re setting a :frame on the window to position it at X 100, Y 100 (from the bottom left) with a width of 500 and height of 500. We :title the window as Postie. If you don’t want to specify the entire frame of the window you can specify just the :size => [500, 500] of the window. You can also specify :center => true to center the window on the desktop. If you look at the Objective-C documentation for NSWindow the options available in Obj-C are available in the HotCocoa layer.

Once the window is created we add a label to the window and set the will_close handler to exit when executed.

The will_close callback is the HotCocoa name for the Cocoa windowWillClose:. Many of the Cocoa callbacks have been remapped to make the names more Ruby like.

Cocoa Callback HotCocoa Callback
window:shouldDragDocumentWithEvent:from:withPasteboard: should_drag_document?(shouldDragDocumentWithEvent, from, withPasteboard)
window:shouldPopUpDocumentPathMenu: should_popup_path_menu?(shouldPopUpDocumentPathMenu)
window:willPositionSheet:usingRect: will_position_sheet(willPositionSheet, usingRect)
windowDidBecomeKey: did_become_key
windowDidBecomeMain: did_become_main
windowDidChangeScreen: did_change_screen
windowDidChangeScreenProfile: did_change_screen_profile
windowDidDeminiaturize: did_deminiturize
windowDidEndSheet: did_end_sheet
windowDidExpose: did_expose(windowDidExpose.userInfo[‘NSExposedRect’])
windowDidMiniaturize: did_miniaturize
windowDidMove: did_move
windowDidResignKey: did_resign_key
windowDidResignMain: did_resign_main
windowDidResize: did_resize
windowDidUpdate: did_update
windowShouldClose: should_close?
windowShouldZoom:toFrame: should_zoom?(toFrame)
windowWillBeginSheet: will_begin_sheet
windowWillClose: will_close
windowWillMiniaturize: will_miniaturize
windowWillMove: will_move
windowWillResize:toSize: will_resize(toSize)
windowWillReturnFieldEditor:toObject: returning_field_editor(toObject)
windowWillReturnUndoManager: returning_undo_manager
windowWillUseStandardFrame:defaultFrame: will_use_standard_frame(defaultFrame)

That’s it for part I. We’ve now setup with our basic application structure and have an idea of what we’re working with. In the next installment, we’ll work on getting our application views setup as we want.

Update: Part II and Part III are now available.