Archive for October, 2009

MacRuby tips: browse for folder or file dialog

This is yet another pretty simple tip.
Use case: let say you want your applications users to choose one or multiple files or folder on their file system. A good example would be that you want the user to choose a file to process or a folder where to save some data.

In the example above, I added a browse button and a text field.

I would like my users to click on the browse button, locate a folder and display it in the text field.

In your MacRuby controller, use a simple action method as well as an accessor to the text field:

1
2
3
4
attr_accessor :destination_path
 
def browse(sender)
end

Now, in Interface builder bind the destination_path outlet to the text field you want to use to display the path and bind the button to the browse action.

Let’s go back to our action method and let’s create a dialog panel, set some options and handle the user selection:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def browse(sender)
  # Create the File Open Dialog class.
  dialog = NSOpenPanel.openPanel
  # Disable the selection of files in the dialog.
  dialog.canChooseFiles = false
  # Enable the selection of directories in the dialog.
  dialog.canChooseDirectories = true
  # Disable the selection of multiple items in the dialog.
  dialog.allowsMultipleSelection = false
 
  # Display the dialog and process the selected folder
  if dialog.runModalForDirectory(nil, file:nil) == NSOKButton
  # if we had a allowed for the selection of multiple items
  # we would have want to loop through the selection
    destination_path.stringValue = dialog.filenames.first
  end
end

That’s it, your user can now browse for a folder and the selection will be displayed in the text field. Look at the NSOpenPanel documentation for more details on the Cocoa API.

, ,

1 Comment

RailsSummit – an amazing adventure

I have had the opportunity to go to and speak at many conferences but this year was the first time I had the chance to go to RailsSummit in São Paulo, Brazil.

I was really looking forward to this trip and I have to say it went beyond my expectations. I had really good feedback from people like the Phusion guys (Ninh & Hongli), Gregg Pollack and others.

For those who don’t know, RailsSummit is one of the biggest, if not the biggest, Ruby event in Latin America. It’s organized yearly by Locaweb, the #1 hosting company in Brazil (props to Akita & Cristiane for their work).


As part of my involvement in the Ruby community I have met a lot of Brazilians always willing to help and especially giving time to translate content. (A good example would be the Portuguese version of the Rails wiki or José Valim GSOC contribution) However, I did not realize how fast Rails was growing over here.

To come back to the conference, I have to say it was one of the best conference I have gone to. Chad Fowler, who gave the opening keynote, later told me that it reminded him of the first Ruby conferences in the US . For me, what made a huge difference was the fact that it was a very positive conference. People were happy to be here, eager to share and you could feel the passion. Unfortunately, I missed the first few Ruby conferences, but I can totally imagine how must have been. Passionate people, not trying to push their products but instead, share the cool stuff they’ve been working on. This is exactly the feeling I had during this conference.

Maybe it’s because I don’t understand Portuguese well enough or maybe it’s just a cultural thing, but the people at the conference were just super friendly and always willing to help. I was really glad to meet those who have been using some of my work, some new people to Ruby and people who don’t do Ruby but were just interested in knowing what was going on in the Ruby world.

The schedule was pretty packed and the discussions very interesting, you could certainly feel the excitement in the air. Ruby seems to catching up quickly over there. Brazilian Rubyists seem to be very pragmatic and a good illustration of that was certainly made by Vinicus Teles, Brazil Agile XPert, who shared tips on how to release a successful product.

I stayed a few days after the conference and went to visit the Rubyists in Rio. Rio is a great city. It has some of the best soccer players in the world and some seriously talented software developers. Ruby & Rails are not just the new trendy startup secret to success, companies like globo.com, currently the largest TV network in Latin America and the fourth largest in the world, also started using Ruby and Rails. I had the opportunity to visit their office and meet their teams. It’s very exciting to see how they use Agile/XP and Ruby and how they seem to be so successful. But I will keep that for another post.

Overall, even though the actual traveling to/from Brazil was a bit long, RailsSummit was a blast. I really hope to be able to come back next year, and by then, hopefully my Portuguese will have improved.

, , ,

7 Comments

MacRuby tips: capturing keyboard events

If you are writing any type of games you might want your users to interact with your application using their keyboards.

This is actually not that hard. The approach is simple and fast forward if you are used to Cocoa.

