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.

Small Nerd Example Chapter 2

Aaron Hillegass’ book, “Cocoa Programmig for Mac OS X” is a great way to learn Cocoa, Objective-C and Interface Builder, and I highly recommend you buy it to understand OS X programming (of course, you’ll be buying the new, improved Second Edition, and I’m working off the first edition here, so things may not completely match up). In order to demonstrate how compact, yet readable, Renaissance + Python can be, I’m going to convert the example programs from this book. Aaron’s company is called Big Nerd Ranch, and I was trying to find something to show which postings were examples from the book, so readers can follow along, but without implying in any way that Aaron has approved of these conversions in any way, so I’m going to call them the Small Nerd Examples (The small nerd being me).

Note that while you are developing, it is inconvenient to have to build the project every time you touch a file, so you can use python setup.py py2app –alias to build it once, then you can edit your files normally and the changes will be reflected when you run your application. Just remember to re-run this when you add a new file, and to re-run it without the alias flag when you’re building any version to be distributed.

Like the previous example, Hello World, this one contains four files. The setup.py file will look radically similar to the Hello World version. The program itself is simple: A window with two buttons, one which seeds the random number generator with the current time, and another which generates and displays a random number between 1 and 100 in a text field.

MainMenu.gsmarkup

<?xml version="1.0"?>
<!DOCTYPE gsmarkup>
<gsmarkup>
    <objects>
        <menu type="main">
            <menu title="ch02" type="apple">
                <menuItem title="About ch02" action="orderFrontStandardAboutPanel:"/>
                <menuSeparator/>
                <menu title="Services" type="services"/>
                <menuSeparator/>
                <menuItem title="Hide ch02" action="hide:" key="h"/>
                <menuItem title="Hide Others" action="hideOtherApplications:"/>
                <menuItem title="Show All" action="unhideAllApplications:"/>
                <menuSeparator/>
                <menuItem title="Quit ch02" action="terminate:" key="q"/>
            </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>
                <menu title="Help" type="help">
                <menuItem title="ch02 Help" keys="?"/>
        </menu>
    </objects>
</gsmarkup>

MainWindow.gsmarkup

<?xml version="1.0"?>
<!DOCTYPE gsmarkup>
<gsmarkup>
    <objects>
        <window delegate="#NSOwner" resizable="no">
            <vbox>
                <button target="#NSOwner" action="seed"
                    title="Seed random number generator with time"/>
                <button target="#NSOwner" action="generate"
                    title="Generate random number"/>
                <textField editable="no" id="text" align="center"/>
            </vbox>
        </window>
    </objects>
    <connectors>
        <outlet source="#NSOwner" target="text" key="outputNum"/>
    </connectors>
</gsmarkup>

ch02.py

'''
Hillegass Example, Ch. 02
'''
from Foundation import *
from AppKit import *
from Renaissance import *
import random
class AppDelegate(NSObject):
    outputNum = None

    def windowWillClose_(self, notification):
        NSApp().terminate_(self)

    def quit_(self, notification):
        NSApp().terminate_(self)

    def close_(self, notification):
        NSApp().terminate_(self)

    def applicationDidFinishLaunching_(self, notification):
        NSBundle.loadGSMarkupNamed_owner_('MainWindow', self)

    def seed(self):
        random.seed()

    def generate(self):
        self.outputNum.setStringValue_(str(random.randint(1,100)))

def main():
    app = NSApplication.sharedApplication()
    delegate = AppDelegate.alloc().init()
    app.setDelegate_(delegate)
    NSBundle.loadGSMarkupNamed_owner_('MainMenu', delegate)
    NSApp().run()
if __name__ == '__main__': main()

setup.py

'''
Run with:
% python setup.py py2app
or
% python setup.py py2app --alias # while developing
'''
from distutils.core import setup
import py2app
setup(
    data_files = ['MainMenu.gsmarkup', 'MainWindow.gsmarkup'],
    app = ['ch02.py'],
)

OK, this time out we’ve got about 52 lines of XML markup and 44 lines of Python. Of course, the program doesn’t really do much more than Hello World, but we’re getting somewhere. If anyone has questions about the code, or want more explanatory text around the Renaissance markup, please let me know in the comments or by email.

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