JavaFX でプレゼンツール その 3

その 1
その 2

プレゼンツールのその 3 です。

今回はプレゼンツールの中でのデモです。プレゼンツールが JavaFX で書かれていることの最大の利点は JavaFX のデモをシームレスに行えることです。

でも、シームレスにデモすることは、そんなに難しいことではないです。

まずは、用意しておいたデモを起動させることを考えます。ここでは、Duke のイメージを回転させるというデモを使います。

public class AnimationDemo {
    public void start(Stage stage) {
        Group root = new Group();
        Scene scene = new Scene(root, 600, 500);
        
        ImageView image 
            = new ImageView(new Image(this.getClass().getResourceAsStream("DukeWithHelmet.png")));
        image.setLayoutX(50); image.setLayoutY(20);
        root.getChildren().add(image);
        
        RotateTransition rotate
            = new RotateTransition(new Duration(3000));
        rotate.setNode(image);
        rotate.setToAngle(360);
        rotate.setAutoReverse(true);
        rotate.setCycleCount(Animation.INDEFINITE);
        rotate.setInterpolator(Interpolator.EASE_BOTH);
        rotate.play();
        
        stage.setScene(scene);
        stage.show();
    }
}

start メソッドは使っていますが、Application クラスのサブクラスというわけではないです。というのも、JavaFX では Application.launch メソッドは 1 度しかコールできないからです。

ProcessBuilder クラスで別プロセスとして JavaFX のアプリケーションを起動するのであればいざ知らず、同じプロセスの中で実行するのであればアプリケーションスレッドも同じものを使用します。

さて、FXML にはデモを起動するためのボタンを作っておきます。FXML の一部分をだけを示しておくと、こんな感じです。

    <Button fx:id="p2" onAction="#executeDemo" text="Execute">

onAction 属性で # がついている文字列はコントローラクラスのメソッドを示しています。ようするに、ボタンがクリックされると、コントローラクラスの executeDemo メソッドがコールされます。

executeDemo メソッドはこうなります。

    public void executeDemo(ActionEvent event) {
        // デモの実行
        AnimationDemo demo = new AnimationDemo();
        Stage stage = new Stage();
        demo.start(stage);
    }

先ほどの AnimationDemo クラスのオブジェクトを生成した後、Stage オブジェクトを生成して AnimationDemo クラスの start メソッドをコールしています。

すると、別ウィンドウが開いて Duke が回転します。

f:id:skrb:20120630011233p:image

その場でコンパイル & 実行

ここまではたいしたことありません。では、次にその場でプログラムをコンパイルして、実行できるようにしてみましょう。

ここで使うのが、Compiler API と Script API です。

Compiler APIJava のソースファイルをコンパイルするための API、Script APIJava からスクリプト言語を実行するための API です。

本当は Compiler API だけでいいのですが、Compiler API は使うのがとても面倒くさいのです。

普通のファイルであればそれほど面倒ではないのですが、今は文字列として保持しているコードをコンパイルしなくてはいけません。そうすると、文字列を対象とした仮想ファイルシステムを構築してと文字列をあたかもファイルのように扱ってやなければいけません。

でも、そんなの面倒なので、Script API を使うわけです。

ただし、デフォルトで使用できるスクリプトは JavaScript だけなので、Java 用のスクリプトエンジンが必要です。

Java 用のスクリプトエンジンの JAR ファイルは公開されていないので、自分で JAR ファイルを作る必要があります。Java 用のスクリプトエンジンのソースは java.net の Scripting Project にあります。

Java のスクリプトエンジンは http://java.net/projects/scripting/sources/svn/show/trunk/engines/java です。

make ディレクトリに Ant の build.xml が入っているので、これを使用してコンパイルし、JAR ファイルを作ります。できた JAR ファイルの java-engine.jar を NetBeans のクラスパスに含めます。

また、Script API を使用するには JAVA_HOME/lib/tools.jar が必要なので、これもクラスパスに含めておいてください。

なお、Compiler API については ITproJava 技術最前線で取りあげているので、くわしくはそちらをご覧ください。Script API を使った場合についても書いてあります。

「Java SE 6完全攻略」第89回 プログラムからコンパイル - Compiler API その1
「Java SE 6完全攻略」第89回 プログラムからコンパイル - Compiler API その2
「Java SE 6完全攻略」第89回 プログラムからコンパイル - Compiler API その3
「Java SE 6完全攻略」第89回 プログラムからコンパイル - Compiler API その4
「Java SE 6完全攻略」第89回 プログラムからコンパイル - Compiler API その5

Script API を使った場合、スクリプトエンジンを取得して eval します。ただし、Java の場合はファイル名とメインクラスが必要なので、コンテキストで指定しておきます。

    public void initialize(URL url, ResourceBundle rb) {
        // スクリプトエンジンの取得
        ScriptEngineManager manager = new ScriptEngineManager();
        engine = manager.getEngineByName("java");

        ScriptContext context = engine.getContext();
        // メインクラスとファイル名を設定
        context.setAttribute("mainClass",
                "AnimationDemo",
                ScriptContext.ENGINE_SCOPE);
        context.setAttribute(ScriptEngine.FILENAME,
                "AnimationDemo.java",
                ScriptContext.ENGINE_SCOPE);

                <<省略>>

スクリプトエンジンは ScriptEngineManger クラスから取得します。そして、ファイル名などは ScriptContext クラスで指定します。

UI ではテキストエリアとボタンを用意しておきます。ボタンがクリックされると次のメソッドがコールされます。

    public void compileAndExecuteDemo(ActionEvent event) {
        try {
            // スクリプトの実行
            engine.eval(p3.getText());
        } catch (ScriptException ex) {
            System.err.println("スクリプトの実行に失敗しました");
            ex.printStackTrace();
        }
    }

TextArea オブジェクトから getText メソッドで表示している文字列を取得して、それを eval します。

これでその場でコンパイルして、実行できます。

デフォルトではテキストエリアに先ほどの AnimationDemo クラスがそのまま表示されています。なので、実行すると、先ほどと一緒に Duke が回転するアニメーションが表示されます。

f:id:skrb:20120630230520p:image

そこで、image.setScaleX(0.4); をテキストエリアに追加してから、ボタンをクリックして起動すると細長い Duke が回転します。

f:id:skrb:20120630230521p:image

ソースは GitHub にあげてあります。

https://github.com/skrb/SimplePresenter

ついでにポップアップメニューで終了できるようにするなど、チョコチョコと変更してあります。

追記

その場でコンパイルして、実行は Web Start や Applet だと実行できないことを書くのを忘れていました。

これをできるようにしてしまうと、任意のコードを埋め込んで、実行できるようにするわけですからセキュリティ的にかなりやばいです。まぁ、できないのが当然ですね。

もし、どうしてもやりたいという場合は、署名して、ポリシーを設定すればできますけど、お勧めはできないです。