A Django site.
September 30, 2008

Scott Paul Robertson
spr
Spr: The Ramblings
» Building Cocoa GUIs in Python with PyObjC, Part Five

Adding Python Modules to the Bundle

If you try to use Python modules on the standard OS X Python path import statements will work fine. However, if you have non-standard modules that might be in a different location, or ones that you want to ship with, you will notice you can't just import them.

To bring them into the application bundle you'll have to go through a couple steps, but when it is all done the application will be able to use the modules, and you don't have to require the end user to install anything extra.

  1. Add the files to the Xcode project.

    • Select 'Project -> Add to Project' (option-command-a)
    • Select the Python module (directory) that you want to add.
    • On the next screen select "Copy items into destination group's folder (if needed) Add to Project dialog
    • Select the correct targets in 'Add to Targets'
    • Select "Create Folder References for any added folders"

And you're done. Nothing to difficult, but it does take some getting used to. Don't forget to import the module in the main.py file so you can use it.

<< Part4: Creating an Open Dialog

September 11, 2008

Scott Paul Robertson
spr
Spr: The Ramblings
» Key-Value Coding in PyObjC

So when doing GUIs in with PyObjC you'll realize that the IBOutlet for tying objects to variables have a few limitations. The one I hit was that a single outlet can only be connected to one object. So I read up on Key-value coding. So to do this you have to add two functions per variable:

def name(self):
     return self.var
def setName(self, x):
     self.var = x

You can imagine that this becomes tedious really fast. Luckily there is a solution. As an example:

from PyObjCTools.KeyValueCoding import kvc

class controller(NSWindowController, kvc):
    title = ""
    artist = ""
    album = ""

So this ties __getattr__ and __setattr__ to valueForKey: and setValue:forKey. Making every class variable available for Key-value coding. Very handy.

July 5, 2008

Scott Paul Robertson
spr
Spr: The Ramblings
» Building Cocoa GUIs in Python with PyObjC, Part Four

Creating an Open Dialog

Our controller doesn't really do anything at this point. We'll begin by adding an open dialog for the user. This will require us to really delve into the Objective C bridge provided by PyObjC. We'll start by reading some documentation that ships with Xcode.

Let's launch the local documentation browser in Xcode. Under "Help" click "Documentation". In the newly opened documentation browser select the "Mac OS X 10.5" documentation set and search for NSOpenPanel. This is the class we'll be working with to create our open dialog. Have a look through the documentation, then let's code.

First we do some basic stuff, creating the object and setting some permissions. A modified open method on our controller.py looks like this:

filetypes = ('mp3', 'ogg', 'mp4', 'flac', 'm4a', 'm4p')

@IBAction
def open_(self, sender):
    panel = NSOpenPanel.openPanel()
    panel.setCanChooseDirectories_(NO)
    panel.setAllowsMultipleSelection_(NO)

This gives us an NSOpenPanel object, turns off directory selection and multiple selections. The filetypes tuple contains the extensions that we allow the user to select, which will be used when we start the dialog.

The first open dialog we'll write will be modal. Modal dialogs block other actions in the application until the window finishes. It is the easiest way to do an open dialog. Have a look at the function runModalFortypes: in the documentation. Now to add to the open method:

    ret_value = panel.runModalForTypes_(self.filetypes)
    if ret_value:
        print "Open %s" % panel.filenames()
    else:
        print "Canceled"

Go ahead and run this code.

open window

A few things to note:

  • The only files that could be selected are contained in the filetypes tuple.
  • The return value is not the button clicked, rather it is 1 on "Open" and 0 on "Cancel".

Since our app really doesn't need to take up so much screen real estate we'll use a sheet instead. This is attached to the window that creates it, and looks a whole lot cooler. Unfortunately it is more difficult to use. Have a look at beginSheetForDirectory:file:types:modalForWindow:modalDelegate:didEndSelector:contextInfo: in the documentation. Now put in this code in place of what we just added:

    panel.beginSheetForDirectory_file_types_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
            os.getcwd(), None, self.filetypes, NSApp().mainWindow(),
            self, 'openPanelDidEnd:panel:returnCode:contextInfo:', 0)

We'll need a new method on the class controller:

@AppHelper.endSheetMethod
def openPanelDidEnd_panel_returnCode_contextInfo_(self, panel, returnCode,
        contextInfo):
    if returnCode:
        print "Open: %s" % panel.filenames()
    else:
        print "Cancel"

Run this and you'll see the lovely open sheet that behaves like our previous modal dialog.

