First Cocoa application using smalltalk

Tagged:  •  

Recently I have done some work on the gst-objective-c-binding.
Now most of the basic feature is here. Of course a lot of polishing still need to be done.

The binding now support sending message from objective-c to smalltalk and from smalltalk to objective-c.
You can also sublcass some objective-c class like NSView.

In this post I will show you how to get the bridge working and create you first Cocoa app.

Getting gst-objc

(If you have a better name idea feel free to give me suggestions.)

You can fetch the source from github:

$ git clone git://github.com/mathk/gst-objc.git

Then you have to build it.

$ autoreconf -vi
$ ./configure
$ make
$ [sudo] make install

Depending on you platform you may need gnustep.
You can find all the instruction to build gnustep here.

Here you should be able to load Objc package:

GNU Smalltalk ready

st> PackageLoader fileInPackage: 'Objc'
Loading package Objc
2011-05-01 22:17:52.971 gst[91826:903] Load complete
PackageLoader
st> 

First window

Now that you can load Objc I will show you how to create a NSWindows.
For convenience I will drop every thing in a file myFirstWindow.st

"First load the package"
Eval [
    PackageLoader fileInPackage: 'Objc'.
]

After that we need to create a NSApplication that will handle the main event loop.

  nsApplication := Objc.ObjcRuntime at: 'NSApplication'.
  nsApp := nsApplication sharedApplication.

As you can see when sending #sharedApplication to the object your are in fact forwarding the message to objective-c.

We can then create our first NSWindows:

  nsRect :=  Objc.NSRect gcOriginX: 0.0
                    y: 0.0
                    width: 100.0
                    height: 100.0.

  nsWindow :=  Objc.ObjcRuntime at: 'NSWindow'.
  nsWindow := nsWindow alloc.
  nsWindow initWithContentRect: nsRect styleMask: 15 backing:Objc.ObjcAppKit nsBackingStoreBuffered defer: (Character value:1).
  nsWindow setTitle: 'Test windows' asNSString.

nsRect is just a CStruct that tell which size the window is going to have.
#initWithContentRect:styleMask:backing:defer: and #setTitle: are again forwarded to objective-c. You have to be careful to chose the right type when passing argument to objective-c. For instance the last parameter is of type 'c' in objective-c which is a Character.

#asNSString is a helper to convert from smalltalk string to objective-c string like @"Testing window".

After that we have to create a NSView.
The way NSWindow handle the drawing is by calling #drawRect: on a view.
So we have to subclass NSView and implement the #drawRect: method.

The way to do this is by using class and method pragma.

Objc.ObjcObject subclass: MyNSView [
    <objcSubclass: 'NSView'>

    drawRect: rect [
        "if you are running on gnustep you have to use:
         v@:{_NSRect={_NSPoint=dd}{_NSSize=dd}}
        and use f or d depending on you arch 32 or 64bit.
        This need to be improve.
        "
        <objcTypeStr:'v@:{CGRect={CGPoint=dd}{CGSize=dd}}'>
        | nsColor |
        nsColor := Objc.ObjcRuntime at: 'NSColor'.
        nsColor redColor set.
        Objc.ObjcAppKit nsRectFill: self bounds
    ]
]

The pragma objcSubclass: tells what is the super class. In oder word MyNSView is a subclass of NSView.

The pragma objcTypeStr: tells the type of the objective-c method.
You can found out more here.

The #drawRect: method is pretty simple. It just fill the window in red.

Now we can use this newly create class:

  "Create a new view but don't retain it. Instead autorelease it."
  view := MyNSView noRetainAlloc.
  view init.
  view autorelease.

  nsWindow setContentView: view.
  nsWindow center.
  nsWindow orderFront: 0.
  nsWindow contentView.
  nsApp run.

At this point you should see a small window fill of red.
That is it for this tutorial.

I hope you enjoy it.
Fell free to ask questions or comments.

Last I would like to thanks Paolo Bonzini and David Chisnall for theirs helps and advices.

Complete code:

Eval [
    PackageLoader fileInPackage: 'Objc'.
]

Objc.ObjcObject subclass: MyNSView [
    <objcSubclass: 'NSView'>

    drawRect: rect [
        <objcTypeStr:'v@:{CGRect={CGPoint=dd}{CGSize=dd}}'>
        | nsColor |
        nsColor := Objc.ObjcRuntime at: 'NSColor'.
        nsColor redColor set.
        Objc.ObjcAppKit nsRectFill: self bounds
         
    ]
]

