Groovy と GUI アプリケーション

たまに作業の関係で OS ネイティブの GUI アプリケーションを作りたくなる時があります。 適当に自分用につくったコマンドラインベースのものが "ウケ” がよくて GUI に昇格させてみんなに使ってもらう、なんてときですね。 🙂

加えてぼくは普段 Linux を使っていますが、折角なのでたまに使う Windows でも同じもの動かしたいなんて要求が加わってくると使う環境は絞られてきて、、 GTK + スクリプト系言語とか Mono とか Java + Swing とかで、、いろいろやってみましたが一番簡単にランタイム環境を用意できて綺麗に動かせるのは Java + SWT ではないかと思います。

というわけで、SWT。

しかも SWT あまり分かっていないのに、これを Groovy の groovy-swt モジュールを使って呼び出してみました。 …分からないことだらけで大変(笑)

Groovy – GroovySWT

GroovySWT is a wrapper around SWT, the eclipse Standard Widget Toolkit. It allows you to easily write Eclipse SWT applications by using Groovy’s builder mechanism.

これが何かといえば、Groovy お得意の動的言語の機能をつかって、GUI のツリーを Groovy 記述で構築してしまおうというものです。

GUI コンポーネントを扱ったことがある方なら分かりますが、これらは親子関係とかいろいろ大変ですのでこれを解決しようという動きです。 XML 定義に Groovy の制御構文が使える、というイメージで扱うのがよさそうです。

ちなみに、GroovySWT のダウンロードですが、現在あろうことかリンクが切れていますのでリポジトリからダウンロードします。

Revision 21442: /trunk/groovy/modules/groovy-swt/dist

groovy-swt-0.5-examples.zip

groovy-swt-0.5-windows-all.jar

groovy-swt-0.5-without-swt.jar

without-swt のほうがうまいこと swt だけ除いてある jar になっていますので、Windows 以外の場合は without と各機種にあった swt をいっしょにクラスパスに通しておきます。

GroovySWT 0.5 時点での SWT 依存は 3.5 になっていましたので以下からダウンロードできます。(trunk のコミットコメントみると 3.5.1 でもよさそうな感じです)

Eclipse Project

swt-3.5-win32-win32-x86.zip
swt-3.5-win32-win32-x86_64.zip
swt-3.5-wpf-win32-x86.zip
swt-3.5-win32-wce_ppc-arm-j2se.zip
swt-3.5-win32-wce_ppc-arm-j2me.zip
swt-3.5-gtk-linux-x86.zip
swt-3.5-gtk-linux-x86_64.zip
swt-3.5-gtk-linux-ppc.zip
swt-3.5-motif-linux-x86.zip
swt-3.5-gtk-solaris-sparc.zip
swt-3.5-gtk-solaris-x86.zip
swt-3.5-motif-solaris-sparc.zip
swt-3.5-motif-hpux-ia64_32.zip
swt-3.5-photon-qnx-x86.zip
swt-3.5-motif-aix-ppc.zip
swt-3.5-cocoa-macosx.zip
swt-3.5-cocoa-macosx-x86_64.zip
swt-3.5-carbon-macosx.zip

各 OS の環境に合わせた x32 とか x64 とか使えばOKです。 Windows は WPF 版なんかが目を引きますね。 🙂

commons-logging-1.1.1.jar も動作時に class not found したので入れました。 swt.jar と groovy-swt-0.5.without-swt.jar とあとは Groovy の jar を入れれば準備OKです。

またこれを使ってプログラムかくにはクラスライブラリのソースコード必須です。 swt と groovy-swt のソースはダウンロードして jar にアタッチしておきましょう。(まずは groovy.swt.SwtBuilder からみると良さそうです。あとは SWT のコンポーネントソース見てプロパティ設定していきます)

ということでこの状態でから始めたのですが、、

[tegaki]どーやってもメニューが追加できない!![/tegaki]

サンプルもうまく動いていない模様。  ここの解明が疲れました。。

どうやら groovy-swt のライブラリに不具合があるようです(たぶん)。

これであっているかどうかな上にひどいコードですが、以下のようにパッチ。。(早く動かしたくて手抜きしたの図)

しかも diff 逆にとってしまったのはご愛嬌。。 + と – がさかさまです(笑)

Index: WidgetFactory.java
===================================================================
--- WidgetFactory.java    (revision 5)
+++ WidgetFactory.java    (revision 2)
@@ -119,19 +119,14 @@
                                 Object[] arguments = { parent, new Integer(style) };
                                 return constructor.newInstance(arguments);
                             }
