28 January 2008

Alpine Navigation DVD's: Alpine are idiots

We have a Honda Odyssey which has an Alpine navigation system in it that runs off DVD. They only update the DVD annually. Fine. But get this, they prioritize new cars, sitting on a dealer's lot over their existing customers who are actually using the darn thing every day! No joke.

I called and talked to them today about it, and that's what I was told. They told me that until that process is done, only dealers can order the DVD (it's been out since the fall of 2007), and only once all the dealers have done it will they make it something you can order on the web site.

How ridiculous is this in this day and age? It's a DVD, burn a few more, or heck, burn on demand! I fail to see how there is any logic, any value to their customers, etc.

27 January 2008

RubyCocoa Rocks

My infatuation with RubyCocoa continues. I've been working on a small app for the Building Web Apps folks. I'd originally been tasked with a feature that was to be done 100% within the web app. However, some of our requirements changed, and the workflow was not efficient enough. So, essentially what we moved forward on was a desktop Mac application that could interface with NetNewsWire, as well as the Building Web Apps site. The desktop app gives us a faster way to add data into the system - rapidly processing a ton of content and injecting what is desired into the web app, yet without getting slowed down by a web app interface. AJAX or Flex, or some other web UI tech wouldn't make it any faster in this particular situation.

Thus, I pursued building a Cocoa app, but this time using RubyCocoa. I've written Objective-C apps before, and spend the bulk of my time in Ruby, but this was my first opportunity to use RubyCocoa. The combination, much like JRuby, Jython and other hybrid systems, gives you "the best of both worlds." There are cons of course (slower, a few Cocoa things you can't do, debugging is harder, etc.), but for the most part, it's really nice.

For me, the infatuation stems from letting me use the aspects of each system that I am either more efficient with, or that are easier for a particular piece of functionality, all yielding a faster, and better end result. I can iterate on the app more quickly, and get a solution to BWA faster. And, one step further in the case of RubyCocoa: having the native OS integration abilities at hand.

What's been great is the ability to use Ruby's more effective (for me at least) string processing, XML processing, and networking features; create a native Mac application - using any cool Cocoa/native features; and support for AppleScript, which was critical for this particular application. This latter feature ruled out using something like Adobe Air.

This evening I setup the second use of AppleScript/Apple Events in this app: registering a custom URL protocol for the app. Applications like Pukka and Mailplane do this. What for? Well, in this case, it allows me to create a web browser bookmarklet, that can send data from the browser to our app. It also means that it works in basically any browser on the Mac (as opposed to AppleScript not working for Firefox). Further, it allows a simple "push a button" in the browser to send the data over to the app, as opposed to having to switch to the app, pick a menu item to pull the data, or horror of horrors, copy-paste.

How do you do this? This article is short and covers how to do it in Objective-C. It's just as easy in RubyCocoa: You need to add an entry into your Info.plist file to specify the name of your URL protocol as described in the article. Then, you need to register your app as a handler for that protocol:

self, :getUrl_withReplyEvent, fourcharcode('GURL'), fourcharcode('GURL'))

The fourcharcode method is my way of translating four character codes for use in Ruby. I discussed this in more detail in my last post about RubyCocoa, but here's the actual method for your pleasure:

def fourcharcode(character_code)

Ok, so now that you've registered your app to handle its custom URL protocol, you will get an Apple Event sent to you with the URL whenever one is opened. This is handled (as per the parameter in the registration function above), by the getUrl_withReplyEvent method:

def getUrl_withReplyEvent(event, reply)
url = event.paramDescriptorForKeyword(fourcharcode('----')).stringValue
# url now contains the complete URL as a string
# do your processing of the URL/content...

That's it. Pretty cool eh? Handling events from NetNewsWire is almost identical (register for them, write a handler function).

And, one more great thing I could integrate: Sparkle. Sparkle is a superb Cocoa library that does automatic application updates. It checks the web for a newer version of your app, downloads it, and installs it. Integrating it is simple, and in fact, depending on your needs, you don't have to write a single line of code. The only code I wrote for it was actually a Rake task to build the appcast and upload it to the server. Slick.

All this could only be done as a native Mac app, which means Cocoa. But, as said above, doing it with RubyCocoa gives me access to all these abilities, yet, I can do all the more heavy string and XML processing I need to do using Ruby, which is much more effective for me. Also, the web services calls and code is a lot easier for me to do in Ruby than Cocoa.

