マルチプラットフォームの GUI アプリケーション

たまぁになんですが、自作のちょっとしたアプリで GUI を使いたいときがあります。 ほいでもって、ぼくは家で大抵は Linux を使っているのですが、せっかくつくったアプリなのでたまに使う Windows でも使いたい、、。 ということで、マルチプラットフォームで動く GUI アプリ。

Mono + GTK、Mono + Windows.Forms、 Java + Swing、 Java + GTK(あるのかな?)、などなど思いつきますが、今回は Java + SWT をやってみました。 🙂

GTK は各種プラットフォームで動作する GUI ツールキットですが、自前で描画をしているので特に Linux 以外で動かすとやっぱりほんのすこーし、違う。 まぁ特別問題があるわけではないのですが、Pidgin や GIMP を Windows などで動作させたことがある方ならなんとなく分かるのではないでしょうか。

Swing は逆に Windows で動かす分にはそれなりなのですが、Linux で動かすとフォント系が厳しい。。 Swing は TrueType も自前で描画するので特にアンチエイリアスがある環境ではなにか違う感じを醸し出します(笑) (フォントレンダラがあんまりよくないのかな)

てなわけで、やっぱネイティブだよねってことで Java + SWT です。

Standard Widget Toolkit – Wikipedia

SWT は Java で書かれている。GUI部品を表示するため、SWT はそのオペレーティングシステムが提供するGUIライブラリを JNI(Java Native Interface)経由で使用する(これはシステム固有のAPIを使う一般的手法である)。SWT を使うプログラムは移植性があるが、ツールキット自体の実装は Java でかかれているにも関わらず、各プラットフォーム固有である。

ネイティブライブラリですが、Windows、Linux、Mac OS X などなどいろんなプラットフォームに移植されていますので、実行ファイルこそ変われど同じアプリケーション(ソース)が動作します。 Eclipse などの巨大なアプリケーションが SWT で動作していますので、ライブラリ自体もかなり枯れていると思われます。

で、SWT にかぶる形で GUI のフレームワークとして JFace というのがあって今回はこれも使ってみました。

とりあえず、SWT と JFace のライブラリを持ってきます。 JFace は Eclipse の plugins に入っているものをほげってきました。(外部 Jar 追加で参照してもいいのですが、なんとなくコピーして自分のおなかに)

jface02

こんな感じにライブラリをいれておきます。 これは Linux の場合。

詳しくはこちらが参照になります。

SWTとJFaceに必要な外部JARファイルを特定する – Identify the Required External JAR Files for SWT and JFace – 何かしらの言語による記述を解析する日記

JFaceプロジェクトには、SWTのクラスとJFaceのクラス、その他JFaceが依存するEclipseのクラスが必要です。SWTプロジェクトのウェブサイトから、SWTのクラスを含むファイルをダウンロードできます。JFaceのファイルとJFaceが依存するファイルは、プロジェクトに手動で追加する必要があります。

ほいでもってソースをかきます。

あちこちのサイト様を参照しました。 JFace は ApplicationWindow から extends して開始です。 (未来の自分用へのメモ)

JFaceSample.java

package net.maple4ever.sample.jface;

import java.io.File;

import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.window.ApplicationWindow;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;

public class JFaceSample extends ApplicationWindow {

    public JFaceSample() {
        super(null);
    }

    @Override
    protected Control createContents(Composite parent) {
        // ウインドウタイトル設定
        parent.getShell().setText("JFaceSample");
        // ウインドウサイズ設定
        parent.getShell().setSize(480, 320);

        // 親の下にさらに Composite をつくる
        Composite child = new Composite(parent, SWT.NONE);
        // レイアウトマネージャ設定
        child.setLayout(new GridLayout());
        // テーブル作成
        TableViewer table = new TableViewer(child);
        table.getTable().setLayoutData(new GridData(GridData.FILL_BOTH));
        // テーブルにプロバイダ設定
        table.setContentProvider(new FileTableContentProvider());
        table.setInput(new File(System.getProperty("user.home")));

        return parent;
    }

    @Override
    protected MenuManager createMenuManager() {
        // 親メニュー作成
        MenuManager bar = new MenuManager();
        MenuManager fileMenu = new MenuManager("ファイル(&F)");
        // Action クラスを継承したインスタンスを渡す
        fileMenu.add(new ActionExit(this));
        // 追加メニューを返却
        bar.add(fileMenu);

        return bar;
    }