open sheet

Items of note:

  • This really demonstrates "replace : with _" for method names. Method names get really long very quickly.

  • The @AppHelper.endSheetMethod decorator is a convience shortcut for the type signature decorator @signature('v@:@ii').

  • The SEL type converts to a correctly formatted string. The Objective C

    @selector(openPanelDidEnd:panel:returnCode:contextInfo:)
    

    becomes

    'openPanelDidEnd:panel:returnCode:contextInfo:'
    

By looking through the official documentation on NSOpenPanel and this you should be able to get a very good feeling for how to translate between Objective C and PyObjC. The code for this step is available in git as always.

In the next installment we'll get QuickTag reading and writing audio tags, finishing the controller.

References

<< Part3: Writing A Python Controller

June 24, 2008

Scott Paul Robertson
spr
Spr: The Ramblings
» Building Cocoa GUIs in Python with PyObjC, Part Three

Writing A Python Controller

First let's add your new controller into the GUI. Add an object from "Objects & Controllers" into your GUI. Now click on it and bring up the "Identity" tab in the inspector. The first drop-down lets you select a class, find "controller" and pick it.

Now Open up your newly created controller.py with your favorite text editor, and we'll begin.

#
#  controller.py
#  QuickTag
#
#  Created by Scott Paul Robertson on 6/11/08.
#  Copyright (c) 2008 __MyCompanyName__. All rights reserved.
#

from objc import YES, NO, IBAction, IBOutlet
from Foundation import *
from AppKit import *

class controller(NSWindowController):
    pass

There are two parts we will be adding, the Outlets (variables) and Actions (methods). first lets add a few outlets to the class controller.

    name = IBOutlet()
    artist = IBOutlet()
    albumArtist = IBOutlet()
    album = IBOutlet()
    ...

These class variables can now be connected to various fields in your GUI. In Interface Builder you simply control-click your controller object and drag to the text field that you want to attach that variable to. Wire up the fields as you would expect.

Next we start by adding some actions to the controller. First we add the method awakeFromNib which is called at window creation, allowing us to act at application start time.

    def awakeFromNib(self):
        print "awake"

Now let's write a method that we will wire the "Save & Close" button to.

    @IBAction
    def save_(self, sender):
        print "Save"

    @IBAction
    def saveClose_(self, sender):
        print "Saving and Closing"
        self.save_(sender)

Connect these to buttons or menu entries by control-clicking the button and dragging to the controller. Now if we run the application the console will print messages every time we hit a connected button.

We'll go ahead and write similar functions for our other actions and wire them up. controller.py is the final result of this process.

In the next part we'll look into doing something useful with our controller.

<< Part 2: Starting A Cocoa-Python Application | Part 4: Creating an Open Dialog >>

June 12, 2008

Scott Paul Robertson
spr
Spr: The Ramblings
» Building Cocoa GUIs in Python with PyObjC, Part Two

Starting A Cocoa-Python Application

Getting started is easy. First, install the Developer Tools if you haven't yet. Now launch Xcode and start a new project. Select "Cocoa-Python Application". You'll be presented with the following window.

xcode project window

Before we begin, go ahead and double-click on "MainMenu.xib (English)" and put together your interface. We're going to make a tag editor, so give yourself a window with:

  1. Text fields for things like: Name, Artist, Album, Track Number, Genre.
  2. Buttons for two actions: Revert and Save & Close.
  3. Menu items for: Open, Save, Revert to Saved, Close. Keep the edit menu, window menu, and help.

If you prefer, use mine. It already as all the needed connections made, the code is all that is missing.

Now we will start the controller. Create a new file for the project of the type "Python NSWindowController subclass". Name it something like "controller.py".

To tell the system to actually use our controller code we need to make a quick edit. Our first edit will be to main.py. Add the following where the other import statements are:

import controller

Some PyObjC Basics

We're ready to start building our controller. But first let's go over a few basics.

  1. Outlets. If we want to attach values to variables we need to have outlets. You add these as class variables to the controller and assign them the value returned from the function IBOutlet:

    form_field = IBOutlet()
    
  2. Actions. If we want to attach actions to functions we need to inform the system what methods are eligible. This is done by adding the IBAction decorator to a method:

    @IBAction
    def clickButton_(self, sender):
    
  3. Colons. Objective C loves colons. It uses them to separate return values, method names, and arguments. The method:

     - (IBAction)convert:(id)sender;
    

    becomes

    @IBAction
    def sender_(self, sender):
    

    So we simply change colons to underscores. Easy.

