17 November 2008

SketchUp 7 is Out!

For my day job, I work on SketchUp, a 3D modeling tool written for designers, not CAD jockeys (no offense to CAD jockeys, ahem). SketchUp is by far my favorite Google product. @Last software, the original creators of SketchUp, was acquired by Google about a year before I got here. Fortunately, we have been able to keep the same passionate development team together (except for a few additions, like your truly). I really love working with these guys.

Today, it has finally gone public that we are releasing SketchUp 7. You can even see a video with me in it on that page, though I can't really recommend my acting. I work on the 2D presentation tool called LayOut. I helped develop the new curve tool, the editable paths, the move rotate and scale manipulators, and the in-place group edit. It has been a great year and I'm really proud of what we've come up with.

So that's it for this week. It has been a crazy few months. I think we're all looking forward to Christmas break!

26 August 2008

Localization and Internationalization

With a co-worker, I recently had the chance to investigate how the Mac OS localization system works. We were curious to see how difficult it would be to convert several interdependent projects (frameworks and applications) to use the suggested ISO language designators (e.g. en.lproj, es.lproj) from the deprecated language designators (e.g. English.lproj, Spanish.lproj). Recorded below are some of the lessons we learned.

The Set Up

According to the Internationalization Programming Topics (applicable page), the old language designators (English.lproj, Spanish.lproj, German.lproj, etc) are deprecated, and could be discontinued in the future. The documentation strongly suggests using the ISO language designators (en.lproj, es.lproj, de.lproj, etc). Judging from Apple's own applications, the OS will be supporting the legacy designators for some time to come. Still, we want to be up to date so it was a little surprising to see that Xcode seems to favor these legacy language designators rather than the ISO labels. The default application template includes an English.lproj directory and sets the Development Region to be English in the Info.plist.

Still, Xcode makes adding new localizations pretty easy. Select the resource that you would like to localize in the file Group Tree and then Get Info. At the bottom of the General tab, there is an "Add Localization" button. When you click on it a sheet appears with a combo box that you can enter the name of the localization. Again, Xcode give examples in the combo box of the deprecated language designators, but it is easy enough to type in the two letter ISO language designator. Once you do this, a new lproj directory is created and a copy of the resource is placed in it.

We played around a bit with a simple application that simply had an image well and a text field on a window. We created several localizations and put custom text and custom images into the text field and image well, respectively, for each locale. Then, we built the application and started moving things around to see what the localization system would do.

Test One, Removing .lproj Directories

Without describing all of the permutations we went through, here's the final experiment we performed. In the Contents/Resources directory, we had the following lproj directories: English.lproj, en.lproj, Spanish.lproj, es.lproj, German.lproj, and de.lproj. In the System Preferences, in the International Pane, we set our first three Languages to be German, then Spanish, then English.

First we opened the application and it showed we were using the resources in the de.lproj directory. We renamed the de.lproj directory and then relaunched the application. The German.lproj directory was used. After commenting out German.lproj, the es.lproj was used, then the Spanish.lproj, then en.lproj, and finally English.lproj. There were two lessons learned here. First, the ISO names are preferred and the language search order in the System Preferences is honored.

Test Two, Removing Resources

Next, we wanted to see what would happen if we deleted resources from within the lproj directories. Our thought from the above experiment was that it would follow the same search path until it found the resource in a localized directory. As we discovered above, when we removed an entire lproj directory, it would fall back to the next most appropriate language. However, if we only removed a resource within an lproj directory, it wouldn't fall back to the next appropriate language, but it would fall back to the English.lproj. Why was it favoring English.lproj, when we had en.lproj and it seemed to favor en.lproj in our first test? The answer is that once the appropriate lproj is found, all of the others are ignored, with the one exception being the lproj designated as the Development Region in the Info.plist.

As described in The Bundle Programming Guide (relevant section) the search for resources goes something like this:

  1. Look for a non-localized verison of the resource.
  2. If that doesn't work, look in the lproj folder that the system tells you to use.
  3. If that doesn't work, look in the lproj that has been designated as the Development Region in the Info.plist.

Sure enough, along with the default English.lproj that Xcode creates for you, it also sets up English as the Development Region (see the CFBundleDevelopmentRegion key in the Info.plist). Once we change the value of that key to en the application started using en.lproj as the fallback for missing localized resources. So the lesson learned with this test is If it can't find the resources, it will fall back to the Development region lproj.

Summary