    public static void main(String[] args) {
        // ApplicationWindow インスタンス生成
        JFaceSample window = new JFaceSample();
        // メニューバー追加(createMenuManagerメソッド が呼ばれる)
        window.addMenuBar();
        // ウインドウクローズまでイベントループブロック指定
        window.setBlockOnOpen(true);
        // イベントループ開始(終わるまでここでブロック)
        window.open();
        // イベントループ終わったらリソース解放
        Display.getCurrent().dispose();
    }

}

ActionExit.java

package net.maple4ever.sample.jface;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.window.ApplicationWindow;

class ActionExit extends Action {

    private ApplicationWindow window = null;

    public ActionExit(ApplicationWindow win) {
        // ApplicationWindow もらっておく
        window = win;
        setText("終了(&X)@Ctrl+W");
    }

    @Override
    public void run() {
        // Action の処理を run にかく
        // ApplicationWindow 呼んじゃえ
        window.close();
    }

}

FileTableContentProvider.java

package net.maple4ever.sample.jface;

import java.io.File;

import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.Viewer;

class FileTableContentProvider implements IStructuredContentProvider {
    @Override
    public Object[] getElements(Object element) {
        File currentDir = (File) element;
        File[] files = currentDir.listFiles();
        return files == null ? new Object[0] : files;
    }

    @Override
    public void inputChanged(Viewer arg0, Object arg1, Object arg2) {
    }

    @Override
    public void dispose() {
    }
}

で、まずは Linux。 実行~ 🙂

jface03

うむうむ。 Linux SWT の GTK 版なのであたりまえですが、GTK で描画されています。 Linux の場合、GTK のテーマでみんないろいろ画面をかえているので、何にもしなくても追随してくれるのはやっぱりいいですね。 🙂

同様に Windows でも Windows 用の SWT ライブラリをあつめて、同じアプリケーションのソースを実行!

jface01

エアローってことで、 Windows 7 で動かしたの図ですが同じソースでネイティブ描画してくれております。 よい、よい。 😀

JFace は、普通の GUI フレームワークとちょこっと趣向が違うところがあったりして面白いですね。 スレッドの中からの GUI 描画方法とかまだ全然分かっていない部分もあって本気で使いこなすには時間がかかりそうですが、小物ならなんとかいけそうです。

ちなみにできたアプリの配布ですが、小さいアプリであれば Eclipse のエクスポートウイザードの実行形式 jar で ant 書き出してもらうのが楽そうです。

jface04

で、できた jar 玉を JRE が入っていれば通常ダブルクリックで実行できます。

jface05

gjc で JRE なしのまじネイティブ (.exe 版とか)もできるのかなぁ。 Eclipse の gjc 版はあるのでできると思いますが、ちょっとそこまでは調べていません。

swt 、JFace とも Eclipse Public License 系のライセンスなのでこういった配布形態も問題ないとおもいますが、やる方がいたら各自よくご確認ください。 ぼくは配布するまではなさそうなので、、(笑)

てなわけで、画面系はやっぱりネイティブがいいよね、という方は試してみるといいかもしれません。 🙂

WordPress 2.9 リリースと 2.9 対応版 wp-kyodeki とタイムゾーンと

というわけで、まさに日本時間で昨日になりますが WordPress 2.9 がリリースされました。 めでたい。 いろいろ新機能もあるようで使うのが楽しみです 🙂

