Packaging Frameworks

Packaging Renaissance applications (or any other frameworks) takes a bit more care than just wrapping your Python scripts. Today’s exercise helps us get our apps out into the world.

Now that we can build cool OS X applications with Python and Renaissance, it would be cool if we could share them with others, wouldn’t it. And that stumped me for a bit. We’re using py2app to package our scripts as applications, and it knows how to include a framework, but it takes a bit more than that. Specifically, the wrapper which imports the framework into Python has to be a tiny bit smarter. Let’s take a look at the wrapper I provided earlier in this series, to go in Python’s site-packages/Renaissance/ folder.

Old __init__.py

import objc, AppKit, Foundation
objc.loadBundle('Renaissance', globals(),
    bundle_path='/Library/Frameworks/Renaissance.framework')
del objc, AppKit, Foundation

And the new, smarter version, which handles bundling.

__init__.py

import objc, AppKit, Foundation, os
if 'site-packages.zip' in __file__:
    base_path = os.path.join(os.path.dirname(os.getcwd()), 'Frameworks')
else:
    base_path = '/Library/Frameworks'
bundle_path = os.path.abspath(os.path.join(base_path, 'Renaissance.framework'))
objc.loadBundle('Renaissance', globals(), bundle_path=bundle_path)
del objc, AppKit, Foundation, os, base_path, bundle_path

That takes care of importing the Renaissance framework, now we just need to make sure it gets included in our application bundle. You can do this by passing it on the command line

python setup.py py2app –framework=Renaissance.framework

But I’d prefer to put it into the setup.py so we don’t have to remember command-line flags. Py2app is new enough that the documentation is a little rough, but Bob Ippolito (author of py2app) is very responsive on the pythonmac mailing list and he gave the following advice.

If you use a python module that links to Renaissance, it will automatically get included. Otherwise, you have to specify it as a framework.

% python setup.py py2app -f Renaissance.framework
(you can specify a full path to the framework or the dylib inside the framework if you want)

or from setup.py it would look like:
setup(
app = [...],
options = dict(py2app=dict(
frameworks=['Renaissance.framework'],
)),
)
This command is your friend:
% python setup.py –help py2app

Every “long name” in the list corresponds to an option you can pass via the options dict. Hypens are converted to underscores. This same dance works for any distutils command, btw.

That’s good to know about distutils, I’ve had trouble figuring out the mapping between command-line parameters and setup.py configuration. So here’s the new version of setup.py for the browser app:

setup.py

'''
Smarter setup.py example, run with:
% python setup.py py2app
'''
from distutils.core import setup
import py2app
setup(
    data_files = ['MainMenu.gsmarkup'],
    app = ['browser.py',],
    options=dict(py2app=dict(frameworks=['Renaissance.framework'],)),
)

There’s still a lot more we can do with py2app, like adding custom icons, giving the application a better name, a version, the ability to advertise its ability to open certain types of files, etc. We’re just getting to the good stuff.

GMarkup Browser

Today’s exercise was to port the GMarkup Browser, an Objective-C program which comes with Renaissance, to Python. Using this allows you to see what various gmarkup files will look like when they are loaded, which is very handy as you’re developing a program’s UI, without having to build a program around them. The source code of Renaissance (available separately from the binary we installed a few entries ago) contains an Examples folder showing all of the tags in use, both as small toy examples and tests, and as full-fledged applications. It’s well worth downloading this and poking around in it with the Browser.

There are three files today, the setup.py, a simple MainMenu.gsmarkup, and the browser.py application. Note that loading resources, especially menu resources, into a running program can cause bad side effects. So far I’ve been able to exit by ctrl-clicking the application icon and selecting quit, even when the menus disappeared, but use caution. Also, buttons wired up to the “terminate:” selector will exit your program if you click on them. With great power comes great responsibility, or something like that. Caveats aside, you can open multiple gsmarkup files at a time, just use the File->Open menu or Cmd-O.

MainMenu.gsmarkup