So, with these experiments under our belts, we feel much more comfortable about converting to the ISO names. We just need to remember:

  • The ISO names are preferred. Names like English.lproj are deprecated.
  • ISO names are searched before deprecated names when the system selects the appropriate lproj.
  • If the system can't find the appropriate lproj, it will search for other languages in the order specified in the International Preference Pane.
  • If a resource isn't found in the selected lproj, it falls back to the lproj designated as the development region with the CFBundleDevelopmentRegion in the Info.plist.

This is a sophisticated system of localization that is predictable and flexible, when you know what is going on. Hopefully, after reading this, you do!

15 August 2008

Using Google Testing Framework in your Xcode Projects

Google has recently released open source project called the Google Testing Framework (blog post, project site), or gtest for short. This post will explain how you'll be able to use this unit testing framework in your own Mac OS X projects. This tutorial, which is also posted here, begins by quickly explaining what to do for experienced users, then go into depth about each step with additional explanation below. Finally, there is an example project that uses gtest that you can download to get started.

Quick Start

Here is the quick guide for using gtest in your Xcode project.

  • Download the source from the website using this command: svn checkout http://googletest.googlecode.com/svn/trunk/ googletest-read-only
  • Open up the gtest.xcodeproj in the googletest-read-only/xcode/ directory and build the gtest.framework.
  • Create a new "Shell Tool" target in your Xcode project called something like "UnitTests"
  • Add the gtest.framework to your project and add it to the "Link Binary with Libraries" build phase of "UnitTests"
  • Add your unit test source code to the "Compile Sources" build phase of "UnitTests"
  • Edit the "UnitTests" exectable and add an environment variable "DYLD_FRAMEWORK_PATH" with the value of the relative path to the framework containing the gtest.framework.
  • Build and Go

Now, I'll go into depth to each of these steps, describing in more detail how to complete it and mentioning some variations.

Get the Source

Currently, the gtest.framework discussed here isn't available in a tagged release of gtest, it is only available in the trunk. As explained at the gtest svn site, you can get the code from anonymous SVN with this command:

svn checkout http://googletest.googlecode.com/svn/trunk/ googletest-read-only

Alternatively, if you are working with Subversion in your own code base, you can add gtest as an external dependency to your own Subversion repository. By following this approach, everyone that checks out your svn repository will also receive a copy of gtest (a specific version, if you wish) without having to check it out explicitly. This makes the set up of your project simpler and reduces the copied code in the repository.

To use svn:externals, figure out where you would like to have the external source reside. I choose to put the external source inside the trunk, because I want it to be part of the branch when I make a release. However, keeping it outside the trunk in a version-tagged directory called something like third-party/gtest/1.0.1, is another option. Then, use svn propedit svn:externals _directory_ to set the property on a directory that will contain the code. The directory that receives the svn:externals property must already be in the repository.

