2011年12月18日日曜日

gtk2hs と Glade の相性

gtk2hs を Glade と組み合わせて使うときのメモ。

Haskell の GUI プログラミングを試したくて、テキストボックスに文字列を入れて Enter を押したらラベルに表示されるような簡単なプログラム echo を、gtk2hs を使って書く実験をしてみた。

で、まず Glade というツールで GUI を定義する XMLを生成して、これを適当に書いた Haskell プログラムに読ませたら、こんなエラーが出た

$ ./echo 

(echo:4887): libglade-WARNING **: Expected <glade-interface>.  Got <interface>.

(echo:4887): libglade-WARNING **: did not finish in PARSER_FINISH state
echo: user error (Pattern match failure in do expression at echo.hs:8:5-12)

なんか Glade が生成した GUI定義XML の形式に問題があるっぽい。

調べてみると、Glade の出力形式には libglade と GtkBuilder の二通りの方法があり、gtk2hs が対応しているのは前者という事らしいのだが、ファイルを保存するときに libglade を選んでも、事態は全然変わらない。

さらに調べてみると Glade の3.8系 と 3.10系 で大きな違いがあって、出力XML に関してだと 3.10系では libglade 形式が無くなってるらしい。自分は、特に意識しないまま 3.10 を使っていた模様。

この2つのバージョン間で、保存時のダイアログに以下のような違いがある。

3.10系
3.8系
XML出力の違いは以下のような感じ
3.10系
<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <requires lib="gtk+" version="2.24"/>
  <object class="GtkWindow" id="window1">
    <property name="can_focus">False</property>
    <child>
    …
3.8系
<?xml version="1.0" encoding="UTF-8"?>
<glade-interface>
  <widget class="GtkWindow" id="window1">
    <property name="can_focus">False</property>
    <child>
    …

というわけで、Glade の 3.8 系を使えば、gtk2hs がちゃんと処理できる形式の XML になる。

ただし、うちの環境で使ってる Fedora16 なんかでは、「ソフトウェアの追加/削除」から普通に Glade をインストールすると 3.10系が入って来てしまう。従って、この場合 3.8系を別途インストールする必要がある。

これは、ここから glade3-3.8.1.tar.xz を落としてきて、tar Jxf glade3-3.8.1.tar.xz -> ./configure -> make -> make install のようにすれば、/usr/local/bin/glade-3 が使えるようになる。

ちなみに、以下が実験で使った Haskellコード

module Main where

import Graphics.UI.Gtk
import Graphics.UI.Gtk.Glade
import Graphics.UI.Gtk.Gdk.Events as Evt

main = do
    initGUI
    Just xml    <- xmlNew "echo.glade"
    window      <- xmlGetWidget xml castToWindow "window1"
    onDestroy window mainQuit
    label       <- xmlGetWidget xml castToLabel "label1"
    entry       <- xmlGetWidget xml castToEntry "entry1"
    onKeyPress window $ \(Evt.Key _ eventSent _ _ _ _ _ _ name _) -> do
      keyPressHandler label entry name
      return eventSent
    widgetShowAll window
    mainGUI

keyPressHandler :: Label -> Entry -> String -> IO ()
keyPressHandler label entry "Return" = do
  name <- get entry entryText
  set label [labelText := name]
keyPressHandler _ _ _ = return ()
たかだかこれだけのコードを書くのに、かなり骨を折るが、いちおう動作する。

0 件のコメント:

コメントを投稿