GNU Smalltak で cairo のお勉強 その2

前回はとりあえず線を引っ張ってみたのだけれど、cairo のおいしいところの基本は、それがベクターグラフィックだということで、拡大したり縮小したりしてもスムースで美しい画像がレンダリングされるのをみて感動するのが、お勉強の王道です。

・・が、いまいちよくわかんなかったのですよね。前回はそのままの流れ

expose: aWidget event: aGdkEvent [
             ・
             ・
             ・
   width  := aWidget getAllocation width.
   height := aWidget getAllocation height.
   cairoContext scaleBy: (width / 200.0) @ (height / 200.0).
             ・
             ・
]

でサクっと Window にあわせて絵も拡大/縮小する!・・・で〆メたかったのですが、getAllocation で 取れるハズの スクリーンのサイズが、いろいろやっても 0 しか入ってきませぬ。うぬぬ。GTK+ よくわかってないからなー、あたし..orz


* * *


というわけで、一晩おいて、うまくいかなかった

w := aWidget getAllocation width.
h := aWidget getAllocation height.

"→ w も h もいつもゼロ なので 以下は無効なコードになる"
cairoCtx scaleBy: (w / 200) @ (h / 200).

なコード、作戦をちょびっと変えて、void gdk_drawable_get_size(GdkDrawable *drawable, gint *width, gint *height); の方でやってみると、スルっといけちゃいました。

gdkDrawable := aWidget getWindow.
w := CInt value:0.
h := CInt value:0.
gdkDrawable getSize: w height: h.

"これはいける"
cairoCtx scaleBy: (w value / 200) @ (h value / 200).

ウインドウをググッと引っ張れば

のびたり

ちじんだり

ソレに合わせて中身も変わる。

ちょっと余談 : GNU Smalltalk の cCall

二つ以上の返値がほしいとき、スタック大好きな C の常套手段な 引数にアドレスを設定するやつ、GNU Smalltalk の cCall ではどうやるのかというと、

GTK.GdkDrawable >> getSize: width height: height
  <cCall: 'gdk_drawable_get_size' returning #void
    args:#(self #cObject #cObject )>

やっぱり 適切な CObject をつくって、それを渡すようなコードを書きます。

プチ落とし穴なのが、CObject new は NULL への参照となることかな。これはObject だけならそんなに「えー!?」とは思わないのだけれども、CObject の派生型 ――たとえば CInt や CDouble なんかもそうなっちゃっていて、単に返値を受け取るだけの空箱を作ろうとして new すると、アボーンして「えー!?」な気分になる((なる、、というか、「なった」です。つまんないところで時間をつかってしまったですよ(T△T) しくしく))。いつでも #value: クラスメソッドを使って

x := CInt value: 0.
x := CInt value: 0.
window getPointer: x y: y.

するのが鉄板(コードを読むと、value: の中で 領域確保をして、その上で初期値をそこにコピー みたいなことをやってくれてる)。

あと このケースだと、CInt を渡さないとだめで、CDouble とか渡しちゃうと 不正な cast がされて変な値が得られてしまったりとかとかにも若干注意かしらん。

でも、Smalltalk でこれって気持ち悪くないですか?

というわけで、目的は達成できたのだけれども、GTK+ な C なコードをそのままラッピングしただけのメソッドなので、Smalltalk 的にはかなり気持ち悪い。Smalltalk なのに こういうブツブツ文で切れちゃう手続きすぎるコードを書かされるのも、個人的にはイヤンな感じです。

んー、ここは

GTK.GdkDrawable extend [
    getSize [
      | h w |
      h := CInt value: 0.
      w := CInt value: 0.
      self getSize: w height: h.
      ^ w value @ h value
    ]
]

みたいに GdkDrawable を拡張して、気持ちよく使いたいです。

あと、cairo context の取得部分もチマチマっているのですが、これは GTK.GdkDrawable >> withContextDo: をつかうと気持ちよく書けそう。というわけで、最後にちょっとだけコードのお掃除です。

#!/usr/bin/env gst

PackageLoader fileInPackage: 'GTK'.
PackageLoader fileInPackage: 'Cairo'.

GTK.GdkDrawable extend [
    getSize [
        | h w |
        h := CInt value: 0.
        w := CInt value: 0.
        self getSize: w height: h.
        ^ w value @ h value
    ]
]

Object subclass: MainWindow [
    | window  |

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

    expose: aWidget event: aGdkEvent [
        | gdkDrawable |
        gdkDrawable := aWidget getWindow.
        gdkDrawable withContextDo: [:cairoCtx |
            cairoCtx scaleBy: gdkDrawable getSize / (200@200).

            cairoCtx lineCap: #round.
            cairoCtx lineWidth: 30.0.

            cairoCtx sourceRed: 0 green: 0 blue: 200.
            cairoCtx moveTo: 50@50.
            cairoCtx lineTo: 50@150.
            cairoCtx stroke.

            cairoCtx sourceRed: 0 green: 200 blue: 0.
            cairoCtx moveTo: 50@150.
            cairoCtx lineTo: 150@100.
            cairoCtx stroke.

            cairoCtx sourceRed: 200 green: 0 blue: 0.
            cairoCtx moveTo: 150@100.
            cairoCtx lineTo: 50@50.
            cairoCtx stroke.
        ].
        ^false
    ]

    show [
        | canvas |
        window := GTK.GtkWindow new: GTK.Gtk gtkWindowToplevel.
        window setTitle: 'Cario Lean'.
        window connectSignal: 'delete_event' 
            to: self selector: #delete:event: userData: nil.
        window setDefaultSize: 200 height: 200.

        canvas := GTK.GtkDrawingArea new.
        window add: canvas.

        canvas connectSignal: 'expose-event'
            to: self selector: #expose:event: userData: nil.

        canvas setEvents: 
            (GTK.Gdk gdkButtonPressMask bitOr:
                 GTK.Gdk gdkButtonReleaseMask).
        canvas connectSignal: 'button_press_event'
            to: self selector: #penDown:event: userData: nil.
        canvas connectSignal: 'button_release_event'
            to: self selector: #penUp:event: userData: nil.

        window showAll.
    ]   
]

Eval [
    window := MainWindow new.
    window show.
    GTK.Gtk main
]