flickrPreviewR
For the past couple of days I have been creating an application that I think I would use. And maybe some other people might find it useful too (possibly as an example for doing things in RubyCocoa). For the lack of a better name, I decided to call it flickrPreviewR and hopefully I would not be sued.
flickrPreviewR screen. It even has the typical flickr logo design from flickrlogomakr. The line to the right is the the side of a window that apparently shifted a bit to my other screen and included in the image capture.
It all started when Flock released a new version of their browser sometime last week. I downloaded it since I was eager to see what the perpetually-in-beta browser has created this time. This time I was really disappointed. The only reason for my using Flock was its integration with Flickr. I just like to quickly browse photos without having to use Flickr's interface. Unfortunately, this new version of Flock really left a sour taste in my mouth. Its interface is hideous and the newly revamped Flickr integration seems buggy and really slow. What does it need to do that requires that much computation power? It just needs to fetch the damn photos damnit!
Anyway, this provided the motivation and gave me another reason to play with RubyCocoa. The last time I dabbled with RubyCocoa, I created a version of the RaiseMan application from the book Cocoa Programming for Mac OS X with bindings. Since then, RubyCocoa has had additional changes and I was curious whether it is now easier to develop programs for it. My conclusion: nothing much has changed in terms of the cosmetics of language (there are underlying changes for optimizations, etc), but overall there was nothing new to pick up.
For the backend of this application, I relied on rflickr, the Ruby API kit for accessing flickr. I needed to make some slight changes to the code to accommodate some changes in the flicrk API. rflickr source is bunbled as part of the source code. I did want to not rely on it being a gem since I am not sure how RubyCocoa handles gems and I did not want to add a dependency for the gem; so I just bundled it. flickrPreviewR is under the Creative Commons Attributions License. This might violate the rflickr license and if it does, then let me know and I will see what I can do.
You can find the set of images on my flickr account. I do not have a pro account so I cannot create a set for it. You should read the description of each photo if you are planning on using this. As usual, use this at your own risk. I am doing this as a personal application that I would use and as the developer, I am aware of its limitations and I do not push them while using the application.
One very important limitation that I need to stress is the inability for it to update itself.
There is a weird quirk with using threads in RubyCocoa and NSNotifications. I have not figured out how to get the view to refresh once all the images have been downloaded. Right now, there is the VERY cumbersome need to click on ANOTHER favorite or photoset to force a refresh. The best way to see if the photos have finished loading is to view the progress bar at the drawer.
THIS NEEDS TO BE FIXED once I figure out how.
Here are some things that I picked up while doing this:
- Interface Builder is a great tool for quickly laying out the interface. I cannot imagine how I would have done this with some other GUI toolkit. Even with my familiarity with Java, it would taken much longer just to get all the pieces aligned properly.
- CocoaDev is the site for almost all the questions that you can have on how to do things in Cocoa. Without CocoaDev, it would have taken me a longer time to get things done.
- Cocoa Programming for Mac OS X is a decent reference book once you know your way around how certain things in Cocoa work. That still does not make it a good book to start learning Cocoa programming.
- You can save yourself great pains if you make all your classes subclasses of OSX::NSObject explicitly instead of normal Ruby classes. This will save you trouble when you get weird behavior and crashes. Like me, you might want to use normal Ruby classes sometimes because YAML works well with the regular Ruby classes. In that case, make sure that your class is being used as simple data structure with simple functions that do not rely on any of the Cocoa libraries.
- Threading is hard to do with RubyCocoa and there is little reference on what works and what does not.
- Downloading images with Ruby net/http library did not work for me. The image will always end up corrupted. Thus, I had to rely on curl to download the images.
- WebKit is an excellent piece of work. I used it as the primary backend for displaying the images. To intercept the handlers, I made each preview image be a link an imaginary 'flickr://' protocol. I then overwrote
webView_decidePolicyForNavigationAction_request_frame_decisionListenerto intercept the call and perform the appropriate actions.
For those who are interested, the source code is here. I did not package it as a stand-alone application because I am not sure what you actually need to run this application. You probably need RubyCocoa and the Ruby 1.8.5. So for simplicity, I just provided the project folder for XCode. If you can build it then you can probably run it.
Rubyfication of Raise Manager
I spent a couple of hours transforming the RaiseMan application from Cocoa Programming for Mac OS X (2nd Edition). In the RubyCocoa examples folder, there is a version of this but it is based on the first edition of the book. My version includes key-value-binding, undo and redo and also alert panels. I also implemented some of the end-of-chapter exercises that I felt were useful. I skipped the part on Localization though.
Rereading the book makes me think of how much I do not like it. There is very little rationale behind each of the examples. Most of the time, the author just says do this or that. And his anecdotes are pretty annoying (I am not sure where he got his stories from). I felt that the book could have been better if the author spent more time explaining why things are done that way instead of listing the API and describing what it does (it's almost identical to the documentation).
Things actually made more sense to me this time around because I was exposed to some design patterns and could see the rationale behind the way of doing things. I am not sure if a beginning programmer would appreciate the way of doings things just from reading this book. I have heard better things about Cocoa Programming but that book is old and has not been updated. I have not read that book yet so I cannot offer my opinions on it.
Anyway, here are somethings I learned from this effort that may be useful to people:
- Before starting, you should read this page on RubyCocoa to get acquainted with the conventions. You can choose to use
somemethodwithargument0_withargument1_withargument2(arg0,arg1,arg2)orsomemethodwithargument0(arg0, :withargument1, arg1, :withargument2, arg2). Once you have decided on one, it is best to stick with it. - Since you cannot drag-and-drop your MyClass.h (we don't have one, we only have MyClass.rb) into Interface Builder to get it to update any new ib_outlets, you need to do this by hand. The easiest way I can think of is to click on the "Classes" tab on the nib file, locate your MyClass and right click to add actions or outlets. That being said, Interface Builder is pretty good for creating user interfaces. The separation between code and user interface is pretty clean and it is not too hard to get things to work the way you want it to.
- For key-value-binding to work on an array, use
kvc_array_accessor. For an example, look at my MyDocument.rb file. More information on key-value-bindings can be found in oc_import.rb in the RubyCocoa source. - Always, always, always, build and run regularly. There is virtually no good debugging support for RubyCocoa. Sometimes the error message can tell you which file (and in the optimistic case, which line the error occurred at). But in general, it is going to be cryptic. By testing early and frequently, you can at least narrow the error down to the last edit that you made.
- Remember to qualify your ib_outlets when you used them with the '@' symbol. For instance, if you have
ib_outlet :some_objectthen in your methods, you refer to that object as@some_object. I am not sure why I keep forgetting this but it has been the cause of many problems. - Remember to always prefix Cocoa classes with
OSX::. You can avoid this by usinginclude OSX. Also be careful that you check the spelling for the Cocoa classes (you need the NS prefix, etc). Misspellings have bitten me quite a few times. - Read oc_attachment.rb in the RubyCocoa source code to find out how you can use Ruby idioms like [], []=, etc for accessing arrays and dictionaries. Also decide if you want to use those notations or just stick with
objectForKey(), etc. - There are some idiosyncrasies with
OSX::NSRunAlertPanel. You cannot do Ruby string substitution in the arguments. If you need string substitution, you can do it using the special @ symbol as such:choice = OSX::NSRunAlertPanel("Delete", "Do you want to delete %@ records?","Delete","Cancel",nil, selected.size).
One problem that I had was that I was not able to build it for release. I had to build it for debug. I think there could be something wrong with the way the project is setup. Anyway, the file is available from here.
Update: I just installed RubyCocoa 0.9 from Subversion. Instructions can be found here. I can now successfully built it as a universal binary. The next test I did was to run this RaiseManager application. I was greeted by a successful build... but the application could not create new employees. The error logs report that there is something wrong with the NSUndoManager but I suspect that it has something to do with key-bindings since there is supposed to be some change to how that is done in RubyCocoa 0.9. I will have to take a look at that. On the bright side, RubyCocoa is approaching the 1.0 mark after so many years!
Update (Jan 3, 2007): The latest version from the Subversion repository has addressed the issues that I reported above. The current working version (revision 1325) was checked in today by Laurent. Suffice to say that there are major additions from RubyCocoa 0.5 that are worth checking out. It would be good to see how RubyCocoa plays with the new Objective-C 2.0 that is included with Leopard. On a side-note, there is a new Ruby/Objective-C bridge out by Tim Burks here.
RubyCocoa
Since I had some free time this week, I wanted to brush up on some Cocoa programming. However, I have not been using Objective-C for some time and would rather not have to program in it. Instead, I decided to use Ruby via the RubyCocoa. I have always had great difficulties installing RubyCocoa on my machine. It never seems to just work. I recall getting version 0.4.3 to work somehow over Spring but the steps seem to elude me now.
I tried searching on the web but there was no clear answer. Even the installation instructions for RubyCocoa seems incomplete (and might even be auto-translated from the Japanese version). Anyway, I downloaded the source code for version 0.5 and follow the instructions to do ruby install.rb config and ruby install.rb setup. The installation failed -- it was not able to locate some of the symbols.
So I reread the instructions on the website. There was some mention of using Ruby 1.8.5. and I only had version 1.8.4 installed. Since Ruby 1.8.5 has been out for some time, I decided to upgrade my version. There are various discussions on what needs to be done to get Ruby 1.8.5 to compile on OS X but I stuck with the instructions from HiveLogic and just substituted the latest version Ruby into the instructions. Everything works fine, even readline. I had to recompile RMagick though since the links were broken after the upgrade. Some might also find the instructions here useful for upgrading.
I followed the steps on the RubyCocoa website and tried to build RubyCocoa. It still was not working. Then I realized something. I was actually issuing the following command: sudo ruby install.rb config --build-universal. I removed the build-universal just to try the default settings. And it WORKED! I am not sure how the developers themselves got the universal binary to build but it was not working on my machine. Right now, RubyCocoa is not a universal binary but that matters little. At least it is finally working.
It's always the simple things that make life so complicated. It also shows that one should always try the default values for configure. After all this, I really think I should have just stuck with rereading some Objective-C. RubyCocoa is a very nice framework but it takes a lot of guess work to get it working. However, as a consolation, I finally got Ruby 1.8.5 installed.