Everything starts in Interface Builder, add a custom view instance to your window.

Now switch to your project and a new file with a class called KeyboardControlView and make in inherit from NSView. We are creating a subview of NSView so we will be able to make our top view “layer” use this subclass.

class KeyboardControlView < NSView
  attr_accessor :game_controller
 
  def acceptsFirstResponder
    true
  end
 
end

As you can see in the example above, I added an attribute accessor. attr_accessor class method creates getters and setters. It’s basically the same as writing:

 def game_controller=(value)
  @game_controller = value
end
 
def game_controller
  @game_controller
end

MacRuby is keeping an eye on these accessors and let bind outlets to them.
But let’s not get ahead of ourselves, we’ll keep that for another time.

Let’s go back to our newly created class. Notice, we also added a method called `
acceptsFirstResponder` and returns true. acceptsFirstResponder returns false by default.
But in this case we want it to return true so our new class instance can be first in the responder chain.

Now that our class is ready, let’s go back to Interface Builder, select our new custom view and click on the inspector button.


Click on the (i) icon and in the Class field choose our new KeyboardControlView.
Yep, our new class just shows up by magic, it’s also called the lrz effect, just don’t ask ;)
So now when our application starts, a new instance of our NSView class is created and Cocoa will call different methods based on events triggered.

The two methods we are interested in reimplementing are keyDown and keyUp. They get called when a key gets pressed or released.

def keyDown(event)
  characters = event.characters
  if characters.length == 1 && !event.isARepeat
    character = characters.characterAtIndex(0)
    if character == NSLeftArrowFunctionKey
      puts "LEFT pressed"
    elsif character == NSRightArrowFunctionKey
      puts "RIGHT pressed"
    elsif character == NSUpArrowFunctionKey
      puts "UP pressed"
    elsif character == NSDownArrowFunctionKey
      puts "DOWN pressed"
    end
  end
 super
end

I don’t think the code above needs much explanation. The only things that you might not understand are ‘event.isARepeat’. This method returns true if the user left his/her finger on the key. The other thing is the use of the ‘super’ call at the end of the method. Basically, we reopened a method that was already defined and we don’t want to just overwrite it, we just want to inject out code within the existing method, so once we are done handling the event, we just pass it back to original method.

Final result:

class KeyboardControlView < NSView
  attr_accessor :game_controller
 
  def acceptsFirstResponder
    true
  end
 
  def keyDown(event)
    characters = event.characters
    if characters.length == 1 && !event.isARepeat
      character = characters.characterAtIndex(0)
      if character == NSLeftArrowFunctionKey
        puts "LEFT pressed"
      elsif character == NSRightArrowFunctionKey
        puts "RIGHT pressed"
      elsif character == NSUpArrowFunctionKey
        puts "UP pressed"
      elsif character == NSDownArrowFunctionKey
  	puts "DOWN pressed"
      end
    end
    super
  end
 
  # Deals with keyboard keys being released
  def keyUp(event)
    characters = event.characters
    if characters.length == 1
      character = characters.characterAtIndex(0)
      if character == NSLeftArrowFunctionKey
       puts "LEFT released"
      elsif character == NSRightArrowFunctionKey
        puts "RIGHT released"
      elsif character == NSUpArrowFunctionKey
       puts "UP released"
      elsif character == NSDownArrowFunctionKey
        puts "DOWN released"
      end
    end
    super
  end
 
end

Now it’s up to you to handle the other keystrokes and do whatever you want. That’s it for this tip, I hope it helps.

No Comments

MacRuby 0.5 beta 1 and Textorize

Good news everyone!

MacRuby beta 1 has been released! Official announcement here.

Download MacRuby 0.5 beta1

Download MacRuby 0.5 beta1

Note that the download is only for SnowLeopard, intel machines.

Lots of great stuff in this new release, the first one based on LLVM. Check the Laurent’s post to learn more about the work done on compilation, optimization, concurrency, compatibility and Cocoa interaction. And a big thank you to Laurent Sansonetti who is putting so much effort in this project!

However, don’t forget it’s still a beta release and you might encounter bugs. Feel free to report them in the bug tracker or ask on the mailing list.