Eval [
  | view nsInstance nsApplication nsApp nsWindow |

  nsApplication := Objc.ObjcRuntime at: 'NSApplication'.
  nsApp := nsApplication sharedApplication.
  nsRect :=  Objc.NSRect gcOriginX: 0.0
                    y: 0.0
                    width: 100.0
                    height: 100.0.

  nsWindow :=  Objc.ObjcRuntime at: 'NSWindow'.
  nsWindow := nsWindow alloc.
  nsWindow initWithContentRect: nsRect styleMask: 15 backing:Objc.ObjcAppKit nsBackingStoreBuffered defer: (Character value:1).

  nsWindow setTitle: 'Test windows' asNSString.

  view := MyNSView noRetainAlloc.
  view init.
  view autorelease.

  nsWindow setContentView: view.
  nsWindow center.
  nsWindow orderFrontRegardless.
  nsWindow contentView.
  nsApp run.

]

Looks nice! One question though: why not use Objc.NSWindow instead of "Objc.ObjcRuntime at: 'NSWindow'"? This would make the code look a lot nicer. If the wrapper objects are created dynamically, you could use GST's Autoload class which, at least in master, lets you hook in arbitrary autoloading behavior.

Hi,

When I run make I get the following error:

MacBook:gst-objc mymachine$ make
(echo 'Objc_FILES = \'; \

         /opt/local/bin/gst-package --srcdir=. --vpath --list-files Objc gst-objc/package.xml | \
           tr -d \\r | tr \\n " "; \
       echo; \
       echo '$(Objc_FILES):'; \
       echo '$(srcdir)/gst-objc/stamp-classes: $(Objc_FILES)'; \
       echo '        touch $(srcdir)/gst-objc/stamp-classes') > ./gst-objc/Makefile.frag

source='gst-objc.m' object='gst-objc.lo' libtool=yes \
       DEPDIR=.deps depmode=none /bin/sh ./depcomp \
       /bin/sh ./libtool   --mode=compile gcc -DPACKAGE_NAME=\"GNU\ Smalltalk\ package\ Objc\" -DPACKAGE_TARNAME=\"gst-objc\" -DPACKAGE_VERSION=\"0.0\" -DPACKAGE_STRING=\"GNU\ Smalltalk\ package\ Objc\ 0.0\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE_URL=\"http://www.gnu.org/software/gst-objc/\" -DPACKAGE=\"gst-objc\" -DVERSION=\"0.0\" -DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 -DHAVE_DLFCN_H=1 -DLT_OBJDIR=\".libs/\" -D__x86_64__=1 -I.  -I/opt/local/include -I/opt/local/lib/libffi-3.0.9/include     -g -O2 -c -o gst-objc.lo gst-objc.m

libtool: compile: gcc "-DPACKAGE_NAME=\"GNU Smalltalk package Objc\"" -DPACKAGE_TARNAME=\"gst-objc\" -DPACKAGE_VERSION=\"0.0\" "-DPACKAGE_STRING=\"GNU Smalltalk package Objc 0.0\"" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE_URL=\"http://www.gnu.org/software/gst-objc/\" -DPACKAGE=\"gst-objc\" -DVERSION=\"0.0\" -DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 -DHAVE_DLFCN_H=1 -DLT_OBJDIR=\".libs/\" -D__x86_64__=1 -I. -I/opt/local/include -I/opt/local/lib/libffi-3.0.9/include -g -O2 -c gst-objc.m -fno-common -DPIC -o .libs/gst-objc.o
gst-objc.m: In function 'gst_initModule':
gst-objc.m:13: error: 'VMProxy' has no member named 'dlOpen'
gst-objc.m:14: error: 'VMProxy' has no member named 'dlOpen'
make: *** [gst-objc.lo] Error 1

I'm using a MacPorts install of gst. Any ideas ?

Thanks
Colin

gst-objc require 3.2 at least.
But even so Paolo have push some patch in his master repository.
So you may need a fresh clone from github.
git://github.com/bonzini/smalltalk.git]

Thanks for the pointers.

Hi Math,

You've made a great work ;-)

Congrats,
Gwen

Thanks :)

User login