+                            // lets try to find the constructor with 1
+                            // arguments
+                        } else if (types.length == 1
+                                && types[0].isAssignableFrom(parent.getClass())) {
+                            Object[] arguments = { parent };
+                            return constructor.newInstance(arguments);
                         }
                     }
-                    // lets try to find the constructor with 1
-                    // arguments
-                    for (int i = 0, size = constructors.length; i < size; i++) {
-                        Constructor constructor = constructors[i];
-                        Class[] types = constructor.getParameterTypes();
-                        if (types.length == 1
-                                && types[0].isAssignableFrom(parent.getClass())) {
-                            Object[] arguments = { parent };
-                            return constructor.newInstance(arguments);
-                        }
-                    }
                 }
             }
             return beanClass.newInstance();

SWT の org.eclipse.swt.widgets.Menu は 2引数コンストラクタがあって parent と style が設定できて、parent が Shell オブジェクトで style が SWT.BAR のときにメニューとしてはりつくと思うのですが、パッチ前の処理では 1引数コンストラクタを先に見つけてしまうため、style なしで new されてうまくメニューがでないという不具合のようです。(たぶん他の Widget は 2引数のコンストラクタが上のほうに定義されているのでしょう)

さて、そんなこんなで以下のつくったソースで動作させてみる。

変数定義の位置がおかしいとか大文字小文字が混在しているとかは気にしない方向で。  万が一動かしてみたい奇特な方がいらっしゃいましたら、画像のところはコメントアウトしてください。 🙂

import groovy.swt.SwtBuilder
import org.eclipse.swt.layout.GridData;
 
class Gonswt {
 
   def shell
   def sashForm1
   def builder
   def tab
 
   def run() {
      builder = new SwtBuilder()
      shell = builder.shell (text:'Groovy on SWT', size:[410, 600], location:[300,200]) {
         menu(style:"BAR") {
            menuItem("バインダー" , style:"CASCADE") {
               menu( style:"DROP_DOWN") {
                  menuItem(style:"PUSH", "&Open file")
                  menuSeparator()
                  menuItem(style:"PUSH", "E&xit"){
                     onEvent('Selection'){
                        shell.dispose()
                     }
                  }
               }   
            }
            menuItem("焼きそばん" , style:"CASCADE") {
            }
         }
         gridLayout()
         composite() {
            rowLayout()
            toolBar( style:"FLAT, WRAP, RIGHT") {
               toolItem(style:"push", "再チャレンジ") {
                  image( src:'resource/item1.png' )
               }
               toolItem(style:"push", "里に帰ろう") {
                  image( src:'resource/item2.png' )
               }
            }
         }
         composite(layoutData:gridData(horizontalSpan:3
            , horizontalAlignment:GridData.FILL
            , verticalAlignment:GridData.FILL
            , grabExcessHorizontalSpace:true
            , grabExcessVerticalSpace:true)) {
            fillLayout()
            sashForm1 = sashForm( style:"vertical") {
               tab = cTabFolder( id: 'myTabs', tabHeight:28, simple:false, style:"BORDER") {
                  fillLayout()
                  //gridData( style:'fill_both')
                  cTabItem( 'タイムマシーン', style:'none') {
                     table(linesVisible:true, headerVisible:true, style:"V_SCROLL") {
                        tableColumn("食べたい", width:80)
                        tableColumn("食べちゃいたい", width:60)
                        10.times {
                           tableItem(["", "ほげほげ"]) {
                              image:image( src:'resource/item3.png' )
                           }
                           tableItem(["", "もげもげ"]) {
                              image:image( src:'resource/item4.png' )
                           }
                           tableItem(["", "ほえほえ"]) {
                              image:image( src:'resource/item5.png' )
                           }
                        }
                     }
                  }
                  cTabItem( 'マンション', style:'none') {
                     text( style:'border, multi, v_scroll, h_scroll', 'Content for Tab1' ) 
                  }
                  cTabItem( 'ダイレクトショッピング', style:'none') {
                     text( style:'border, multi', 'Content for Tab2' ) 
                  }
               }
               composite( style:"none" ) {
                  fillLayout()
                  text( style: "border, multi, wrap")
                  }
            }
            // 2つの weight を決める
            sashForm1.weights = [90, 10]
            // デフォルト表示タブ
            tab.setSelection(0)
         }
         composite(style:"BORDER") {
            rowLayout()
            toolBar( style:"FLAT, WRAP, LEFT") {
               toolItem(style:"push", "ラーメンバー")
            }
         }
      }
 
      // fillLayout のときは縮められるので pack を false にする
      shell.doMainloop(false)
   }
 