On a different topic, the other day, John Gruber from Daring Fireball wrote a quick note about Thomas Fuchs’ textorize script which since got its own place on the internet http://textorize.org/.

Textorize is a Ruby-based font rasterizer command line utility for Mac OS X. It generates PNG files from an input string and options, using Mac OS X’s pristine typography facilities. As John said, it’s a case where a few lines of Ruby code beat Photoshop.

Thomas version is based on RubyCocoa which is great… but not MacRuby.

To celebrate MacRuby 0.5 beta1, I ported the gem over and pushed it to the excellent gemcutter.org facility.

After installing MacRuby beta, follow these directives:

$ macgem sources -a http://gemcutter.org
$ sudo macgem install textorize-mr
$ textorize -f"Didot" -s200 "MacRuby 0.5b1"
$ open output.png

And you will get a subpixel antialiased fancy bitmap like that:

macruby05b1

Check http://textorize.org/ for more examples and http://github.com/mattetti/textorize for the source code.

, , , ,

1 Comment

MacRuby tips: how to play an audio file

Let’s say you would like to play an audio file in your MacRuby app/script, how would you do?
It’s actually pretty simple, you just need to use NSSound. If we wanted to use a system sound we could do:

NSSound.soundNamed('Basso').play

But let’s look at a more advanced example with some Cocoa patterns. We will loop through all the audio files in a folder and we will play them one after the other.

#!/usr/bin/env macruby
framework 'Cocoa'
# Cocoa documentation reference:
# http://developer.apple.com/mac/library/documentation/Cocoa/Reference/ApplicationKit/Classes/NSSound_Class/Reference/Reference.html
 
def play_sound
  if @sounds.empty?
    NSApplication.sharedApplication.terminate(nil)
  else
    sound_file = @sounds.shift
    s = NSSound.alloc.initWithContentsOfFile(sound_file, byReference: false)
    puts "previewing #{sound_file}"
    s.delegate = self
    s.play
  end
end
 
# This is a delegate method called by the sound object
def sound(sound, didFinishPlaying: state)
  play_sound if state
end
 
# Delegate method called when the app finished loading
def applicationDidFinishLaunching(notification)
  @sounds = Dir.glob("/System/Library/Sounds/*.aiff")
  play_sound
end
 
# We are delegating the application to self so the script will know when
# it finished loading
NSApplication.sharedApplication.delegate = self
NSApplication.sharedApplication.run

On my machine, I get the following output:

$ macruby macrubysound.rb
previewing /System/Library/Sounds/Basso.aiff
previewing /System/Library/Sounds/Blow.aiff
previewing /System/Library/Sounds/Bottle.aiff
previewing /System/Library/Sounds/Frog.aiff
previewing /System/Library/Sounds/Funk.aiff
previewing /System/Library/Sounds/Glass.aiff
previewing /System/Library/Sounds/Hero.aiff
previewing /System/Library/Sounds/Morse.aiff
previewing /System/Library/Sounds/Ping.aiff
previewing /System/Library/Sounds/Pop.aiff
previewing /System/Library/Sounds/Purr.aiff
previewing /System/Library/Sounds/Sosumi.aiff
previewing /System/Library/Sounds/Submarine.aiff
previewing /System/Library/Sounds/Tink.aiff

Cocoa delegation might seem a bit strange at first when you come from Ruby. In Ruby we rarely do any type of async delegation so let’s quickly look at what’s going on in this script.

The first thing we do is loading the cocoa framework which makes total sense, then we define a bunch of methods and finally we get to:

NSApplication.sharedApplication.delegate = self
NSApplication.sharedApplication.run

We are setting the run loop to delegate to set, which means that when an event is triggered, the delegation method will be called on self (our script).
Once that done, we are starting out run loop.

Once the application is loaded the applicationDidFinishLaunching delegate is being triggered (line 24). At this point, we are looking for all the sound files in the sound system folder and storing them in an instance variable (line 25). Finally, we are calling the play_sound method (line 26).

The play_sound method checks that we have some audio files left to play (line 7), otherwise it quits the app. (line 8 ) If we still have some files in the queue, we get the first one (line 10) and use it to create an instance of NSSound (line 11).