Part 3: Writing A Python Controller >>

» Building Cocoa GUIs in Python with PyObjC, Part One

Introduction

Building GUIs for Apple OS X traditionally meant you would code in Objective C. To overcome this issue people have made programming bridges to allow development in other languages. PyObjC is the project that enables Python programmers to take advantage of Cocoa, Apple's development environment. I recently began learning how to use PyObjC, and how to make (almost) pure Python GUI applications.

PyObjC isn't new, it has been around for a while, and there is actually a pretty good tutorial for wiring an interface up with Python. Leopard (10.5) ships with Python 2.5 and PyObjC 2.0, meaning there is nothing we need to install. Additionally Apple has also shipped support for Python in Xcode. This makes certain things much easier.

The current tutorial directs a user to build an interface, and then take the generated Nib file* and run a script that generates the appropriate controller. From here the user can create a fully working app bundle without needing Objective C. There are a few annoying things:

  • To get your various view to controller connections you have to create Objective C header files and wire with that. Those then are translated to the generated Python file where you implement them.
  • Every time you change the way the interface is wired to the controller, you need to re-generate the Python file, and merge in your changes.

Xcode 3.0, which ships with Leopard, provides support for Python, allowing the controller to be developed completely in the language. Interface Builder knows how to understand Python files, so you can attach actions and variables directly, with only a few necessary tricks.

In this series I will go through the steps to build a simple, but useful, application. There will be no Objective C written, only Python.

* Xcode 3.0 defaults to version 3 Nib files, which will not work with the PyObjC tutorial. You'll have to Save As a version 2 Nib file.

Part 2: Starting A Cocoa-Python Application >>

» Build Cocoa GUIs in Python with PyObjC, Part Two

Starting A Cocoa-Python Application, the basics of PyObjC

Getting started is pretty easy. First, install the Developer Tools if you haven't yet. Now launch Xcode and start a new project. Select a "Cocoa-Python Application". You'll be presented with the following window.

xcode project window

Before we begin, go ahead and double-click on "MainMenu.xib (English)" and put together your interface. If you prefer, use mine.

Now we will start the controller, create a new file for the project of the type "Python NSWindowController subclass". Name it something like "controller.py". Our first edit is to main.py, add the following where the other import statements are:

import controller

We're ready to start building our controller. But first let's go over a few basics.

  1. Outlets. If we want to attach values to variables we need to have outlets. You add these as class variables to the controller and assign them the value of IBOutlet:

    form_field = IBOutlet
    
  2. Actions. If we want to attach actions to functions we need to inform the system what methods are eligible. This is done by added the IBAction decorator to a method:

    @IBAction
    def clickButton_(self, sender):
    
  3. Colons. Objective C loves colons. It uses them to separate return values, method names, and arguments. The method:

     - (IBAction)convert:(id)sender;
    

    becomes

    @IBAction
    def sender_(self, sender):
    

    So we simply change colons to underscores. Easy.

» Build Cocoa GUIs in Python with PyObjC, Part One

Introduction

Building GUIs for Apple OS X traditionally means you will code in Objective C. To overcome this issue people have made programming bridges to allow development in other languages. PyObjC is the project that enables Python programmers to take advantage of Cocoa, Apple's development environment. I recently began learning how to use PyObjC, and how to make (almost) pure Python GUI applications.

PyObjC isn't news. It has been around for a while, and there is actually a pretty good tutorial for wiring an interface up with Python. Of course with OS X Leopard (10.5) Apple has shipped with Python 2.5, and the latest version of PyObjC. With that, they have also shipped with support for Python in Xcode. This allows the programmer to more easily develop in Python, skipping a few tedious steps.

The current tutorial directs a user to build an interface, and then take the generated Nib file (a version 2.0 save which is not the default in Leopard) and run a script that generates the appropriate controller. From here the user can create a fully working app bundle without needing Objective C. Except for building the interface. To attach methods to actions you have to make an Objective C header. That's not so bad, but it can be annoying. Additionally if you change things in the interface, or add actions you will have to regenerate the Python controller and merge it with your previous version.

Xcode 3.0, which ships with Leopard, provides support for Python, allowing the controller to be developed completely in the language. Interface Builder knows how to understand Python files, so you can attach actions and variables directly, with only a few necessary tricks.

In this series I will go through the steps to build a simple, but useful, application. There will be no Objective C written, only Python.

Part 2: Starting A Cocoa-Python Application, the basics of PyObjC