An Introduction to RubyCocoa, Part 2
Pages: 1, 2, 3
You'll notice that the first section of this method looks similar to the addFile method we created earlier. That's because we're creating an instance of the NSSavePanel, which is very similar to the NSOpenPanel. One key difference you'll notice here is that we're calling a method called setAccessoryView and we pass into it our custom view we created in IB. This method adds our custom view to the NSSavePanel, which will give the user the ability to choose the type of archive file he or she wants to create when choosing a name and location for the file.
If the users chose the "OK" button from the NSSavePanel, we create the archive for them. We start this process by first gathering all of the information from the NSSavePanel — such as the file type, file name, and directory. Next, we use this information to construct the tar command that will create an archive file and compress it according to the chosen file type.
After that, we create an instance of the NSAlert class and set it to a successful message to be displayed to the user if all goes well. Now we truly begin the process of creating the archive. We use the system method found in the Kernel module to execute the shell commands that will create our temp directory, copy the files into it, tar and compress the temp directory, copy the tarred file to the chosen directory, and finally, remove the temporary directory and its contents from the system. If everything executed properly, the NSAlert we created earlier will be displayed; otherwise, we will manipulate the NSAlert to display an error message to the user.
If everything got copied into your project properly, and you have the correct permissions to do so, you should be able to run the application and create a new archive file. Assuming that works properly, we have half of our application's functionality working. So let's press on and get the other half working. The next portion of this section will add the ability to extract files from already existing archives.
The extractArchive method is very similar to our createArchive method. First, we must create an instance of the NSOpenPanel class that will allow our users to choose the directory in which the archive file will be located. Then, it will create a tar command to decompress and untar the chosen file. Finally, if all goes well, it will display an alert that notifies the users of the success of the extraction, otherwise an error alert will be displayed. The following code should look somewhat familiar and should be copied into our extractArchive method skeleton:
###
# extractArchive
# Displays an instance of the NSOpenPanel and
# gets the directory in which the extracted
# files will reside. Then, it extracts the
# archived file to that directory.
###
def extractArchive(sender)
oPanel = NSOpenPanel.openPanel
oPanel.setCanChooseFiles(false)
oPanel.setCanChooseDirectories(true)
buttonClicked = oPanel.runModal
if buttonClicked == NSOKButton
filename =
@archiveFile.stringValue.to_s
filetype = filename.split('.')[1]
directory =
oPanel.filenames.objectAtIndex(0)
# Create the tar extraction command
case filetype
when "tgz"
tarCommand = "tar -xvzf " +
"#{filename} -C #{directory}"
when "bz2"
tarCommand = "tar -xvjf " +
"#{filename} -C #{directory}"
when "Z"
tarCommand = "tar -xvZf " +
"#{filename} -C #{directory}"
when "tar"
tarCommand = "tar -xvf " +
"#{filename} -C #{directory}"
end
# Create an alert dialog to display
# the outcome of the tar command
alert = NSAlert.alloc.init
alert.setMessageText(
"Extraction Successful")
alert.setInformativeText(
"The tar file was successfully " +
"extracted")
alert.setAlertStyle(
NSInformationalAlertStyle)
alert.addButtonWithTitle("Ok")
# Extract the files from the archive
begin
system(tarCommand)
rescue
alert.setMessageText(
"Extraction Unsuccessful")
alert.setInformativeText(
"An error occurred while " +
"extracting the archived " +
"file.\nMake sure you have " +
"the correct permissions " +
"and the chosen file exists.")
alert.setAlertStyle(
NSCriticalAlertStyle)
end
alert.beginSheetModalForWindow(
@mainWindow,
:modalDelegate, self,
:didEndSelector, nil,
:contextInfo, nil)
end
end
In the extractArchive method, the first thing you'll notice is that we create an instance of the NSOpenPanel class; However, this instance is implemented slightly different than the one we created for the addFile method. We set up this panel so that only directories can be chosen — not files — and we went with the default of allowing only a single selection.
After this we see something that looks very much like the createArchive method. We create a tar command string to extract the files from the chosen archive into the directory that was selected in the NSOpenPanel. Next, we create an instance of the NSAlert class and call the system command to execute the tar command string we created in the last step. Finally, we display an alert message to notify the user of the success of the operation. If the system command ran into problems we catch it using the rescue clause and display an error message instead.
There's only one thing still missing from our application, and that is one final piece of convenience. Rather than having the users type in the long and tedious names of the files they wish to extract, we need to implement a method that allows the users to select the file using an NSOpenPanel instead. Our browseForArchive method does just this, and its implementation can be found below.
###
# browseForArchive
# Pops up an instance of the NSOpenPanel and
# allows the user to select the archive they
# wish to open.
###
def browseForArchive(sender)
# Create an array of the file types
# allowed in the NSOpenPanel
filetypes = ["tgz", "bz2", "Z", "tar"]
# Create the NSOpenPanel and get the
# archive file to extract
oPanel = NSOpenPanel.openPanel
oPanel.setAllowsMultipleSelection(false)
buttonClicked =
oPanel.runModalForDirectory(nil,
:file, nil,
:types, filetypes)
if buttonClicked == NSOKButton
file =
oPanel.filenames.objectAtIndex(0)
@archiveFile.setStringValue(file)
end
end
Again, we find ourselves in familiar territory with just a few differences from what we saw earlier. The first thing you'll notice is that once again we are creating a new NSOpenPanel object. However, this time around we are only letting the user select specific file types. These file types are the four that we have chosen to deal with in our application and can be found in the filetypes array we created just before creating our NSOpenPanel instance. Once, we have displayed the open panel we make sure the user pressed the "OK" button and then we set the archiveFile NSTextField to display the name of the file the user has just chosen. This method allows our application to be a bit speedier and less error-prone than it would be if it counted on the users typing in the full name of the file they wish to decompress and extract.
Final Thoughts
Well, that's it. You should now have a fully functioning RubyCocoa application. Hopefully, the trip was fun and entertaining, and if you're lucky you even got an application that you may put to use in the end. Just in case you had any trouble along the way — or if you just followed along and didn't participate in the tutorial — I have placed a copy of the Xcode project for this article online here. Do with it what you will, and maybe even try adding in some of the suggestions I've listed below.
Obviously, there are still several areas in which this application could be improved. One area this application could definitely be enhanced is in the number of supported file extensions. Right now the application only supports four different file extensions, but there are many more, as well as compression techniques that could be added to this application to make it even more robust. You could also look into changing all of the open and save panels into sheets rather than modal dialogues. Another great feature would be the ability to open an already existing archive and then add and remove files from it.
All of these are great ways to extend this application and make it something that you use on a daily basis. So those of you out there who enjoyed this article, I believe you have some homework to keep you busy for quite awhile. If you make some good changes send them to me and I'll see if I can't find somewhere to post them for others to get them; just remember to send me the full code.
One last note: I just wanted to share with those of you who have followed my articles up till now. I have really enjoyed writing these tutorials, and I am already formulating some ideas for other Ruby articles in the near future. So if you've liked this series on Ruby programming so far, keep on the lookout for a new batch of Ruby articles from me very soon.
Christopher Roach recently graduated with a master's in computer science and currently works in Florida as a software engineer at a government communications corporation.
Return to MacDevCenter.com.
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 13 of 13.
-
Ruby-esque
2005-03-08 17:17:43 n9yty [Reply | View]
Looks like perl-ish code, not Ruby-esque...
Another example:
for i in 0..count - 1
@files.push(
files.objectAtIndex(i))
end
would be more Ruby-esque as:
0...count.times { |el| @files.push(files.objectAtIndex(el)) }
Wouldn't it? Thanks for the impetus to look into RubyCocoa, though!
-
Code is hard to read
2005-03-05 06:06:11 MrN [Reply | View]
First of all: Pretty cool article. I like to know of the fact that you can do fancy OSX GUI programming with Ruby and how. But there is a little problem that makes it difficult for me to benefit from this article.
The code is a bit hard to read because of e.g. those large comments even around suboptimal code. A suggestion. Instead of (I stripped the comments):
count = indices.length - 1
indices = indices.reverse
for i in 0..count
@files.delete_at(Integer(indices[i]))
end
where count has a misleading name, you could just do:
indices.reverse_each do |f|
@files.delete_at(Integer(f))
end
or something like that.
Now I feel better *g*.
-
Class Controller not found
2004-12-08 14:37:48 chmeee [Reply | View]
After a successful installation in panther (10.3.6) and after following the instructions, I found this when I try tu run the application from Xcode:
Unknown class `Controller' in nib file, using `NSObject' instead.
I haven't found the solution googling... so, do you know why this could be? I have double-checked every step and I have looked for differences with the examples, but no success. Whereas RubyCocoaTar does not run properly, I have no problems with the examples.
Thanks,
Chema.
-
RubyCocoa Binary for Panther (OS X v10.3)
2004-11-16 06:44:25 Christopher Roach |
[Reply | View]
Hi all,
I just wanted to let anyone know who may still be having trouble installing RubyCocoa on their Mac's running OS X Panther edition that I found a link to download a binary version. You can download the disk image at the following URL.
http://homepage.mac.com/kimuraw/archive/RubyCocoa-0.4.1-panther.dmg
I hope this binary installer will help any of you out there still having trouble.
Cheers
-
Installer grief
2004-10-26 04:51:21 Snarke [Reply | View]
I just keep hating Unix more.
OK, so the XCode from this article won't compile, giving the error "/Library/Frameworks/RubyCocoa.framework/Versions/A/Resources/ruby/osx/objc/oc_import.rb:22:in `module_eval': undefined method `NSClassFromString' for OSX:Module (NameError)"
Oh, neat. But maybe it's a problem witn Ruby 1.6.8? OK, I will install Ruby 1.8.1. Now, to my way of thinking, the *stupidest* place to put anything on a Mac is in an invisible folder, so when Ruby 1.8.1 leapt for /usr/local/lib, I reinstalled it into /Library/Ruby, where it belongs.
Then I had to find /usr/bin/ruby and rename it, because 1.6.8 was in front of 1.8.1.
OK, I'm finally getting "ruby 1.8.1 (2003-12-25) [powerpc-darwin]" when I type "ruby -v"
I close and open my XCode app. Nope, still won't compile. Same error. I clean all targets. Same error.
I attempt to reinstall Rubycocoa. I can't. Re-entering "ruby install.rb config" gets me "install.rb:47:in `require': No such file to load -- rbconfig (LoadError)
from install.rb:47
"
I put a link to /Library/Ruby/lib/ruby in /usr/lib/ruby. Nope. I erase my rubycocoa installer directory, and start over with the mysterious, unexplained sequence of commands that starts with CVS. Nope. I look inside "install.rb". There's a --help command, but it's broken; I can't get it to admit there's a way to tell it what the install prefix is, since it fails before getting to that part.
So I reinstall Ruby 1.8.1 with the defaults. NOW I can get the RubyCocoa installer to barf up its --help options. I point it at /Library/Ruby.
This changes the XCode error from "....(NameError)" to "....(NoMethodError)" Everything else is identical. OK, let's reinstall Ruby 1.8.1 back where it belongs, into Library, and clean the targets in XCode. Nope, still NoMethod.
The last time an installer program told me I could tell it where I wanted it to go, then malfunctioned wildly if I was presumptious enough to select anything other than the default, was Windows NT4. (Although the OSX Software Update control panel is completely unable to comprehend the idea that you might put an Application in a subfolder....)
Am I having fun yet?
-
Installer grief
2004-11-07 18:22:59 Christopher Roach |
[Reply | View]
Ok, first, let me begin by saying that I am really sorry that you're having so much trouble trying to get everything working on your system. Also, I would like to apologize for such a long delay in getting back to you, but I have been out of town for business for a while and just recently got a chance to go back through all my missed emails and so forth.
Murphy's Law normally seems to work against me. So, normally if anything can go wrong with an install it will happen to me, but for some reason once I switched to the CVS version of RubyCocoa, it installed without any problems whatsoever. This is nice, since software should never be hard, but it puts me at a bit of a disadvantage when trying to answer your questions since I never ran into these problems.
I did look up your problem on the RubyCocoa-talk forum on Sourceforge.net and found an exact match with the following as their answer:
RubyCocoa requires /usr/bin/cpp3 in "ruby install.rb config". If your
Mac doesn"t have /usr/bin/cpp3, rubycocoa that you build would be broken.
Please install gcc3.1 from XcodeTools1.5 to install /usr/bin/cpp3.
Try out their solution, hopefully that will solve your problem. If not, post back here again and I will see what else I can find.
Thanks for reading the article, and I hope this helps solve your problem. -
Installer grief, continued.
2005-04-28 14:48:28 Snarke [Reply | View]
Didn't work.
I've tried all sorts of things, including the rubycocoa-0.4.1.dmg magic installer program, which (it turns out) is hard-coded to install RubyCocoa into Ruby 1.6.7. Useless for Ruby 1.8.2, as everybody would seem to insist I run.
Anyway, I have confirmed the presense of a /usr/bin/cpp3 file.
> cpp3 --version
cpp3 (GCC) 3.1 20021003 (prerelease)
Now, at this point I really don't know what junk and garbage is still installed where from what, but I started over from the very beginning, in Part I of the article. Used CVS to get a brand-new copy of RubyCocoa-panther (0.4.1, devel-panther, release date of 2003-12-04)
Built, installed, all that jazz. No error messages during Config. During Setup ("building framework target RubyCocoa without using any build style") it appears to have used /usr/bin/gcc-3.3 for compiling. It announced ** BUILD COMPLETED **. Then I find
make
gcc -fno-common -F../../framework/build -framework RubyCocoa -I. -I/usr/local/lib/ruby/1.8/powerpc-darwin7.5.0 -I/usr/local/lib/ruby/1.8/powerpc-darwin7.5.0 -I/Users/snarke/Personal/Programming/Ruby/rubycocoa-panther/ext/rubycocoa -c -o rubycocoa.o rubycocoa.m
gcc: -framework: linker input file unused because linking not done
gcc: RubyCocoa: linker input file unused because linking not done
cc -dynamic -bundle -undefined suppress -flat_namespace -F../../framework/build -framework RubyCocoa -L"/usr/local/lib" -o rubycocoa.bundle rubycocoa.o -lruby -ldl -lobjc
<--- ext/rubycocoa
<--- ext
install.rb: setup done.
</pre>
That doesn't look good to me, whatever it means. I run the Install phase, which doesn't generate any errors or warnings that I can see.
I carry on, following the instructions for the RubyCocoaTar app exactly. The paragraph before "Adding The Guts" starts with the sentence "So, you've tried out your new RubyCocoa application and everything seems to be working fine so far, right?"
Alas, no.
[Session started at 2005-04-28 14:28:55 -0700.]
/Library/Frameworks/RubyCocoa.framework/Versions/A/Resources/ruby/osx/objc/oc_import.rb:22:in `module_eval': undefined method `NSClassFromString' for OSX:Module (NoMethodError)
from /Library/Frameworks/RubyCocoa.framework/Versions/A/Resources/ruby/osx/objc/oc_import.rb:21:in `module_eval'
from /Library/Frameworks/RubyCocoa.framework/Versions/A/Resources/ruby/osx/objc/oc_import.rb:21:in `ns_import'
from /Library/Frameworks/RubyCocoa.framework/Versions/A/Resources/ruby/osx/objc/foundation.rb:5
from /Library/Frameworks/RubyCocoa.framework/Versions/A/Resources/ruby/osx/objc/cocoa.rb:11:in `require'
from /Library/Frameworks/RubyCocoa.framework/Versions/A/Resources/ruby/osx/objc/cocoa.rb:11
from /Library/Frameworks/RubyCocoa.framework/Versions/A/Resources/ruby/osx/cocoa.rb:11:in `require'
from /Library/Frameworks/RubyCocoa.framework/Versions/A/Resources/ruby/osx/cocoa.rb:11
from /Users/snarke/Personal/Programming/My Programs/My Ruby Programs/RubyCocoaTar/build/RubyCocoaTar.app/Contents/Resources/rb_main.rb:9:in `require'
from /Users/snarke/Personal/Programming/My Programs/My Ruby Programs/RubyCocoaTar/build/RubyCocoaTar.app/Contents/Resources/rb_main.rb:9
Executable RubyCocoaTar has exited with status 1.
Ruby is going to be nearly useless to me if I cannot put a decent interface on it. Sigh. I may have to resort to building AppleScript Studio apps that call Ruby command line modules. How sad is that?
-
Installer grief, concluded.
2005-05-25 12:34:22 Snarke [Reply | View]
The answer is I had the wrong RubyCocoa 0.4.1. I think in the CVS instructions in this article, where it says "rubycocoa-panther", is the killer. It needs to be not the '-panther' version, but the current 'real' version, in order to avoid the broken install. I don't understand CVS well enough to be sure.
From the RubyCocoa-talk list:
Although that VERSION says 0.4.1, it isn't the 0.4.1 release. You've checked out an old version of the code that was used for working on initial Panther support.
The VERSION file for 0.4.1 release looks like this:
VERSION = "0.4.1"
STAGE = ""
RELEASE_DATE = "2005-03-25"
Probably easiest to go with the 0.4.1 source tar:
http://prdownloads.sourceforge.net/rubycocoa/rubycocoa-0.4.1.tgz?download
-
Having install troubles
2004-10-16 10:18:31 allClass [Reply | View]
I'm pretty sure something about my install is messed up because even if I open the sample apps and try to run them from xcode I still get errors. For instance:
[Session started at 2004-10-16 10:14:46 -0700.]
/Developer/Examples/RubyCocoa/Pong/build/Pong.app/Contents/Resources/Model.rb:162:in `require': No such file to load -- rbconfig (LoadError)
from /Developer/Examples/RubyCocoa/Pong/build/Pong.app/Contents/Resources/Model.rb:162:in `snd_init'
from /Developer/Examples/RubyCocoa/Pong/build/Pong.app/Contents/Resources/Model.rb:62:in `initialize'
from /Developer/Examples/RubyCocoa/Pong/build/Pong.app/Contents/Resources/AppCtrl.rb:83:in `new'
from /Developer/Examples/RubyCocoa/Pong/build/Pong.app/Contents/Resources/AppCtrl.rb:83:in `ball_init'
from /Developer/Examples/RubyCocoa/Pong/build/Pong.app/Contents/Resources/AppCtrl.rb:77:in `each'
from /Developer/Examples/RubyCocoa/Pong/build/Pong.app/Contents/Resources/AppCtrl.rb:77:in `ball_init'
from /Developer/Examples/RubyCocoa/Pong/build/Pong.app/Contents/Resources/AppCtrl.rb:9:in `awakeFromNib'
from /Developer/Examples/RubyCocoa/Pong/build/Pong.app/Contents/Resources/rb_main.rb:15:in `NSApplicationMain'
from /Developer/Examples/RubyCocoa/Pong/build/Pong.app/Contents/Resources/rb_main.rb:15
Executable rubyapp has exited with status 1.
Any help appreciated -
Having install troubles
2004-10-16 17:26:44 Christopher Roach |
[Reply | View]
Well, I have two things to mention here. First, truthfully, I still have problems with some of the samples that were supllied. The Pong sample is one of these. So, give the RubyTypingTutor sample a try and see what happens. That one worked for me.
Second, I did a search on my system for the file you're having trouble loading (the rbconfig file) and it only shows up in one place--my Ruby install folder, not my RubyCocoa folder. I am running Ruby 1.8.1 and I read over some of the info at the RubyCocoa site and it seems like there are some problems with running the default version of Ruby that comes with the Mac. So, you may want to try installing the latest version of Ruby and then Reinstalling RubyCocoa. That may solve the problem your having.
If either of these don't seem to work for you, write me back and I'll see if I can come up with some other solution.
Good luck. -
Having install troubles
2004-11-07 12:50:28 leeg [Reply | View]
I'm having what seems to be the same problem as the other people who've written here - I wonder whether you or anyone else has discovered a solution yet? The build error (this is with the stub 'puts' lines in Controller.rb) is:
/Library/Frameworks/RubyCocoa.framework/Versions/A/Resources/ruby/osx/objc/oc_import.rb:22:in `module_eval': undefined method `NSClassFromString' for OSX:Module (NoMethodError)
from /Library/Frameworks/RubyCocoa.framework/Versions/A/Resources/ruby/osx/objc/oc_import.rb:21:in `module_eval'
from /Library/Frameworks/RubyCocoa.framework/Versions/A/Resources/ruby/osx/objc/oc_import.rb:21:in `ns_import'
from /Library/Frameworks/RubyCocoa.framework/Versions/A/Resources/ruby/osx/objc/foundation.rb:5
from /Library/Frameworks/RubyCocoa.framework/Versions/A/Resources/ruby/osx/objc/cocoa.rb:11:in `require'
from /Library/Frameworks/RubyCocoa.framework/Versions/A/Resources/ruby/osx/objc/cocoa.rb:11
from /Library/Frameworks/RubyCocoa.framework/Versions/A/Resources/ruby/osx/cocoa.rb:11:in `require'
from /Library/Frameworks/RubyCocoa.framework/Versions/A/Resources/ruby/osx/cocoa.rb:11
from /Users/leeg/devel/Cocoa/RubyTar/build/RubyTar.app/Contents/Resources/rb_main.rb:9:in `require'
from /Users/leeg/devel/Cocoa/RubyTar/build/RubyTar.app/Contents/Resources/rb_main.rb:9
Some potentially useful configuration information:
mallet:~ leeg$ ruby -v
ruby 1.8.2 (2004-07-29) [powerpc-darwin7.5.0]
mallet:~ leeg$ uname -a
Darwin mallet.local 7.5.0 Darwin Kernel Version 7.5.0: Thu Aug 5 19:26:16 PDT 2004; root:xnu/xnu-517.7.21.obj~3/RELEASE_PPC Power Macintosh powerpc
XCode 1.5
Component versions
Xcode IDE: 389.0
Xcode Core: 387.0
ToolSupport: 372.0
I did a cvs up on rubycocoa to make sure that the problem persisted, just before posting this message. Indeed it does. Thanks, leeg.
-
Having install troubles
2004-11-07 18:25:12 Christopher Roach |
[Reply | View]
Hi, I just posted a response above to the "Installer Grief" posting that I think may also pertain to your problem. I hope it helps, but let me know if it doesn't and I'll see if I can dig something else up for you to try.
Thanks for reading the article and I hope that this solves your problems.






When I Build & Go I get this in the console:
2007-12-30 01:56:57.995 RubyCocoaTar[48109:10b] Could not connect the action extractArchive: to target of class NSButtonCell
2007-12-30 01:56:58.003 RubyCocoaTar[48109:10b] Could not connect the action extractArchive: to target of class NSButtonCell
2007-12-30 01:56:58.008 RubyCocoaTar[48109:10b] Could not connect the action browseForArchive: to target of class NSButtonCell
2007-12-30 01:56:58.011 RubyCocoaTar[48109:10b] Could not connect the action addFile: to target of class NSButtonCell
2007-12-30 01:56:58.011 RubyCocoaTar[48109:10b] Could not connect the action removeFile: to target of class NSButtonCell
Can anyone offer advice?