GNU Smalltalk で GTK

GNU Smalltalk 3.2系のウリは GTK なので、そこらへんで遊んでみました。まずは一番簡単なコードをGNU Smalltalk GTK 2.0 Tutorialを見ながら作成。

#!/usr/bin/env gst

PackageLoader fileInPackage: 'GTK'.

Object subclass: HelloWorld [
    | button window |
    hello [
        'hello world' printNl
    ]

    delate [
        'delete event occured' printNl.
        ^false
    ]

    destroy [
        GTK.Gtk mainQuit
    ]

    show [
        window := GTK.GtkWindow new: GTK.Gtk gtkWindowToplevel.
        window connectSignal: 'delete_event' 
            to: self selector: #delete userData: nil.
        window connectSignal: 'destroy'
            to: self selector: #destroy userData: nil.

        window setBorderWidth: 10.

        button := GTK.GtkButton newWithLabel: 'Hello World'.
        button connectSignal: 'clicked'
            to: self selector: #hello userData: nil.
        button connectSignal: 'clicked'
            to: self selector: #destroy userData: nil.

        window add: button.
        button show.
        window show.
    ]
].

Eval [
    hello := HelloWorld new.
    hello show.
    GTK.Gtk main
]


Tk とかに馴染んでいれば、特に解説は要らないごく普通な操作感。ただ、GTK パッケージの部品は委譲で使って、インターフェイスはダックタイピング任せ・・というのが標準的なスタイルの様子なのは、ちょっとだけ「あれ?」と思うポイントでしょうか。Window を継承しないのは新鮮。

あと、detele_event イベントは何かというと、ウィンドウのバッテンボタンを押したりすると出るイベントで、false で返すことで さらにdestroy イベントが発生するそうな。trueで返すと destroy は発生しないわけで、「本当に終了しますか Y/N」みたいなのを出すとかに使うみたいです。

パッキングボックス

ウィジェットの配置は、やっぱりお馴染みの パッキングボックスを使います。

#!/usr/bin/env gst

PackageLoader fileInPackage: 'GTK'.

Object subclass: HelloWorld [
    | box1 button1 button2  window |
    hello: aWidget string: aString [
        ('Hello agein - ', aString, ' was pressed') printNl
    ]

    delete: aWiget event: aGdkEvent [
        GTK.Gtk mainQuit.
        ^false
    ]

    show [
        window := GTK.GtkWindow new: GTK.Gtk gtkWindowToplevel.
        window setTitle: 'Hello Buttons!'.
        window connectSignal: 'delete_event'
            to: self selector: #delete:event: userData: nil.

        window setBorderWidth: 10.

        box1 := GTK.GtkHBox new: false spacing: 0.
        window add: box1.


        button1 := GTK.GtkButton newWithLabel: 'Hello World'.
        button1 connectSignal: 'clicked'
            to: self selector: #hello:string: userData: 'button 1'.
        box1 packStart: button1 expand: true fill: true padding: 0.
        button1 show.

        button2 := GTK.GtkButton newWithLabel: 'Button 2'.
        button2 connectSignal: 'clicked'
            to: self selector: #hello:string: userData: 'button 2'.
        box1 packStart: button2 expand: true fill: true padding: 0.
        button2 show.

        box1 show.
        window show.
    ]
].

Eval [
    hello := HelloWorld new.
    hello show.
    GTK.Gtk main
]

パッキングボックスには GTK.GtkHBox と GTK.GtkVBox があって、それぞれ水平詰めと垂直詰めに用います。#packStart:expand:fill:padding: メッセージと #packEnd:expand:fill:padding: メッセージでウィジェットを積めていきます(Start と End の違いは箱の頭から詰めるかお尻から詰めるかです)。

expand は ボックス内のスペースが余る時、スペースを残さないようにウィジェットの領域を広げるか否かを指定します。fill はその時にウィジェット自体でその領域を埋めるように膨らますか、ウィジェットのまわりにパディングするかの指定です。

GtkTextView / GtkTextBuffer

堪え性のないわたしは、テキストを編集するウィジェットに目移りしてしまって、いきなりチュートリアルを脱線してしまう...。

#!/usr/bin/env gst

PackageLoader fileInPackage: 'GTK'.

Object subclass: PettitWorkspace [
    | box1 sw textview  textbuf doitBtn window |

    doit [
        ^Behavior evalString: textbuf text to: self.
    ]

    delete: aWiget event: aGdkEvent [
        GTK.Gtk mainQuit.
        ^false
    ]

    show [
        window := GTK.GtkWindow new: GTK.Gtk gtkWindowToplevel.
        window setTitle: 'Pettit Workspace'.
        window connectSignal: 'delete_event' 
            to: self selector: #delete:event: userData: nil.

        window setBorderWidth: 10.

        box1 := GTK.GtkVBox new: false spacing: 0.
        window add: box1.

        textview := GTK.GtkTextView new.
        textbuf  := textview getBuffer.
        sw := GTK.GtkScrolledWindow withChild: textview.
        sw setPolicy: GTK.Gtk gtkPolicyAutomatic
           vscrollbarPolicy: GTK.Gtk gtkPolicyAutomatic.

        box1 packStart: sw expand: true fill: true padding: 0.
        sw show.
        textview show.

        doitBtn := GTK.GtkButton newWithLabel: 'Doit'.
        doitBtn connectSignal: 'clicked'
            to: self selector: #doit userData: nil.
        box1 packStart: doitBtn expand: true fill: true padding: 0.
        doitBtn show.


        box1 show.
        window show.
    ]
]

Eval [
    hello := HelloWorld new.
    hello show.
    GTK.Gtk main
]

せっかくなので Smalltalk の Workspace 風にしてみました。

GtkTextView は そのモデル GtkTextBuffer とセットで扱うようにできています。 テキストへの操作(選択されている文字列の取得や、タグ付けなど)は Buffer に対して行います。

一応 Workspace の端くれなので、Smalltalk コードを実行できます。

Behavior >> #evalString:to: で、self (PettitWorkspaceオブジェクト) を渡しているので、実行したまま GUI をコネコネできます。プロパティ変更だけじゃなくってウィジェットを増やしたりとかも。

Before


After


こうしておけば、クラスブラウザやリファレンスを眺めながら適当しながら 使い方を模索できるのでいい感じ。

こういうこと(動的プログラムこねこね)をやりたいと思ってしまうのは、ごく普通の要求のなのか、わたしがSmalltalk に馴れてしまった変態さんだからかは解らないのですが、GUI フレームワークの勉強の良い方法だとわたしは思います。

感想

  • Blox (TK バインディング)にくらべて、Smalltalkっぽくアレンジされてない
    • というか PyGTK そのまんまっぽい(上記チュートリアルも 途中から Smalltalkライズが間に合わずに Python コードになっちゃうし)
  • 薄皮ラッパーなのと、中身が c-call ばっかりなので、クラスブラウザで「読む」ようにコードができていないのは Smalltalk として非常に違和感。(同等コードもコメントもない)
  • 名前空間があるのに、クラス名がいちいち GtkHogeHoge なのはどうなんでしょう。

GNU Smalltalk 3.0 系は、GNU というOSの中のスクリプティング言語 という割り切りが凄くって、文法面よりもむしろ文化や哲学面の方が強く Smalltalk らしくないと感じさせられます。

でも、こういう処理系もあってもいいよね、というか、わたしはこれはこれで好きだったりします。