さてさて、昔はつくっていたプラグインがバージョンアップでよく動かなくなっていたりしていたのでベータでチェックとかしていたのですが、最近わりと動くのでさぼってたら、ふと wp-kyodeki がおかしくなっているよう、、その動作を見てコアにとある変化があったのに気がつくぼく・・・。(←遅い

というわけでとりあえず wp-kyodeki。

このプラグインはアクセスカウンタみたいなもんですが、日付をまたぐとカウントがクリアされる仕様になっております。(もともとアクセスデータを蓄積しないようにつくったプラグインでした)

夜行性のぼくは 2.9 にアップデートされた我がサイトのサイドバーをみて、日付をまたいだのにカウントがクリアされていないことに気がついたのです。。

というわけで不具合がありましたので、 JSeries にリリースしました。お使いの方は更新ください。

WordPress Plugins-JSeries » wp-kyodeki (本日人気の記事表示ウィジェット)

タイムゾーンでの日付変更クリアとならない不具合が修正されます。

さて、なんでだろうという話ですが、コアの wp-settings.php に以下のコードが追加されていました。

if ( function_exists('date_default_timezone_set') )
    date_default_timezone_set('UTC');

PHP の date 関数の戻り値などに影響する、timezone の設定でこれが標準時間になるようになっています。 というわけで、これがなかった今までは日本時間が戻ってきていたのですが、UTC になったので標準時間がもどってくるようになった、ということでした。

wp-kyodeki の不具合です。 冷静に考えたら海外サーバだと WordPress のタイムゾーンを日本に設定していてもあちらの時間で更新されていましたね。すいません(笑)

で、そういやコアがどうなっているのかあんまり考えたことなかったなぁとちらほらソースをみていると、結構 date を生でつかっているところがあるようです。

まぁ、いまも日本タイムゾーン設定で海外サーバ使われている方も多くて問題になっていないようなのでコアは大丈夫かと思いますが、プラグインは考慮漏れているものがあるかもしれない(あんまりないと思いますが)ので、万が一 2.9 の日付系で問題があったら疑ってみてください。 🙂

WordPress プラグイン管理画面を簡単につくる非実用なフレームワーク

WordPress プラグインをつくるときにぼくが結構大変に思うのは、実は管理画面をつくること・・・。

なにげに自分のプラグインのソースを眺めると実ロジックより大きいこともあったりして笑ってしまいます。 というわけで、既存の管理画面のないプラグインにポンとつける用途を念頭に試しに(本当に試しに)フレームワーク的なものをかいてみました 🙂

といっても、PHP 的に厳しい部分もあったりしてごにゃごにゃしなきゃいけない部分もありであまり実用的ではなく、まぁまぁこんな作り方もできるよねって話でみてもらえたらと思います。 PHP 5 系専用です。

まずはサンプル。 なにかしら管理画面の持たない既存のプラグインがあることにします。(以下、ソラでかいているので間違っているかもなのでイメージで)

なんでもいいですがおなじみ the_content フィルターで何か追加する感じのやつにしてみましょう。

   1: function addContent($content) {
   2:     $header = "ほげ";  
   3:     $content = "<p>$header</p>" . $content;
   4:     return $content;
   5: }
   6: add_filter('the_content', 'addContent');

記事の頭に「ほげ」ってつけるプラグインです。。 まぁサンプルってことでごかんべんを。(笑 プラグインヘッダなどは省略です。

ここでは固定値「ほげ」を管理画面から設定できるようにしたいと思います。

とりあえず、こいつを「クラス」にします。 っていってもほぼ機械的な置き換えです。

可変にしたい部分をフィールド(メンバ)においているところと「コンストラクタ」で初期化しているところに注目してください。

   1: class example {
   2:     
   3:     var $header;
   4:     
   5:     function __construct() {
   6:         $this->header = "ほげ";
   7:     }
   8:     
   9:     function addContent($content) {
  10:         $content = "<p>{$this->header}</p>" . $content;
  11:         return $content;
  12:     }
  13:     
  14: }
  15: $example = new example();
  16: add_filter('the_content', array($example, 'addContent'));

$header 変数が外にいきフィールド(メンバ)になりました。

__construct は new ってやったとき(15行目)に勝手に動く特別なメソッドだと思ってください。(ちなみに PHP はコンストラクタもメソッド扱いらしい)

変数を初期化して、あとは WordPress からの the_content の呼び出しを待っているイメージのプログラムです。

さてここからが本題。

今回つくってみたフレームワークというのは DolphinPanel という名前のプログラムで、こいつをつかうとこの手のプラグインに管理画面をつけられます。 “Dolphin” にはなんら意味はないです。 たまたまエコーザドルフィンを思い出しただけです。。(古)

まずプラグインのあたまで、このプログラムを require してあげます(1行目)。 で、プラグインのクラスに dolphin のインターフェースを “implements” します。

まぁかくだけです。 🙂

   1: require_once('dolphin-panel.php');
   2:  
   3: class example implements Dolphinable {
   4:     
   5:     var $header;
   6:     
   7:     function __construct() {
   8:         $this->header = "ほげ";
   9:     }
  10:     
  11:     function addContent($content) {
  12:         $content = "<p>{$this->header}</p>" . $content;
  13:         return $content;
  14:     }
  15:     
  16: }
  17: $example = new example();
  18: add_dolphin($example, 'example');
  19: add_filter('the_content', array($example, 'addContent'));

implements Dolphinable したクラス(3行目)は Dolphin 管轄の WordPress 管理画面を持つことができます。 権利をもったクラスを add_dolphin します。(18行目)

さて Dolphinable であるためには 3つの管理画面に関する決められたメソッド(onActive、onBind、onDispose)が必要になります。 これを実装していきます。

といっても単純にテキストボックスをひとつつけて、値を保存したいだけなら onActive だけ実装すればいいです。 onBind には return true; 、onDispose は空実装してください。

   1: require_once('dolphin-panel.php');
   2:  
   3: class example implements Dolphinable {
   4:     
   5:     var $header;
   6:     
   7:     function __construct() {
   8:         $this->header = "ほげ";
   9:     }
  10:     
  11:     function addContent($content) {
  12:         $content = "<p>{$this->header}</p>" . $content;
  13:         return $content;
  14:     }
  15:     
  16:     function onActive(DolphinForm $form) {
  17:         $form->add(new DolphinTextBox('$header', 'ヘッダ'));
  18:     }
  19:     
  20:     function onBind(DolphinForm $form) {
  21:         return true;
  22:     }
  23:     
  24:     function onDispose() { }
  25:     
  26: }
  27: $example = new example();
  28: add_dolphin($example, 'example');
  29: add_filter('the_content', array($example, 'addContent'));

17行目が Dolphin のキモで、この書き方で変数(フィールド)と管理画面のテキストボックスを結びつけます。 HTML フォームとの値変換、変数へのバインドをこの記述だけでやってくれます。(配列渡すと複数テキストボックスがでてきたりします)

おしまい!。

dolphinpanel02

なんだかここだけみると不思議かもしれませんが、このコードだけでめでたく管理画面がつきます。 簡単あるね。 もちろんプラグインはこの設定値で動きます。 🙂

一応、もうちょっといじるとユーザ対話などもできるようになっています。 追加処理を書くとこんな感じになります。

dolphinpanel01

ここではサンプルだったので極力短くしていますが、onBind は値検査が必要な場合に実装するメソッドで、onDispose はプラグインが非活性になった場合に実装するメソッドです。 入力値に必須が入っていなかった場合のメッセージ出力や、プラグイン非活性時のデータベースからの削除などができます。

もうちょっと詳しいコメント付きサンプルと Dolphin のソースを JSeries の CVS にコミットしてあります。 気になる方は(いるのか?!)みてみてください。

http://sourceforge.jp/cvs/view/wppluginsj/dolphin-panel/

実は管理画面でつかえる HTML コンポーネントがまだテキストボックスとチェックボックスだけだったり実用にはほど遠いのですが、まぁプラグインのサービスクラスに管理画面の意識がなくなるのは楽かなと思います。 また、ここではサービスクラスにつけていますが、単純な VO のクラスに implements  して、上に親のサービスクラスをつくってプラグインを実装するのもいいかもです。

さて、お気づきの方もいらっしゃると思いますが、requre している関係でフレームワークとかいいつつクラス名称がかぶったりして、そのままだと Dolphinable な管理画面をもつプラグインがひとつしか作れなかったり、、、 namespace 使おうと思っても PHP 5.3 からだったりけっこう八方ふさがりなところがあったりします。

一応コントローラ自体は、ひとつで複数の Dolphinable を扱えるようにしているので、myhack.php とかにいれればいいんですが、もうないし(笑) __autoload とかで気合いで最新を読むような制御も考えましたが、hackish すぎるのでやめました。。 運用対処ということで(笑)

あとあと、PHP 5.3 じゃないと private プロパティがこじあけられないので、プラグインのフィールドは public にしてください。。 まぁスコープつけなきゃよいです。(お察しの通りリフレクションを使って変数をバインドしています)

というわけで、いろいろあって途中何度か企画倒れにしようかと思ったのですが、まぁせっかくつくったので話のネタに。 たぶんバグってると思うので、実用よりプログラム的な動きだけ楽しんでもらえればと思います。 🙂