<?xml version="1.0"?>
<!DOCTYPE gsmarkup>
<gsmarkup>
    <objects>
        <menu type="main">
            <menu title="GSMarkup Browser" type="apple">
                <menuItem title="About GSMarkup Browser"
                    action="orderFrontStandardAboutPanel:"/>
                <menuSeparator/>
                <menu title="Services" type="services"/>
                <menuSeparator/>
                <menuItem title="Hide GSMarkup Browser" action="hide:" key="h"/>
                <menuItem title="Hide Others" action="hideOtherApplications:"/>
                <menuItem title="Show All" action="unhideAllApplications:"/>
                <menuSeparator/>
                <menuItem title="Quit GSMarkup Browser" action="terminate:" key="q"/>
            </menu>
            <menu title="File">
                <menuItem title="Open" action="open:" key="o"/>
            </menu>
            <menu title="Window" type="windows">
                <menuItem title="Minimize Window" action="performMiniaturize:" key="m"/>
                <menuSeparator/>
                <menuItem title="Bring All to Front" action="arrangeInFront:"/>
            </menu>
        </menu>
    </objects>
</gsmarkup>

browser.py

'''
Port of GSMarkupBrowser from Objective-C to Python
'''
from Foundation import *
from AppKit import *
from Renaissance import *
class Owner(NSObject):
def takeValue_forKey_(self, obj, key):
    #print 'Set value %s for key %s of NSOwner' % (obj, key)
    pass
def bundleDidLoadGSMarkup_(self, notification):
    if NSUserDefaults.standardUserDefaults().boolForKey_('DisplayAutoLayout'):
        topLevelObjects = notification.userInfo().objectForKey_('NSTopLevelObjects')
        for obj in topLevelObjects:
            if obj.isKindOfClass_(NSWindow) or obj.isKindOfClass_(NSView):
                obj.setDisplayAutoLayoutContainers_(True)
def applicationDidFinishLaunching_(self, notification):
    self.open_(self)
def open_(self, notification):
    filetypes = ['gsmarkup']
    panel = NSOpenPanel.openPanel()
    result = panel.runModalForDirectory_file_types_(None, None, filetypes)
    if result == NSOKButton:
        self.pathname = panel.filenames()[0]
        #print 'Loading', self.pathname
        didLoad = NSBundle.loadGSMarkupFile_externalNameTable_withZone_localizableStringsTable_inBundle_(
            self.pathname,
            {'NSOwner': self}, None, None, None)
        if didLoad:
            print self.pathname, 'loaded!'
        else:
            #print 'Could not load', self.pathname
            NSBeep()
def main():
    defaults = NSUserDefaults.standardUserDefaults()
    defaults.registerDefaults_({'DisplayAutoLayout': 'NO'})
    app = NSApplication.sharedApplication()
    owner = Owner.alloc().init()
    app.setDelegate_(owner)
    NSBundle.loadGSMarkupNamed_owner_('MainMenu', owner)
    NSApp().run()
if __name__ == '__main__': main()

setup.py

'''
Minimal setup.py example, run with:
% python setup.py py2app
'''
from distutils.core import setup
import py2app
setup(
    data_files = ['MainMenu.gsmarkup'],
    app = ['browser.py'],
)

Vancouver XML Users Group

I’ll be presenting on Renaissance, OS X, and Python at the VanX meeting at 6:30 p.m. on Thursday, December 16th. By then I should have a lot more programs and utilities to show.

Renaissance Tags

Nicola Pera has done a great job building Renaissance, and has worked hard to document it well. The manual is clear and easy to follow, but is not complete yet. He invited me to help with the documentation, and I’ve found the source code easy enough to read that I might do that (Objective-C is actually quite Pythonic), but it’s been years since I’ve hacked LaTeX and I’d rather spend my time posting examples of working code. So for now I’ll just stick my notes here about the Renaissance elements which have not yet been documented in the manual.

<browser />
    titled="yes|no" (default: no)
    allowsBranchSelection="yes|no" (default: yes)
    allowsEmptySelection="yes|no" (default: no)
    allowsMultipleSelection="yes|no" (default: no)
    takesTitleFromPreviousColumn="yes|no" (default: yes)
    separatesColumns="yes|no" (default: yes)
    acceptsArrowKeys="yes|no" (default: yes)
    hasHorizontalScrollbars="yes|no" (default: no)
    doubleAction="[selector]"
    minColumnWidth="[number]"
    maxVisibleColumns="[number]"
    matrixClass="[subclass of NSMatrix]"
    cellClass="[subclass of NSCell]"
<colorwell/>
    color="[Color]" (See section 2.3.3.6 of the manual for info on Color attributes)
