Tutorial - Adding Python code to an existing ObjC application

In this tutorial we are going to take an existing ObjC application and add Python and PyObjC to it. One of the reasons why you may want to do this is because some things are much simpler in Python than in ObjC, mainly due to the rich library Python has.

At the time of this writing this tutorial only works with a framework-based python 2.3 (also known as MacPython-2.3), it does not work with Apple's /usr/bin/python 2.2. The reason for this is that Apple forgot to ship the Python dynamic library, so you cannot embed this Python in your application.

You will also need the Apple Developer Kit, and it is expected you are familiar with using Project Builder, Interface Builder and Terminal. MacPython 2.3b2 or later is expected in the standard location (/Library/Frameworks, with auxiliary executables in /usr/local/bin and on your $PATH).

The application we are going to modify is Apple's SimpleComboBox example. This example shows you how to use combo boxes, but that is not what interests us right now: the application pretends to be a database application that allows you to keep notes (such as track list) for your CD collection. With such an application it feels silly that even though you want to type notes on the CD you are currently playing in iTunes you still have to retype album title, artist and genre. This is what we are going to fix: we are going to add a button "ask iTunes", which will use Python's AppleScript support to ask iTunes about the currently playing track and fill in the fields for you.

Follow these steps:

  1. Make a copy of /Developer/Examples/AppKit/SimpleComboBox to work on. Let's call this SimpleComboBoxPlus:
    % cp -R /Developer/Examples/AppKit/SimpleComboBox SimpleComboBoxPlus
    
  2. Open it in Project Builder, build it, and see what it does.
  3. Copy PythonGlue.h, PythonGlue.m and PythonGlue.py from src into the SimpleComboBoxPlus folder:
    % cp -R src/PythonGlue.* SimpleComboBoxPlus
    
  4. Add PythonGlue.h and PythonGlue.m to the "Classes" group.

    These files contain a class PythonGlue that does nothing visibly useful, but it has interesting side effects: when the first class instance is initialized it will initialize the Python interpreter, add the Resource folder of your application to the Python search path and import any modules from that Resources folder. The first bit of this is done by the ObjC code, the latter two by the Python code in PythonGlue.py.

  5. Add PythonGlue.py to the "Resources" group.
  6. Select the "Frameworks" group, "Add Framework...", select /Library/Frameworks/Python.framework.
  7. Now we need to arrange to have a PythonGlue object be instantiated being called early during startup. A good place to do this for the current application is when MainMenu.nib is loaded, as it does not contain any Python dependencies itself.

    Open the MainMenu.nib in Interface Builder and define a subclas of NSObject and call the new class PythonGlue. Instantiate it. Use this instance as the application delegate for the File Owner.

    Jack: I think this method will not work if you want to use Python-based classes in your main NIB file. Suggestions for how to make sure a PythonGlue object is instantiated before our main NIB file is read are hereby requested.

    Ronald: You might add [[PythonGlue alloc] init] to main.m, before the call to NSApplicationMain. You'd also have to create an autorelease pool before creating the PythonGlue instance.

  8. Now compile, build and run. You will get a message printed to standard output ("PythonGlue: Warning: no Python modules found") but all else should be well.
  9. Open CDInfoDocument.nib. Select the Class View, NSObject, subclass as ITunesCommunication. Give the class an askITunes: action. Instantiate the class as object ITunesCommunication.
  10. Go to the object view again, open the Window.
  11. Move the text box down a bit to make space, add a button "ask iTunes".
  12. Connect this button to the askITunes: action of the ITunesCommunication object.
  13. We now need to write the code implementing the ITunesCommunication class. Create a file ITunesCommunication.py in the Resources group. As this tutorial is about using PyObjC in existing ObjC programs and not about PyObjC itself we are going to skip the code itself and simply copy it from src/ITunesCommunication_1.py. Note that this is not the final code yet, it is a debug version that does not yet talk to iTunes.

    Double-clicking on ITunesCommunication.py in Project Builder may cause it to try and run the code in stead of editing it. In this case bring up the contextual menu and select "Open As..."->"Plain Text File".

  14. Build and run. When you press the "Ask iTunes" the "CD Title" and "Band Name" fields will be filled with one of the best albums of the last few years:-)
  15. Now we need to make the program talk to iTunes. The MacPython implementation to the Open Scripting Architecture requires an extra step when compared to AppleScript: you need to manually generate a Python package that wraps all the AppleScript terminology for an application. To make matters more complicated iTunes is one of those special cases where the standard way to generate this package (start the application, ask it for its terminology) does not work, so we have to actually look into the bowels of iTunes.app. This leads to the following hefty command line which you should run in the SimpleComboBoxPlus directory:
    % cd SimpleComboBoxPlus
    % setenv FWPYTHON /Library/Frameworks/Python.framework/Versions/Current
    % pythonw $FWPYTHON/lib/python2.3/plat-mac/gensuitemodule.py \
            --output iTunes --resource --creator hook \
            /Applications/iTunes.app/Contents/Resources/iTunes.rsrc
    

    This assumes MacPython is installed in the standard place and pythonw is on your $PATH.

  16. Add the generated iTunes package to your project: select the "Resources", and add iTunes. Add it as a folder reference, not as a recursive group.
  17. Finally, add the code to ITunesCommunication.py to actually communicate with iTunes. We cop out and copy it from src/ITunesCommunication_2.py.
  18. Build and run. If you press the button when iTunes is playing the Title and Band names will be filled, otherwise they will be cleared. In a real application you would probably put up a dialog in this case. Actually, in a real application you would disable the "Ask iTunes" button unless iTunes was active. All that is left as an exercise to the reader.