   public static void main(String[] args) {
      new Gonswt().run();
   }
}

とりゃ、実行!

Linux 版。

groovy30

Windows 版。

groovy31

Mac 版。 は、残念ながら手元に機械がないのでできません(笑)

てなことで、いい感じでネイティブ描画されて同じように表示されているのが分かります。 可変ウインドウもばっちりです。 斜めタブは Eclipse でおなじみの CTabFolder 。

自分で使いそうな GUI 部品はだいたい入れてみて動いたので、この後は知恵と勇気でなんとかいけそうです。。 間違ってるかもですが。。

まぁ正直なところ SWTBuilder 使わなくても当然 Groovy からは SWT を直接呼べるのでそっちでもいいのですが、これはお遊びということで。 ちなみに、JFace の Builder もありますので、そちらを使えばコンポーネント・データバインディングを使うことができます。

一応描画ができるようになったので、あとはイベントリスナーとスレッド系の操作が分かればなにかつくれそうです。 Groovy でやると確かにお手軽感あります!

もうちょっとやってみます。 🙂

WordPress 3.1 の新しい XMLRPC API

RC3 まできてもうすぐ出ると思われる WordPress 3.1!。

つらつら changelog をみていますと、XMLRPC の API にメディアライブラリ系の API が追加になっていましたので、ここ hiromasa.another の 3.1 テストもかねまして遊んでみました。 :)。

新しく追加になったのは、wp.getMediaLibrary、wp.getMediaItem、wp.getPostFormats で前2つはメディアライブラリに関するもの、最後は投稿フォーマットに関するものです。

というわけで、せっかくですので練習もかねて Groovy  からレッツアクセス。 😀

import groovy.net.xmlrpc.*
 
class WPXMLRPCProxy {
    def proxy
    def user
    def passwd
    def currentArgs
    
    def methods = [
        getMediaLibrary :
            { [blog_id, this.@user, this.@passwd, struct]},
        getMediaItem :
            { [blog_id, this.@user, this.@passwd, attachment_id] },
        getPostFormats :
            { [blog_id, this.@user, this.@passwd] }
    ]
    
    def invokeMethod(String method, Object args) {
        currentArgs = args[0]
        def wpargs = methods[(method)]()
        method = "wp." + method
        proxy."$method"(wpargs)
    }
    
    def getProperty(String name) { currentArgs[name] }
}
 
def wp = new WPXMLRPCProxy(
    proxy : new XMLRPCServerProxy("https://another.maple4ever.net/xmlrpc.php")
    , user : ""
    , passwd : "")
 
wp.getMediaLibrary(blog_id:0
    , struct:[number: 1, mime_type:"image/jpeg"]).each {
    println it
}
 
println wp.getMediaItem(blog_id:0, attachment_id: 1394).metadata
 
println wp.getPostFormats(blog_id:0)

 

わざわざこんなことしなくてもいいかもですが、invokeMethod と getProperty の練習がてら Groovy に備わるリフレクションの仕組みでメソッドとプロパティを動的に生成して WordPress の API と同じ形で呼べるようにしています。

フィールドの methods マップにメソッド名と引数クロージャを追加していけば、wp.* 系の API がそのまま呼べるはずです。(今は新 API だけ入れています)

実行!

wp.getMediaLibrary

