Oblique Strategies

Today’s Example is a simple, but full application. Our setup file is getting more complicated as we give the app a custom icon and a name which isn’t taken from the main python file. We’re finally using the menus for more than just default behaviors. We’re loading in resources at runtime. We’re adding a custom About box. And we’re taking advantage of Python standard libraries from within a Cocoa program. One icon file + 224 lines of python, XML, and HTML.

Some years ago Brian Eno and Peter Schmidt created a deck of cards for brainstorming your way through artistic blocks. Each card had one idea, and they were called Oblique Strategies. The Whole Earth Review published a list of some of the strategies, the decks went through several editions, and there were quite a number of programs written to simulate drawing a card from the deck. The Oblique Strategies Web Site has more details. You can buy the deck from Brian Eno’s site. And a group called curvedspace created a nice OS X version which you can download for free from their site.

The curvedspace app is so nice, in fact, that we’re going to slavishly imitate it. There are only a couple of problems with it. First, you can’t add your own quotes and sayings to the mix. Second, it’s free, but not open source, so you can’t patch it to allow you to add your own quotes. Tonight’s example will build a version identical to the curvedspace tool, but which allows you to choose from among several quote files, not just the Oblique Strategies. A future exercise will be to allow the user to customize it with new quotes from within the running application.

Since there’s quite a bit more code, and by reader request, this example is contained on a downloadable .dmg file. The file contains both the finished, runnable application, and all the source code. I’ll just be describing highlights of what’s different from earlier examples. You can download it all here.

What’s new? First of all, the setup.py defines a plist resource inline to map some of the application features. This gives the application a name (”Oblique Strategies” rather than picking up “oblique” from the python file, and sets up some info for the About box. The setup call is a little more complex too, including English.lproj (which holds our icon) and Credits.html (which is the content of our About box), and passing in the plist we defined.

In the MainMenu.gsmarkup we have added a bunch of menu items to the File menu, to allow the user to pick a quotation file. What’s interesting is that we’ve implemented ‘About Oblique Strategies’ and Edit->Copy, but those menu items didn’t have to change.

In oblique.py, the main script, we implement a subclass of NSWindow called TexturedWindow. This is to work around a limitation of the current version of Renaissance, which doesn’t support so-called Metal windows (because GNUstep doesn’t have them). Nicola has fixed this in CVS, so it will be in the next release, but in the meantime it is a simple class (4 lines of code) and we use the instanceOf attribute of <window /> to call our subclass in the MainWindow.gsmarkup.

Our AppDelegate is similar to earlier examples, but has grown a couple of methods. The change() method is called by our one button to select another quotation at random from the selected file (or a random file if you like). The chooseFile_() method checks to see which menu item called it, and based on the menu item, selects the file for future calls to change(). There is one support function, getQuote(filename) which uses Python standard file manipulation and the random module to pick a quote (much less verbose than doing this from Objective-C).

All that’s left are the quote files. These are simple text files with short quotations, one to a line. If a quote requires newlines, they can be embedded with ‘\n‘. The included files have quotes from Martin Fowler’s book “Refactoring,” the book “The Pragmatic Programmer,” the Magic 8-Ball, Jenny Holzer’s Truisms, and more. Enjoy!

Pre-built examples

I’ve been requested to make binary packages of the applications for people who haven’t been following along at home. The first application I’ve made available is the GMarkup Browser, so you’ll need some GMarkup files to browse with it, either from the example here, the Renaissance site (the source package has lots of examples in the Examples folder) or by writing your own (the whole point is that that it isn’t hard to do). It’s available from the Living Code project on SourceForge.

I’ll make others available as I get the chance, now that I’ve figured out the SourceForge release system (sort of) and the steps for making a disk image for distribution. Soon I need to figure the Mac Package Manager and Installer (which py2app supports) so folks who want to install several apps don’t end up with multiple copies of Renaissance too. Baby steps for now, there’s a lot to learn.

Mac OS 10.3 only. Feedback appreciated. Coming soon: applications that I can post meaningful screenshots of.

Housekeeping

Various small improvements. Switched the template so code doesn’t run off the edge so easily. Fixed whitespace, which I forgot to do after switching the template (thanks, Xavier, for pointing that out!). All the code for the renaissance examples is available via cvs from the SourceForge Living Code project, in the somewhat predictable cvs module, renaissance_examples. As some of the examples grow, I may only publish the highlights in the blog, and put the remainder in CVS. We’ll se how it goes.

Coming attractions. I’m researching how to build the Renaissance projects so they can be distributed (I haven’t forgotten you, Jorjun, I’m just still figuring it out myself). I can do it now (thanks, Bob!), but I want something more straightforward to build. Hopefully later tonight.

Now that we’ve got a brower for Renaissance files (see previous post), I wanted to create a markup file to show off most of the widgets and options, but realized there is no markup for tabbed views, so I’m going to try creating new tags from Python, and show how to do that. When I’ve got the tags which represent Cocoa widgets that do not yet have Renaissance representations working, then I’ll put together the demo gsmarkup file.

Then back to the Small Nerd examples and a couple of other applications (ports of existing tools, nothing terribly original yet).

It’s been nice to hear from people who are enjoying this series. If there are specific things you’d like to see, let me know, either in the comments, or at dethe(at)livingcode.org

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'],
)

« Previous entries Next Page » Next Page »

google

google

asus