<form/>
    titleFont="[Font]" (See section 2.3.3.7 of the manual for info on Font attributes)
    titleAlignment="left|right|center"
<label/>
    color="[Color]"
    backgroundColor="[Color]"
    font="[Font]"
    align="left|right|center"
<matrix/> (contains matrixRow elements)
    doubleAction="[Selector]"
<matrixRow/> (contains matrixCell elements)
<matrixCell/>
    title="[String]"
    action="[Selector]"
    editable="yes|no"
    selectable="yes|no"
    tag="(???)" (I'm not sure how tags are used yet)
<outlineView/>
    outlineColumn="[Number]"
<popupButton/> (contains popupButtonItems)
    title="[String]"
    pullsDown="yes|no"
    autoenabledItems="yes|no" (default: yes)
<popupButtonItems/>
    tag="(???)"
    action="[Selector]"
    key="(???)"
    title="[String]"
<scrollView/>
    hasHorizontalScroller="yes|no"
    hasVerticalScroller="yes|no"
    borderType="none|line|bezel|groove"
<secureTextField/>
<splitView/>
    vertical="yes|no"
<tableColumn/>
    identifier="(???)"
    editable="yes|no"
    title="[String]"
    minWidth="[Number]"
    maxWidth="[Number]"
    width="[Number]"
    resizable="yes|no"
<tableView/> (contains tableColumns)
    dataSource="[Outlet]"
    delegate="[Outlet]"
    doubleAction="[Selector]"
    allowsColumnReordering="yes|no"
    allowsColumnResizing="yes|no"
    allowsMultipleSelection="yes|no"
    allowsEmptySelection="yes|no"
    backgroundColor="[Color]"
    drawsGrid="yes|no"
    gridColor="[Color]"
    autosaveName="[String]"
<textField/>
    editable="yes|no"
    selectable="yes|no"
    align="left|right|center"
<textView/> **(Note: Always put a textView inside a scrollView!)**
    editable="yes|no"
    selectable="yes|no"
    richText="yes|no"
    usesFontPanel="yes|no"
    allowsUndo="yes|no"
    usesRuler="yes|no"
    importGraphic="yes|no"

Well, that’s what I have so far. I will try to keep this updated as I learn more, or simply point to the manual as it becomes more complete.

A New Renaissance

Yesterday I laid out the issues I have with using Interface Builder to create Cocoa applications (whether in Objective-C or Python), and my requirements for a replacement. To sum up, here are the requirements again:

  • Declarative
  • Simple and powerful
  • Able to replace NIB files and Interface Builder
  • Text-based, not binary
  • Agile for rapid development, adapts to rapidly changing code
  • Able to specify simple apps completely, in code, without resorting to pictures or talking the user through mouse gestures

As I hinted at previously, I think I’ve found the tool I was looking for in GNUstep Renaissance, and as an added bonus, it can be used to create applications for Linux, unix, and Windows using the GNUstep framework. So although I’m interested mainly in building applications for OS X, there is still a chance for cross-platform compatibility.

So what does Renaissance look like? It’s and XML format, similar to HTML and Mozilla XUL (but simpler than XUL). Today I will cover how to install Renaissance and set it up to use from PyObjC.

Prerequisites (this is my setup, others may work, but I haven’t tested them).

  1. A Mac
  2. OS X (10.3)
  3. PyObjC (1.1), available from http://pyobjc.sourceforge.net/
  4. py2app (0.1.4), available from http://pythonmac.org/wiki/py2app (we’ll use this to build our double-clickable applications)
  5. Renaissance framework (0.8), available from http://www.gnustep.it/Renaissance/Download.html (this is the secret sauce)

Once you have the prerequisites installed, you need to make Renaissance available from Python. In your site-packages directory (on my machine this is /System/Library/Frameworks/Python.framework/Versions/2.3/lib/python2.3/site-packages) add a Renaissance directory containing the following file:

__init__.py

import objc, AppKit, Foundation
objc.loadBundle('Renaissance', globals(),
    bundle_path='/Library/Frameworks/Renaissance.framework')
del objc, AppKit, Foundation

Well, that was easy enough. Next up, a Hello World application.

« Previous Page« Previous entries « Previous Page · Next Page » Next entries »Next Page »

google

google

asus