The command svn propedit will bring up your Subversion editor, making editing the long, (potentially multi-line) property simpler. This same method can be used to out a tagged branch, by using the appropriate URL (e.g. http://googletest.googlecode.com/svn/tags/release-1.0.1). Additionally, the svn:externals property allows the specification of a particular revision with the -r_##_ option (e.g. externals/src/googletest -r60 http://googletest.googlecode.com/svn/trunk).

Here is an example of using the svn:externals properties on the trunk (read via svn propget) of a project. This value checks out a copy of gtest into the trunk/externals/src/googletest/ directory.

[Computer:svn] user$ svn propget svn:externals trunk
externals/src/googletest http://googletest.googlecode.com/svn/trunk

Add the Framework to Your Project

The next step is to add the gtest framework to your own project. I'll describe the two most common ways below.

  • Option 1 — The simplest way to add gtest to your own project, is to open gtest.xcodeproj (found in the xcode/ directory of the gtest trunk) and build the framework manually. Then, add the built framework into your project using the "Add->Existing Framework..." from the context menu or "Project->Add..." from the main menu. The gtest.framework is relocatable and contains the headers and object code that you'll need to make tests. This method requires rebuilding every time you upgrade gtest in your project. How often that is, depends on you.
  • Option 2 — If you are going to be living off the trunk of gtest, incorporating its latest features into your unit tests (or are a gtest developer yourself). You'll want to rebuild the framework every time the source updates. to do this, you'll need to add the gtest.xcodeproj file to you project. Then, from the build products that are revealed by the projects disclosure triangle, you can find the gtest.framework which can be added to your targets (discussed below).

Make a Test Target

To start writing tests, make a new "Shell Tool" target. This target template is available under BSD, Cocoa, or Carbon. Add your unit test source code to the "Compile Sources" build phase of the target.

Next, you'll want to add gtest.framework in two different ways, depending upon which option you chose above.

  • Option 1 — During compilation, Xcode will need to know that you are linking against the gtest.framework. Add the gtest.framework to the "Link Binary with Libraries" build phase of your test target. This will include the gtest headers in your header search path, and will tell the linker where to find the library.
  • Option 2 — If your working out of the trunk, you'll also want to add gtest.framework to your "Link Binary with Libraries" build phase of your test target. In addition, you'll want. to add the gtest.framework as a dependency to your own target. This way, Xcode will make sure that gtest.framework is up to date, every time your build your target. In addition, if you don't share build directories with gtest, you'll have to copy the gtest.framework into your own build products directory using a "Run Script" build phase.

Set Up the Executable Run Environment

Since the unit test executable is a shell tool, it doesn't have a bundle with a Contents/Frameworks directory, in which to place gtest.framework. Instead, the dynamic linker must be told at runtime to search for the framework in another location. This can be accomplished by setting the "DYLD_FRAMEWORK_PATH" environment variable in the "Edit Active Executable ..." Arguments tab, under "Variables to be set in the environment:". The path for this value is the path (relative or absolute) of the directory containing the gtest.framework.

If you haven't set up the DYLD_FRAMEWORK_PATH, correctly, you might get a message like this:

[Session started at 2008-08-15 06:23:57 -0600.]
    dyld: Library not loaded: @loader_path/../Frameworks/gtest.framework/Versions/A/gtest
      Referenced from: /Users/username/Documents/Sandbox/sidelight/svn/trunk/samplecode/gTestExample/build/Debug/WidgetFrameworkTest
      Reason: image not found

To correct this problem, got to the direcotry containing the executable named in "Referenced from:" value in the error message above. Then, with the terminal in this location, find the relative path to the directory containing the gtest.framework. That is the value you'll need to set as the DYLD_FRAMEWORK_PATH.

Build and Go

Now, when you click "Build and Go", the test will be executed. Dumping out something like this:

[Session started at 2008-08-06 06:36:13 -0600.]
[==========] Running 2 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 2 tests from WidgetInitializerTest
[ RUN      ] WidgetInitializerTest.TestConstructor
[       OK ] WidgetInitializerTest.TestConstructor
[ RUN      ] WidgetInitializerTest.TestConversion
[       OK ] WidgetInitializerTest.TestConversion
[----------] Global test environment tear-down
[==========] 2 tests from 1 test case ran.
[  PASSED  ] 2 tests.

The Debugger has exited with status 0.  

Example

I've included an example project in the sidelight project called gtestSample. It is a self contained example (including the svn:externals property) that you can check out using:

svn checkout http://sidelight.googlecode.com/svn/trunk/samplecode/gtestSample  gtestSample

Next, build the gtest.framework included in the externals directory of the gtestSample. The WidgetFramework.xcodeproj builds a simple C++ framework and uses gtest to test its only class. Note: The WidgetFramework.xcodeproj expects the gtest.framework to be built in its own project directory.

Summary

Unit testing is a valuable way to ensure your data model stays valid even during rapid development or refactoring. The Google Testing Framework is a great unit testing framework for C and C++ which integrates well with an Xcode development environment.

05 August 2008

The Google Testing Framework

The autotools scripts that come with googletest work just fine on Mac OS X, but they build only for a single architecture. If you want to use it with a universal binary (say 32- and 64-bit x86) you have to build it twice, and then use lipo to stick it together. To avoid that hassle, I put together an Xcode project to generate a universal-binary version of the framework (it is pretty easy, in Xcode). I learned (and relearned) a few things along the way:

  • You can preprocess the Info.plist (of course), and you can specify a file to be its prefix header (build setting: "Info.plist Preprocessor Prefix File" or "INFOPLIST_PREFIX_HEADER"). This is a relatively easy way to do string substitution into your Info.plist and I used it in gtest.framework to set up some version strings. One tricky thing is that the Info.plist seems to be generated before any other build phase. Hence, you can't have a "Run Script" phase extract the version strings and generate the prefix header and then use it later on in a different build phase. If you do want to autogenerate the Info.plist prefix header, the "Run Script" has to be part of a separate target and make that target a dependency of the target that generates the Info.plist.
  • The $DERIVED_FILE_DIR is great for storing intermediate files. When you clean the project, the files you put in here are thrown out. Also, it appears that files you place in this directory are included in the "HEADER_SEARCH_PATHS". This is the destination I chose for the Info.plist prefix header.
  • Make liberal use of the "Input Files" and "Output Files" of the Run Script Build Phase. They actually work as advertised. The script will not be run unless files in these locations have been touched.
  • If you want to build a test executable that uses external frameworks (such as gtest.framework) without pulling everything together into a bundle, you can specify the DYLD_FRAMEWORK_PATH in the executable's environment variables and set it to the location of the framework. This should not be used for production executables code, only test code.

The trunk of the googletest project now includes the Xcode project to build the universal-binary framework.

14 July 2008

Adding svn:externals to Your Project

Subversion provides an easy way to add references to other projects. These projects might provide frameworks or other resources that your project needs. Rather than copying these resources into your own repository and trying to manage revisions yourself, Subversion allows you to specify a link to the external project. This is done with the svn:externals directory property.

To add an external project to your own, first decide where you would like it to live. One option is to keep a directory outside of the trunk directory called externals or third-party (the name is up to you) and then add tagged versions of the external code to the directory as needed. Another option is to place the externals directory inside the trunk and then when you branch or the trunk, the correct version of the external project will travel to the branches directory. In any event, you first have to decide where you want the external project to reside within your own.

Next, use the svn propedit svn:externals <dir_name> on a directory that is versioned. The format of the property is destination_dir [-rversion] source_url. Since svn:externals can be a multiline property, it is best to use the svn propedit command.

Then, do an svn update and your done!

Example

I use two (so far) external projects in the Sidelight Project. I use the Xcode configuration files from the Google Toolbox for Mac and the Google Testing Framework called googletest for unit testing the C++ frameworks. I put these external frameworks in a directory called "externals" just under the "trunk" directory in the repository. An svn propget svn:externals on trunk reveals:

computer:sidelight username$ svn propget svn:externals trunk
externals/src/google-toolbox-for-mac/1.5/XcodeConfig  http://google-toolbox-for-mac.googlecode.com/svn/tags/google-toolbox-for-mac-1.5.1/XcodeConfig
externals/src/googletest/1.0 http://googletest.googlecode.com/svn/tags/release-1.0.1

In this case, I have put tagged versions of the external projects into externals since I want them to remain stable until I update them. Alternatively you could check out a particular version of the project's trunk by specifying the version in the property.

Adding links to external projects to your own project is a great alternative to copying the resources directly and trying to keep synchronized with the external project. Additionally, other developers can share the same properties without a need to configure their own machines. Subversion makes this easy with the svn:externals property.

09 July 2008

Frameworks Are Great (Part 3)

The Solution

It turns out that the problem I wrote about in part 1 and part 2 can be solved. In Mac OS 10.5 (Leopard), new features have been added to ld, the linker used at compile time, and dyld, the dynamic linker used at run time, to add arbitrary freedom in specifying the installed path of dynamically linked libraries.

@rpath

The solution is fairly straightforward. Instead of requiring a framework or dylib to have an install location relative to @loader_path, the path of the code requiring the framework to be loaded, or @executable_path, the path of the main executable, they can now have an install location relative to @rpath, the "run path" specified in the code requiring the framework to be loaded. This is explained pretty well in the dyld Release Notes.

The process of using @rpath requires two steps.

  1. The framework (or dylib) specifies its install directory to be relative to the "run path" (the "Dynamic Library Install Name" build setting in Xcode or the -install_name command line flag in ld). For example, the framework example below uses the name @rpath/LevelOne.framework/Versions/A/LevelOne as its "Dynamic Library Install Name." Xcode makes this a little easier by allowing you to specify the "Install Name" as "@rpath" and then it figures out the rest.
  2. The application (or plugin, or another framework) that loads the framework must specify its "run path." Specifying the "run path" is done in Xcode by specifying the "Runpath search paths" build setting or the -rpath option if using ld from the command line.

And that's it. It's pretty easy and almost limitless in flexibility.

Example

Everything is simpler with an example. Here's the scenario: I want to write a screen saver plugin called RpathSaver.saver that uses a framework called LevelTwo.framework. The plugin and the framework require loading a second framework called LevelOne.framework. I can't use an install path relative to the @executable_path, since that would require installing the screen saver plugin is in a known location (i.e. /System/Library/Screen Savers/. Bad form. I can't use an install path relative to the @loader_path since both the plugin and the framework are loaders of LevelOne.framework. As discussed in part 2 I have three bad options.

  1. Pull the executable code out of the two frameworks and put it in RpathSaver.plugin/Contents/MacOS with the rest of the executable code and specify their install path to be @loader_path.
  2. Put the frameworks in a global location like System/Library/Application Support.
  3. Statically link all of the source together.

But in Mac OS X 10.5, I can use the @rpath in the install path, and everything sorts itself out. Here's the solution:

  1. Set the runpath of the RpathSaver.saver plugin to be @loader_path/../Frameworks. That will let dyld search in the frameworks directory of the plugin for loadable code.
  2. Set the runpath of the LevelTwo.framework to be @loader_path/../../... That will let dyld search in the frameworks directory of the plugin for loadable code.
  3. Set the install directory of LevelTwo.framework to be @rpath/LevelTwo.framework/Versions/A/LevelTwo. At run time, dyld will replace the @rpath portion with the runpath specified in the loading code (in this case the plugin) and will find the framework in @loader_path/../Frameworks + LevelTwo.framework/Versions/A/LevelTwo.
  4. Set the install directory of LevelOne.framework to be @rpath/LevelOne.framework/Versions/A/LevelOne. At runtime, the @rpath will be replaced by the runpath of the loading code. In the case of the RpathSaver.saver plugin, it will load from this path: @loader_path/../Frameworks + LevelOne.framework/Versions/A/LevelOne. In the case of LevelTwo.framework, in will load from this path: @loader_path/../../../Frameworks + LevelOne.framework/Versions/A/LevelOne.

I'm enclosing the sample code here. It is the screen saver code that I discuss above. It simply displays some text from LevelOne.framework and LevelTwo.framework, which in turn uses LevelOne.framework to generate its own text. I hope you find it useful.

[Update 12 Aug 2008]

An alternative solution, for Tiger is explained by Jeff Johnson at Lap Cat Software. The post can be found here. The solution involves using install_name_tool.

26 June 2008

Frameworks Are Great (Part 2)

Advanced

Ok, the system of loading frameworks I talked about in my previous post doesn't always work. In fact, it can be kind of terrible. Here's and example that I came up against recently that proved very difficult to solve, at least in a clean hi-I'm-a-mac sort of way.

What I want to do was create a screen saver plug-in. So, what would the @executable_path be in this case? After a little poking around, it appears to be the application that runs and dynamically loads the plug-ins is the ScreenSaverEngine.app, located here: /System/Library/Frameworks/ScreenSaver.framework/Versions/Current/Resources/ScreenSaverEngine.app.

That's not very helpful for loading a plugin bundle's frameworks. Any sort of @executable_path relative directory would not be a good place to put any of my bundle's resources since they wouldn't be encapsulated and wouldn't allow a drag-installer. Well, then, let's turn our attention to the @loader_path. This is promising, the loader path would be the location that my plug-in executable is loaded from. Perfect.

Not so fast

But wait, I'm including a framework that itself requires a framework. In the stand-alone-application version of this (i.e. not a plugin) I can put both of these frameworks in the application's Contents/Frameworks/ directory and we'll be all set. Then the executable in the framework can load the second framework relative to the @executable_path/../Frameworks but this breaks down for a plug-in. In this case, the @loader_path of the second framework is the directory containing the main executable of the first framework, not the plugin. However, the plugin needs to link to this framework too (it is a utility framework). The @loader_framework now resolves to two separate places, the plug-in executable and the first framework executable. The @executable_path is equally useless, as described earlier. What to do...?

Three (bad) solutions

Well, I came up with three solutions, all with nasty side effects.

  • First, we could take the executable code out of the two framework bundles and then put it in the plug-in's Contents/MacOS directory. Then the @loader_path would resolve to the same location for all three pieces. This essentially throws away all of the benefits of frameworks. However, in our case, we don't ship headers or resources in these frameworks and we could live without the versioning capabilities. This might work.
  • Second, we could put the frameworks in a known, system-wide location. For example, /System/Library/Application Support/Sidelight/... or something. This would require shipping a package installer to install a screen saver plug-in. Bad. It would also prevent a drag-uninstaller from ever working. Also bad. This solution won't work.
  • Third, we could include the framework twice. Once in the plug-in's Frameworks directory, and another in the Framework's Frameworks directory. The framework we are using is about 10MB. Even with zippy broadband access this seems like a profligate use of disk space and RAM (since both frameworks would have to be loaded). This also might cause problems with conflicting symbol names. I didn't go down this path far enough to try it out.

There you have it. Three pretty terrible solutions. The first one seems the best, but not satisfying.

The real solution

So a not-so-bad solution to this problem would be to statically link the libraries we are using. There would be an initial hit at load time, but since this is a non-interactive application, it would be tolerable. Or, perhaps the real solution is to re-architect the code to remove the second framework. Most of its functionality is already included in the Cocoa frameworks. I just need a way to abstract the utility interface so I can maintain my cross-platform code base and then use Cocoa for the utilities rather than a cross-platform utility framework.

They're still sorta great

Frameworks are still pretty good, especially if you are Apple. When you are Apple you get your own reserved Frameworks directory, to which everyone can link. If you are not Apple, frameworks are great as long as you don't have two levels of dependent frameworks in a loadable bundle. Is this a fringe case? Yes, I think so. But is seems pretty important to me.

Frameworks Are Great (Part 1)

Frameworks

One the of reasons developing on Mac OS is so fun is the functionality and ubiquity of the system frameworks (Cocoa, Core Image, QuickTime, WebKit). Frameworks are bundles (i.e. directories) of related executable code, header files, and (possibly) localized resources. These frameworks encapsulate functionality and prevent the code-base from turning into a giant ball of spaghetti. It also improves dynamic load time by reducing the footprint of the loaded library to just the sort of code that the user needs (don't need QuickTime, it doens't get loaded).

To see which frameworks are being used by an application, we just need to poke around in the application's main executable file. Let's look at a simple application, Calculator.app, for an example (edited for brevity).

[mycomputer:~]$ otool -L /Applications/Calculator.app/Contents/MacOS/Calculator 
/Applications/Calculator.app/Contents/MacOS/Calculator:
    /System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa
    /System/Library/PrivateFrameworks/SpeechDictionary.framework/Versions/A/SpeechDictionar
    /System/Library/PrivateFrameworks/SpeechObjects.framework/Versions/A/SpeechObjects
    /System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration
     ...
    /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation

Ah, this is interesting. It looks like Calculator links in Cocoa (duh) but also SystemConfiguration.framework. Hmmm, I wonder why. Anyway, with otool -L we can see exactly which libraries the executable will link in at runtime. Very nice that the Calculator.app didn't have to include all of these libraries in its application bundle and even nicer that when Apple improvies their frameworks, Calculator.app can benefit from the upgrades (as long as no new bugs were introduced).

We can use them too!

Individual developers or companies can use this same strategy of encapsulation in their own applications. Many largish applications break their applications in to a main executable which links not only system frameworks, but frameworks that have been built in house. For example, a networking library could be in a Networking.framework and a library of geometric primitives could be in a Geometry.framework.

So, what do developers do to distribute their third party frameworks with their applications? They put them in the MyApplication.app/Contents/Frameworks directory and viola, the executable can find them. In the above example, all of the libraries have a constant installed location /System/Library/Frameworks. However for third party framework developers, the linker can find the framework relative to the executable. Here is a snippet from a great application, Colloquy.app:

[mycomputer:~]$ otool -L /Volumes/Work/Applications/Colloquy.app/Contents/MacOS/Colloquy 
/Volumes/Work/Applications/Colloquy.app/Contents/MacOS/Colloquy:
    /System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa
    /System/Library/Frameworks/Security.framework/Versions/A/Security
    /System/Library/Frameworks/WebKit.framework/Versions/A/WebKit
    /System/Library/Frameworks/AddressBook.framework/Versions/A/AddressBoo
    @executable_path/../Frameworks/AGRegex.framework/Versions/A/AGRegex
    @executable_path/../Frameworks/ChatCore.framework/Versions/A/ChatCore
     ...
    /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation

Notice the frameworks with the @executable_path at the beginning. These frameworks are loaded relative to the path of the current executable. When we start up Colloquy.app the loader don't have to know if the application bundle is located in /Applications or ~/Applications or even some other non-standard location like ~/Desktop. This makes things very nice for drag-installers. The application will find the framework no matter where the application bundle has landed.

Even application plugin bundles can locate this framework in the application Framework directory. For example, if I had an application MyApplication.app that loads a plugin bundle MyPlugin.plugin at runtime, and MyPlugin.plugin requires PluginFramework.framework to run, I would structure my application bundle like so:

and my plugin bundle like so:

The plugin executable would load the framework at the path @executable_path/../Frameworks/PluginFramework.framework/.... This framework could be shared by all of the application frameworks. This solution is good for sharing code among plugins.

Plugins are a bit harder

Now, things start to get a little tricky for plugin bundles loaded by system applications, such as the screen saver application. I couldn't really find any examples of plugin bundles that loaded frameworks in any of the system application plugins (didn't do an exhaustive search though). Don't fret! It is possible! In Mac OS 10.4 (Tiger) and later the loader can find frameworks relative to the @loader_path. This means you can have a plug-in bundle with its own Frameworks directory that includes the required frameworks. For example, if MyApplication.app were a system application that loads a plugin bundle MyPlugin.plugin at runtime, and MyPlugin.plugin requires PluginFramework.framework to run, I would structure my plugin bundle like so:

In this case, the plugin executable would load the framework at the path @loader_path/../Frameworks/PluginFramework.framework/Versions/A/PluginFramework. Very nice!

Conclusion

With the dynamic loading of system-level and thrid-party frameworks, the Mac OS goes a long way to making bundles self-contained. It makes applications much more encapsulated and drag-installable.

15 May 2008

NSColorWell and OpenGL

NSColor objects are specified in a color space. For example, a gray color can be fully specified by one float value in a NSCalibratedBlackColorSpace. However, the same gray color requires three float values in an NSCalibratedRGBColorSpace, three values to represent the red, green, and blue values. To convert between different color spaces, NSColor defines two instance methods which create a new NSColor object from the with a specified color space name. These methods are:

- (NSColor*)colorUsingColorSpaceName:(NSString*)colorSpace device:(NSDictionary*)deviceDescription
- (NSColor*)colorUsingColorSpaceName:(NSString*)colorSpace

OpenGL uses RGBA colors. If you use an NSColorWell to bind to a NSColor object, there is no guarantee that the setter method will be called with a color object specified in the RGB color space. To ensure the color is always in the RGB color space, it can be converted in the setter function, as in the following example for setting the OpenGL clear color.

- (void)setClearColor:(NSColor*)color {
  if (color != clearColor) {
    [clearColor release];
    
    // Convert the arbitrary NSColor from the color well to an RGB color.
    if ([color colorSpace] == [NSColorSpace genericRGBColorSpace]) {
      clearColor = color;
    } else {
      clearColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
    }
    [clearColor retain];
  
    // Notify the view that it needs a refresh
    [self setNeedsDisplay:YES];
  }
}

Then, whenever you need to access the RGBA elements of the color, you are assured that they exist and an exception will be raised. For example, in the example above, we could set the clear color with the following snippet.

  // Get the red, green, and blue components of the color
  float red, green, blue, alpha;
  [clearColor getRed:&red green:&green blue:&blue alpha:&alpha];

  // Set the OpenGL clear color
  glClearColor(red, green, blue, alpha);

OpenGL colors and NSColor colors don't exactly match up. Fortunately, the rich NSColor API easily converts from a variety of color spaces to the required RGBA colors useful with OpenGL.

05 May 2008

Sidelight Project Announced

I've started a new open-source project called Sidelight. It is a collection of graphics related tutorials and projects for the Mac. To begin with, I'll be posting tutorials that dive in to a single OpenGL feature. I'll be following the same structure as the OpenGL Programming Guide (the Red Book). Then, building on these simple tutorials, create more advanced examples.

Today I posted a new animation tutorial which uses a double-buffered OpenGL view and a timer. Documentation to follow.

Lorem Ipsum

Heading 1 - Looks like the Blog Title

This post is just to help with modifying the blogger template. It doesn't contain any useful information beyond the formatting.


Above was a horizontal rule. This is paragraph text. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Heading 2 - Looks like the date

Here is a block quote. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Heading 3 - Looks like the post title

Here is some strong text. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Heading 4 - Looks like regular text

Heading 5 - So does 5
Heading 6 - And six

Here is some emphasized text. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Heading 1Heading 2
Data 1Data 2
Data 3Data 4
  1. Ordered
  2. Lists
  3. Are
  4. Cool
  • Unordered
  • Lists
  • Are
  • Rad