[title:DVC00169.jpg, thumbnail:https://another.maple4ever.net/wp-content/uploads/2011/01/DVC00169-150x150.jpg, date_created_gmt:Sun Jan 23 09:33:11 JST 2011, description:, link:https://another.maple4ever.net/wp-content/uploads/2011/01/DVC00169.jpg, parent:1396, caption:, metadata:[sizes:[thumbnail:[height:150, file:DVC00169-150x150.jpg, width:150], medium:[height:225, file:DVC00169-300x225.jpg, width:300]], height:308, file:2011/01/DVC00169.jpg, width:410, hwstring_small:height='96' width='128', image_meta:[focal_length:0, title:DVC00169, shutter_speed:0, iso:0, camera:SH906i, created_timestamp:1295293049, caption:copy="NO" SH906i, copyright:, credit:, aperture:0]]]

wp.getMediaItem

[sizes:[thumbnail:[height:150, file:DVC00169-150x150.jpg, width:150], medium:[height:225, file:DVC00169-300x225.jpg, width:300]], height:308, file:2011/01/DVC00169.jpg, width:410, hwstring_small:height='96' width='128', image_meta:[focal_length:0, title:DVC00169, shutter_speed:0, iso:0, camera:SH906i, created_timestamp:1295293049, caption:copy="NO" SH906i, copyright:, credit:, aperture:0]]

wp.getPostFormats

audio:Audio, standard:Standard, status:Status, gallery:Gallery, quote:Quote, link:Link, image:Image, chat:Chat, aside:Aside, video:Video

なーるほどざ、にゃんこ。 🙂

あとはちょいちょいプログラムかけば、条件指定してクライアントアプリから画像ひっぱるとか簡単ですね。

xmlrpc01 

よいよい、簡単。 これは swing ですが次は SWT バインディングに挑戦してみたいです。 😀

最近のスクリプト言語

といっても全然最近ではないのですが。 🙂

前のエントリでも書きましたが恥ずかしながら Ruby とかやったことがなく、実はいつだったか Ruby のソースをみたときに処理は分かれど、ライブラリの中身がどうやっているのか理解できず、ショックで引退を考えたことがありましたので最近ようやく勉強を始めました(笑)

ちょっと仕事に近いところというところで、Ruby じゃなくて Groovy でやってます。

DVC00169

Groovy In Action は絶版になっているのか Amazon ですごい値段がついていましたが、ジュンク堂で無事ゲット。 2008 年の本なのでぼくがみても現行バージョンと変わっているなと思う点もありますが(HashMap が LinkedHashMap になっている等)、それほど困ることはなさそうです。 この本は技術書としてもとても面白かったです。 🙂

Groovy はマップ・リスト操作、動的系の機能とクロージャなどのシンタックスを限りなく最小限に抑えている他、JDK にリフレクションで追加する形でクロージャを用いて処理を差し込むパターンのメソッドが多数用意してあるため、ソースから冗長な記述を削除できすっきりとしたソースをかくことができるようです。

というわけで Groovy In Action を読んで仕掛けが何となく分かったので、試しに RSS から画像を取得してファイル保存するプログラムをかいてみました。

// @Grab(group='org.codehaus.groovy.modules.http-builder', module='http-builder', version='0.5.1')
import groovyx.net.http.HTTPBuilder
import static groovyx.net.http.Method.*
import static groovyx.net.http.ContentType.*
 
rssUrl = "http://feedblog.ameba.jp/rss/ameblo/nakagawa-shoko/rss20.xml"
pUrl  = /<img [^>]*src\s*=\s*[\"']?([^\"'> ]+.jpg)/
pFile = /([^\/]+?\.jpg)$/
 
new HTTPBuilder(rssUrl).request(GET, XML) {
   response.success = { res, rss ->
      rss.channel.item.each {
         it.description.text().eachMatch(pUrl) {
            filename = it[1].toURI().path =~ pFile
            new HTTPBuilder(it[1]).request(GET, BINARY) {
               response.success = { res2, image ->
                  new File("./file/${filename[0][0]}").bytes = image.bytes
               }
            }
         }
      }
   }
}

もうちょっと短く書く方法もあるとは思うですが、例えば PHP でかいた場合と違って、http アクセス失敗とか File のオープンクローズとかの処理がソース上に現れていないにもかかわらず、これでうまくいってしまうであろうところが良いですね。

XML パースにはダイナミックランゲージの機能が使われているのが分かります。 あとは String の eachMatch にクロージャ渡せるとか便利です。

  • メソッドの引数の括弧は省略できる。 また、一番外側の引数がクロージャだった場合は括弧にいれなくてOK。
  • インスタンスのフィールドは getter/setter が勝手に生成され、プロパティみたいにフィールド名かけばアクセスできる。

他にもコンストラクタにマップ渡すとフィールドを key/value で初期化できるとか、その際のマップの記述は [] つけなくていいとか、なんだか知らないとぱっと見分からないことでいっぱいだったりするのですが、慣れると便利そうです。

動的型付け、動的言語機能は IDE がほぼ無力になるのでドキュメントとにらめっこするのがちょっと大変ですし、ランタイムエラーをやっぱり出しがちですが、どこで苦労するかのバランスは悪くないように思いました。

というわけで実行。

groovy10 

[tegaki]わーい。 とれた。[/tegaki]

…引退します。。(笑)