gtk2hs+GladeでGUI対応:タイムライン表示+ツイート
前回GLUTを使いましたが、やっぱりこのままこれでGUI開発を続けるのは不可能な気がしたので、
なんとかしてgtk2hsをインストールしました。
インストール時に出るエラーが"gtk2hsC2hsのバージョンが違います"だったので、cabalをアップデートしてからもう一度gtk2hs-buildtoolsをインストールしてみたのですが、うまく行かない。
なんでだ?と思ってよく読むと、gtk2hsC2hsのファイルがあるのは~/.cabal/binのはずなのに、"/usr/local/binにあるgtk2hsC2hsのバージョンが……"と怒られている。
どうやら、~/.cabal/bin以外にも/usr/local/binにgtk2hsC2hsがあったらしく、そのファイルはcabalでアップデートされていなかったと。
そこで、/usr/local/binにあるファイルを~/.cabal/binにあるものに代えたらインストールに成功しました。
で、まぁ苦労しましたがこれでめでたく使えるようになったんで、Gladeも入れてシンプルなGUIを作ってみました。
gtk2hsとGladeを対応させる方法はReal World Haskellに方法+コードが載っていたので、基本的な部分はそれの丸写しです。
Gladeで用意したhshstterGUI.gladeと、HshstterLocalMain.hs、HshstterMain.hsを用意して以下のようにGladeと結合。
GUIにはツイート書込欄・ツイートボタン・タイムライン表示領域の3つをセット。
HshstterLocalMain.hs
HshstterMain.hs
これでOK、コンパイル→実行……しようとしても読み込まれない。
調べてみると、同じようなエラーに悩まされている人のブログ記事を発見。
http://yasutech.blogspot.jp/2011/12/gtk2hs-glade.html
どうやら、Glade3.10系のXMLの形式にgtk2hsがまだ対応してない模様。
そこで、出力されたXMLファイルを3.8版っぽく書きなおした所、なんとか読み込んでくれました。
以下のようなところを書き換えました。
・<interface>タグを<glade-interface>タグに書き換え
・<requires ……>タグを消去
・<object>〜</object>タグを<widget>〜</widget>タグに置き換え
こんな機械的な書き換えで通ったので、スクリプト書いて置き換えたほうがよさそうですね……
ともあれこれでウインドウが表示されたので、あとはイベントを登録するだけなのですが、
ここはかなり直感的に書くことができて、使いやすかったです。
・タイムラインを表示
この部分の実装は、ひとまず書き込み不可なテキストエリアに表示する、という実装にしました。
(これだと画像の表示ができない気がするので、あくまで暫定的に。)
TextView Widgetにテキストを表示させるには、
TextBufferを取得→TextBufferにテキストをセット
という段階を踏めばとりあえず表示できます。
TextBufferの取得は
Graphics.UI.Gtk.Multiline.TextView.textViewGetBuffer :: TextViewClass self => self -> IO TextBuffer
TextBufferへのテキストのセットは
Graphics.UI.Gtk.Multiline.TextBuffer.textBufferSetText :: TextBufferClass self => self -> String -> IO ()
で行えます。
・タイムラインの更新
上のshowTimeline関数はIO Bool型にしていますが、これは次の関数の引数にするためでした。
Graphics.UI.Gtk.General.General.timeoutAdd :: IO Bool -> Int -> IO HandlerId
第二引数に指定した秒数ごとに、第一引数に指定したアクションを実行します。
Bool型の引数がFalseになるとそこでタイマ繰り返しが終了し、Trueが返される限りインターバルごとに実行され続けます。
ここではshowTimelineはTrueをreturnし続けるので、更新され続けます。
・入力欄にツイート内容を書き込み、tweetボタンでツイートする
ツイート入力欄はEntry Widget、ボタンはButton Widgetを使用。
Entry Widgetの内容の取得は
Graphics.UI.Gtk.Entry.Entry.entryGetText :: EntryClass self => self -> IO String
逆にEntry Widgetのセットは
Graphics.UI.Gtk.Entry.Entry.entrySetText :: EntryClass self => self -> String -> IO ()
で行えます。
ボタンがクリックされた時のアクションの設定は
Graphics.UI.Gtk.Buttons.Button.onClicked :: ButtonClass b => b -> IO () -> IO (ConnectId b)
を使い、第一引数にButton Widgetを、第一引数にクリック時のアクションを指定します。
・xボタンでプロセスを終了させる
実はこのままだと、デフォルトではxボタンを押してもプロセスは死なず、ウインドウだけ消えます。
そこで、xボタンをクリックした時のアクション
Graphics.UI.Gtk.Abstract.Widget.onDestroy :: WidgetClass w => w -> IO () -> IO (ConnectId w)
で指定して、プロセス終了関数
Graphics.UI.Gtk.General.General.mainQuit :: IO ()
を呼ぶ必要があります。
とりあえず、これでクライアントとして最低限の機能は実装できました。
エラー処理やらいろいろなところが雑ですが……
ツイートもByteStringで処理するべきでしょう。