Furthermore, this is plain fun! Unlike some, I feel desktop apps still have a place, but love webapps at the same time. With RubyCocoa I can build super cool Mac apps, but do so in a language I'm happier using, yet have the power of Cocoa available to me. For me, some of the best "applications" these days are such hybrids: a web app that does your primary data storage, and gives you access to the app from "anywhere" (i.e. anywhere you can get to a browser and net connection), but a desktop app to use most of the time for faster interaction, potentially better integration on your desktop system, and so on. It's the same reason I use Mailplane (desktop app for Gmail), or PackRat (desktop app for Backpack). I suspect it's the same reason we're seeing other solutions like Adobe Air, or Google Gears. Technology is so cool, isn't it?!

18 January 2008

Building My First RubyCocoa App - Some Notes

Today I started work on my first app built using RubyCocoa, which is now a first class citizen in MacOS X 10.5/Leopard. I had read the docs and tutorial found here. I proceeded, and ran into a few bumps along the way, so here are some notes maybe someone else will find useful...

The code for the example app RSSPhotoViewer, is not quite the same as that shown in the tutorial. Specifically:

  • The tutorial says you need to put "ib_action :method_name" after your methods that are Actions. The example code does not do that, and I found I didn't need to do it either.

  • The example code does not require osx/cocoa or include OSX, yet I had to do this in my Ruby source code in order for it to recognize the class names properly (or at least so I didn't have to prefix them with OSX).

I couldn't get Interface Builder to recognize my Ruby window controller class - i.e. it didn't show it's outlets and actions. I tried a variety of things here, but basically I finally had to go to the command line and run "rake" and let it do a command line build. I have no idea what that did differently, as I can't see any new files it generated, etc., but that resolved it - now IB can see all my outlets and actions.

My app wouldn't run, and I got a strange error in Xcode saying "The debugger is still running" etc. It appears that if your app crashes this will be the case. And, in this case, Console is your friend. Open up Console and you should see messages that will help you assess what's gone wrong.

And now for the win... dealing with Apple Events. My app wants to receive a particular apple event from NetNewsWire. NNW documents this protocol nicely, however, when registering as a handler for the event (using NSAppleEventManager.sharedAppleEventManager.setEventHandler_andSelector_forEventClass_andEventID_), you need to pass in the class and event ID that are not standard ones (they're defined by NNW/external blog protocol). Well, in Cocoa code, these are just a four character string, but as I found, a string that an unsigned long via string packing. So, it was a question of how I get these ID's in via Ruby. Luckily this turned out to be rather simple, as you can use String#unpack. And, in this case, you pass unpack "N" as the format, which is an unsigned long packed in network byte order.

Lastly, debugging. As mentioned above, Console and such are your friend. I haven't tried any shenanigans with ruby-debug or such from the command line, so that might work. But, Xcode can't debug into the Ruby code in your project (it can into the code in main.m just fine though). So, if you have weird crashes and such, check Console. Also, use NSLog, or pop alerts or what not. If someone knows a better way please do tell.

Regardless, I'm quite excited by RubyCocoa and have another couple apps that I may do with it. This app was a small one, and of course is not done in just a couple hours, but it's going to be a nice addition to the tool belt.

17 January 2008

Sending SMS (Text Message) to Your Phone Using Ruby and iChat

Tonight I took a bit more of a look into the various Scripting Bridge and RubyOSA features in MacOS X 10.5/Leopard. This originated with a quick bit of research on a script that would pull data from the current article being read in NetNewsWire, and send it off somewhere else. But then seeing a Tweet from a friend, and how he's using Twitter and such, I thought, why not just send SMS directly (more robust in his case). This led me to the fact that the latest iChat can send SMS's, and knowing that I could drive iChat via Ruby (instead of AppleScript), I investigated.

With no further ado, here's a simple script to send an SMS to a phone that's in your buddy list (i.e. you've had to sent an SMS to it from iChat previously):

require 'rubygems'
require 'rbosa'

ichat = OSA::app('iChat')
myphone = ichat.buddies.select {|buddy| buddy.name == "+18005551212"}.first
ichat.send2("from ruby!", myphone)

Obviously put in the correct phone number. You'll need to install the RubyOSA gem:

sudo gem install rubyosa

Anyway, I think this is pretty cool. One other thing I learned was that you can leverage a neat tool called rdoc-osa that will generate rdoc for a scriptable application! For example:

rdoc-osa --name iChat
open doc/index.html

This will generate the docs (placing them in a directory called "doc" in your current directory - you likely want to put it elsewhere if you plan to keep it around), and then the second command obviously will open those up in your browser. The docs are more of a way to get started, and you'll likely want to combine those (for the Ruby syntax/naming), with the docs from the app's scripting dictionary (pull that up by running Script Editor, and then opening the scripting dictionary of the app you're interested in).

I should note, you can also do this via the osx/cocoa Ruby library, if for example, you have a full fledged Cocoa app written in Ruby (another super cool thing introduced in/built into Leopard). But, I find using the RubyOSA stuff just slightly nicer if all you are doing is driving an app via scripting, and aren't doing Cocoa stuff.

11 January 2008

Things: the Ultimate Todo/GTD App?

I've finally struck gold when it comes to a todo list/GTD app! I've recently switched to a new Mac app called Things, and I couldn't be happier. Finally, a system that works the way I do, is fast, looks nice, and has everything I need. Things is produced by Cultured Code, who also happen to make the excellent Xyle Scope product.

For the longest time, I used Backpack to manage my todo lists, and I've worked in a semi GTD way. I originally chose Backpack as it was relatively simple, I was using it for other things, and it provided access to my lists anywhere I had access to the 'net. When I was working at Adobe, and had many computers, plus my home machines, this was valuable. But, I disdained having to always keep a browser tab open for it, and have it mixed in with other browser use. I started working on an Apollo app to have a dedicated system for it, but that was in the early days of Apollo when the HTML view was buggy and prevented this app from working effectively. Along came Packrat and solved it, making a dedicated Backpack app, as well as providing offline storage, and much faster access. Yea! I still use this combo today, and that will be the subject of an upcoming post. But, alas, I was not happy with my todo system.

Now that I'm using essentially a single machine, and I wanted further speed for this commonly used app, as well as a better overall functionality, I started looking. This was stimulated when I saw Anxiety, which was a nice HUD implementation of a display for your iCal todos, which I'd been thinking about using to sync to my phone. I switched and tried it a while. Not bad, but definitely some issues (Anxiety was a pure viewer (you can add items) - you can't re-order, it doesn't pay attention to priority, and so on). But I'm babbling, get on with it right?

I found out about Things, I believe via Twitter I think. I watched the screencast, which starts a bit slow, but is a good display of the app. What immediately attracted me was the "Today" view, and it's separation from Next, as well as the abilities to tag, to postpone (with a timered re-add/check for re-add), and the whole Someday system.

I had been organizing my stuff more manually into a "to do today" list, and everything else. It was actually somewhat tedious to manage in Backpack, until they added dragging between lists. But still, Things just does it naturally and really well. Simple buttons to move things back and forth between Today and Next, or you can drag. Also, it pays attention to due dates on tasks, and automatically moves them into Today if they are due today.

Also, I really like the Areas of Responsibility and the Projects. I use Areas heavily, and Projects much less, but they are both useful and distinct. In general I organize all my different "work" items into areas (by client or project, or various other ways), and my personal stuff is area-less, with some exceptions. The Areas show up as distinct lists, or as separate lists in the Next and Today views. I could tell you more, but just check out the web site and/or the screencast.

So, what about web/network storage, and access from multiple machines? Nope, Things doesn't have this (yet?). Hasn't bothered me a bit though. It is an extremely rare day that I don't use Packrat to for Backpack, and with my single computer setup now, I just have much less need. Things will supposedly have some Export options later, and it's data is stored in an easily accessible and readable XML file, so I could create a simple solution for read-only data if I wanted to at least have it on the web, but I just haven't had the need or desire. The app itself works so darn well, that this aspect has barely even been on my radar.

I've looked at a variety of other apps and solutions over the last couple years, but hands down, Things is the best.

09 January 2008

Cocoa Screen Saver Prefs and Bindings (or Not)

In my latest work on my Visionary Saver screen saver, I had tried switching all my preferences to use Cocoa Bindings, to make it super easy to manage the prefs. After doing this, and having it appear to work, I realized that it does not.

The problem is that screen savers are supposed to use the ScreenSaverDefaults class to manage their preferences. This is a special Defaults class that namespaces a screen savers defaults/preferences within the defaults system, given that a screen saver is a bundle, and works within System Preferences (as opposed to being its own application). The reason it doesn't work with bindings is that you can't tell the Bindings system about ScreenSaverDefaults (to my knowledge), in the same way as you can bind to the Shared Defaults Controller. ScreenSaverDefaults requires a module name, and so on. If there's a workaround, I'd love to hear it.

I thus had to go back to manually getting and setting the preferences for Visionary. This did simplify one thing, which is the preferences settings for an NSPopUpButton, where the content values come from an array, yet the selection and setting should go to preferences. Personally, NSPopUpButtons, for simple use, are a real pain. I'm not an Interface Builder expert, but it's odd that you can set up to 3 values into an NSPopUpButton in the UI, as generic text, but if you want more, you have to setup the whole NSArrayController and its content array, and so on, then bind that to the popup, etc, etc. It's not awful, but the documentation is pretty weak in terms of a straight forward use of something like this. I suspect many other folks don't have complicated data models behind the values for some of their popup buttons, and a cleaner way to do all this would be nice.

Anyway, it's all good now, or well, it's all fixed up, and there's a new version of Visionary Saver out.