Creating your first PyObjC application.
In this tutorial you will learn how to create your first Python Cocoa application:
a simple dialog that allows you to convert amounts of money from one
currency to another. Definitely easier to do with a calculator, but in the
process of following the tutorial you will learn which bits of Apple's Cocoa
documentation apply to PyObjC and which bits are different, and how to adapt
the different bits to PyObjC from Objective-C.
To follow the tutorial you need PyObjC, which you apparently have, and either
Python 2.2 (pre-installed by Apple since Mac OS X 10.2) or MacPython 2.3,
which you probably also have. PyObjC works fine with any python more recent than
2.2, so if you installed python 2.2.2 through ``fink`` that should work, but
you will have to work out the details for step 1 yourself. PyObjC does not
work with MacPython-2.2 or MacPython-OS9 2.3.
In addition you need Interface Builder to
create your user interface. Interface Builder is not included in Mac OS X
by default, but it is part of Apple's free Developer Tools, which can
be gotten from http://developer.apple.com/tools as a hefty 300 MB download
after registering (for free).
1. Create a work directory ``src``. Check which Python you have installed PyObjC
for, by running ``python`` and checking that ``import Foundation`` works. If it
does not work it could be that you have installed PyObjC for ``/usr/local/python``
but Apple's ``/usr/bin/python`` comes first in your ``$PATH``. Make sure you
use the right python whereever it says ``python`` in this tutorial.
For convenience, set a shell variable PYLIB
to the Python Lib directory. For MacPython-2.3 this will be::
$ setenv PYLIB /Library/Frameworks/Python.framework/Versions/Current/lib/python2.3
or if you use bash as your shell::
$ export PYLIB=/Library/Frameworks/Python.framework/Versions/Current/lib/python2.3
For Apple's ``/usr/bin/python`` set the variable to ``/usr/lib/python2.2``,
if you are running on MacOS X set it to ``/System/Library/Frameworks/Python.framework/Versions/Current/lib/python2.3``.
2. Start Interface Builder, select *Cocoa Application*
in the new file dialog, save this file as ``src/MainMenu.nib``.
3. Proceed with the instructions as lined out in Apple's
`Developing Cocoa Objective-C Applications: a Tutorial`_, `chapter 3`_,
just after the section "*Creating the Currency Converter Interface*".
Work through "Defining the Classes of Currency Converter", "Connecting
ConverterController to the Interface", and stop at "*Implementing the Classes
of Currency Converter*", as we are going to do this in Python, not Objective-C.
Your nib file should now be the same as step3-MainMenu.nib_.
.. _`Developing Cocoa Objective-C Applications: a Tutorial`: http://developer.apple.com/techpubs/macosx/Cocoa/ObjCTutorial/index.html
.. _`chapter 3`: http://developer.apple.com/techpubs/macosx/Cocoa/ObjCTutorial/chapter03/index.html
.. _step3-MainMenu.nib: step3-MainMenu.nib
4. Create the skeleton Python script by running ``NibClassBuilder`` as a tool.
When invoked as a main program from the command line ``NibClassBuilder`` will
parse the NIB file and create a skeleton module for you. Invoke
it as follows (from the ``src`` directory)::
$ python $PYLIB/site-packages/PyObjC/PyObjCTools/NibClassBuilder.py \
MainMenu.nib > CurrencyConverter.py
The result of this can be seen in step4-CurrencyConverter.py_.
.. _step4-CurrencyConverter.py: step4-CurrencyConverter.py
5. There is no step 5.
Testing the user interface
6. Now we need to create an application framework around our program. Again,
in the future it could well be that this step is not needed during
development, or that it becomes simpler, but for now we do the following::
$ python $PYLIB/site-packages/PyObjC/bundlebuilder.py --link --nib=MainMenu \
--mainprogram=CurrencyConverter.py --resource=MainMenu.nib build
If you are using Python 2.3 the script is located in ``plat-mac`` instead
of ``site-packages`` and the command is::
$ python2.3 $PYLIB/plat-mac/PyObjC/bundlebuilder.py --link --nib=MainMenu \
--mainprogram=CurrencyConverter.py --resource=MainMenu.nib build
There are a few things to note:
- We use the ``--link`` argument. This creates a ``.app`` bundle which has symlinks
to our source files (``CurrencyConverter.py`` and ``MainMenu.nib``) in stead of copies.
This allows us to keep working on the sources without having to re-run bundlebuilder
after every edit.
- You have to specify MainMenu twice: once (with ``--resource``) to get it linked/copied
into the bundle and once (with ``--nib``) to get it listed in the ``.plist`` file.
7. Run the program. This can be done in three ways:
- double-click ``build/CurrencyConverter.app`` from the Finder (where you won't see the
- similarly, open it from the terminal with::
$ open build/CurrencyConverter.app
- run it directly from the Terminal, as::
The last method is actually the best to use: it leaves stdout and stderr connected
to your terminal session so you can see what is going on if there are errors. When
running with the other two methods stdout and stderr go to the console.
When you run your script as it is now it should behave identically as when you
tested your interface in Interface Builder in step 3, only now the skeleton is
in Python, not Objective-C.
Writing the code
8. Time to actually write some code. Edit CurrencyConverter.py again, and add
some. Follow Apple's documentation again, chapter 3, section "Implementing
Currency Converter's Classes". We need to do some name mangling on ObjC
names to get the corresponding Python names, see *An introduction to PyObjC*
for the details, but
in short if the ObjC name of a method is ``modifyArg:andAnother:``, in
other words, if an ObjC call would be::
[object modifyArg: arg1 andAnother: arg2]
the Python name will be ``modifyArg_andAnother_`` and you invoke it as::
Note that we don't do this mangling for ``Converter.convertAmount()``: this method is
only called by other Python code, so there is no need to go through the name mangling.
Also, if we would want to make this method callable from ObjC code we would have
to tell the PyObjC runtime system about the types of the arguments, so it could
do the conversion. This is beyond the scope of this first tutorial, *An introduction to PyObjC*
has a little more detail on this.
The application should now be fully functional, try it. The results of what we have
up to now can be seen in step8-CurrencyConverter.py_.
.. _step8-CurrencyConverter.py: step8-CurrencyConverter.py
Extending the functionality
9. We are going to add one more goodie, just to show how you edit an existing application.
The main problem, which may be obvious, is that we cannot run NibClassBuilder again
because we would destroy all the code we wrote in steps 5 and 8, so we do this by
What we are going to do is add an "invert rate" command, because I always get this
wrong: in stead of typing in the exchange rate from dollars to euros I type in the
rate to convert from euros to dollars.
Open ``MainMenu.nib`` in Interface Builder. Select the *Classes* view and there select the
``ConverterController`` class. In the info panel select the *Attributes* from the popup.
Select the *Actions* tab, and add an action ``invertRate:``. You have now told Interface Builder
that instances of the ``ConverterController`` class have grown a new method ``invertRate_()``.
In the ``MainMenu.nib main`` window open the *MainMenu* menubar. Select the ``Edit``
menu. Make sure the *Menus* palette is open and selected, drag a separator to the
``Edit`` menu and then drag an ``Item`` there. Double click the item and set the text to
``Invert Exchange Rate``.
Make the connection by control-dragging from the new ``Invert Exchange Rate`` menu item to
the ``ConverterController`` instance in the Instances tab in the ``MainMenu.nib`` main window.
*NOTE:* you drag to the *instance* of ``ConverterController``, not to the class. This is logical
if you think about it, but I keep forgetting it myself all the time too.
In the *Info* panel, *Connections* section, select ``invertRate:`` and press *Connect*.
*NOTE:* that is another thing I always forget: pressing *Connect* after selecting the action:-)
10. We know our program can't invert rates yet, because we haven't actually written the code
to do it, but we are going to try it anyway, just to see what sort of spectacular
crash we get. Alas, nothing spectacular about it: when the NIB is loaded the Cocoa runtime
system tries to make the connection, notices that we have no ``invertRate_()`` method in
our ``ConverterController`` class and it gives an error message::
2003-03-24 16:22:43.037 CurrencyConverter Could not connect the action
invertRate: to target of class ConverterController
Moreover, it has disabled the ``Invert Exchange Rate`` menu command and continues, so the
program really works as it did before, only with one more (disabled) menu item.
11. Writing the code is easy: add a method ``invertRate_(self, sender)`` that gets the float
value of ``rateField``, inverts it and puts it back. We deliberately forget to test for
divide by zero. We run the program again, and now the menu entry is enabled. After
trying it with a couple of non-zero exchange rates we try it with an exchange rate of
zero (or empty, which is the same). We get a dialog box giving the Python exception, and
offering the choice of continuing or quitting.
*XXXX Implementation Note:* what is described in the next paragraph does not
seem to work in the current distribution.
If we select *Quit* then we get a normal
Python exception traceback in the Terminal window. The exception is actually re-raised,
so we can use the standard Python trick to debug this: set shell variable
``PYTHONINSPECT`` to ``1``, run our program, try to invert an exchange rate of ``0``, press quit.
At the ``>>>`` prompt, type ``import pdb ; pdb.pm()`` and we can inspect all local variables,
12. Fix the final bug by testing for ``rate==0`` in ``invertRate_()``. The result is in the
.. _step12-src: step12-src
Creating an applet for local use
Your application is finished, and you want to move it to the ``Applications`` folder
(or anywhere else) and insulate it from the original source code.
This can be done by re-running the ``bundlebuilder.py`` invocation from step
6 without using the '--link' in the invocation. Move ``build/CurrencyConverter.app``
anywhere you want and double-click it from the Finder to run it.
For programs with more Python sourcefiles you include all additional sources as resources.
It is even possible to include all of Python (or, if you are using Apple's Python 2.2,
all the bits of Python that are non-standard), this gives you an application that
is distributable to anyone in the world (as long as they have Mac OS X 10.2)! Unfortunately,
the exact details of this procedure are not streamlined enough for inclusion in this
tutorial at this point in time.