Before playing the NSSound instance, we are setting its delegate to our script (self) (line 13). If you read NSSound Cocoa documentation for #play, you will notice that #play “initiates playback asynchronously and returns control to your application. Therefore, your application can continue doing work while the audio is playing.” In our case, we don’t want to do that, otherwise all the sounds will play at the same time.

The documentation also mentions a delegation you can use to avoid that. This is exactly what we did when we implemented:  def sound(sound, didFinishPlaying: state) (line 19)

Rubyists might be surprised by the method signature. MacRuby extended Ruby to support Objective-C selectors.

When the sound is playing, this delegate gets called, we check on the state of the sound, if it finished playing then we call play_sound again.

That’s it!  It’s a very elegant implementation giving us the benefits of both Ruby and Cocoa.

, , ,

No Comments

MacRuby tips: embed a custom font

Let say you want to release your MacRuby app and use a custom embedded font?
You probably don’t want to force your users to install the font.
Well, don’t worry, just put the font file in your resources folder and use the following code:

font_location = NSBundle.mainBundle.pathForResource('MyCustomFont', ofType: 'ttf')
font_url = NSURL.fileURLWithPath(font_location)
# in MacRuby, always make sure that cocoa constants start by an uppercase
CTFontManagerRegisterFontsForURL(font_url, KCTFontManagerScopeProcess, nil)

That’s it, now your custom font is available and you can change your textfield instance’s font like that:

text_field.font = NSFont.fontWithName('MyCustomFont', size:24)

The only tricky things here were to know the Cocoa API call and to know that even if the Cocoa API references the constant to use as kCTFontManagerScopeProcess, because in Ruby, constants start by an uppercase, you need to convert it to: KCTFontManagerScopeProcess.

, , , , , ,

2 Comments

MacRuby soon to reach a new milestone

Laurent just posted a MacRuby status update on the mailing list and the first official beta of MacRuby 0.5 should be released pretty soon.

Let’s quickly look at Laurent’s report:

  • Early backtracing support.
  • Much better AOT compilation. Parts of the standard library are now pre-compiled for testing.
  • Migrated to LLVM top of tree.
  • Dispatcher performance is now back to normal (we lost about 30% due to gcc not inlining code).
  • Many bug fixes.

In lay terms, backtracing is what you see when your app crashes or has a problem, it’s the list of methods called before the exception was raised and the line where the error happened. Currently the backtrace is similar to what you would have with Ruby 1.9, however objective-c exceptions are not supported and there is still some work to do.

AOT compilation or Ahead Of Time compilation is the process of compiling a script into machine code. I already covered that feature earlier. Progress has been made and now some Ruby standard libraries are now pre-compiled in MacRuby. The two main advantages of doing AOT compilation are startup speed and obfuscation. Two important features for desktop applications or for when you want to license your server app.

Updating LLVM doesn’t mean much for end users. With the support of the MacRuby team, Claudio setup a nightly build bot http://macruby.icoretech.org/ allowing you to download nightly builds for SnowLeopard. The app is a Sinatra app that could can checkout on github:  http://github.com/masterkain/macruby-nightlies-web What’s interesting is that Apple is sponsoring both LLVM and MacRuby which will hopefully bring some synergy and help both projects.

Fixed dispatcher, this is a just a perf bug fix only affecting people on trunk.

I personally used MacRuby quite a lot recently. I have been writing a 2D video game for my RubyConf talk.

It’s a simple game only using Ruby and Cocoa. It’s not a fancy game and, no, it doesn’t run on the iPhone yet ;)

I never wrote a game in Cocoa and I decided not to use an existing framework like Gosu or cocos2d, instead I decided to write everything from scratch. Using CoreAnimation instead of OpenGL, the task wasn’t that hard at all. in less than 1,000 LOC (before refactoring), I have a fully working game.

I will be previewing the game at RailsSummit and will show the code and explain how to get there at RubyConf.

When I started getting involved with MacRuby, I really did not think I would write a video game in Ruby and actually enjoy it. At the end of the day, I will more than likely use MacRuby for desktop/mobile/server apps more than games, but it’s awesome to be able to use your favorite language to do other things than what you are usually paid to do.

MacRuby is coming along very nicely, and I’m really excited about the multitude of new options offered to Ruby developers on OSX and can’t wait for 0.5 final and see what people will do with it.

11 Comments