JavaFX: WYSIWYGエディタを作る

この記事は2014年のJavaFX Advent Calendarの5日目の記事です。
前日の記事はmike_neckさんのJavaFXで画面を作るときにFXMLを小さく作るです。
明日の記事はKatsumi Kokuzawaさんです。

JavaFXでHTMLのWYSIWYGエディタを作れないかな、と考えてたら、そのものずばりHTMLEditorというクラスがあります。
そのチュートリアルサンプルコードをほんの少しカスタマイズして、既存のWebページを読み込んで、それをHTMLEditorで編集して、編集結果をブラウザ表示するというサンプルを作ってみました。

こんな感じ。
f:id:soutoku:20141202160550p:plain
左側のセピアのブラウザがオリジナルのWebページです(ブラウザ側でセピアトーン効果をかけています)。真ん中上段のHTMLEditorで編集可能で、「Load Content in Browser」ボタンを押すと、下段のテキストエリアに編集済みのHTMLコードと、右側のブラウザに編集結果のページが表示されます。

ブラウザからHTMLのソースを取得してHTMLEditorにセットする部分が地味に面倒です。一発で取得できないものだろうか。

玩具みたいなものですが、複雑なページの一部を編集した際のHTMLソースが欲しい場合などに使えそうです。

import java.io.StringWriter;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Worker;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextArea;
import javafx.scene.effect.SepiaTone;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.web.HTMLEditor;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;

public class HTMLEditorSample extends Application {

    @Override
    public void start(Stage stage) {
        stage.setTitle("HTMLEditor Sample");
        stage.setWidth(500);
        stage.setHeight(500);
        Scene scene = new Scene(new Group());
        
        // 横にレイアウトする為のHBoxを追加
        HBox base = new HBox();
        
        VBox root = new VBox();
        root.setPadding(new Insets(8, 8, 8, 8));
        root.setSpacing(5);
        root.setAlignment(Pos.BOTTOM_LEFT);
        
        final HTMLEditor htmlEditor = new HTMLEditor();
        htmlEditor.setStyle("-fx-font: 12 cambria; -fx-border-color: brown; -fx-border-style: dotted; -fx-border-width: 2;");
        htmlEditor.setPrefHeight(245);
        
        //HTMLのソースコードを表示するテキストエリアを追加
        final TextArea htmlCode = new TextArea();
        htmlCode.setWrapText(true);
        
        final WebView browser = new WebView();
        final WebEngine webEngine = browser.getEngine();
        
        //ブラウザにセピアトーン効果をかける
        browser.setEffect(new SepiaTone());
        
        ScrollPane scrollPane = new ScrollPane();
        scrollPane.getStyleClass().add("noborder-scroll-pane");
        scrollPane.setStyle("-fx-background-color: white");
        scrollPane.setContent(browser);
        scrollPane.setFitToWidth(true);
        scrollPane.setPrefHeight(180);
        
        //編集後のページを表示するWebViewを追加
        final WebView viewer = new WebView();
        final WebEngine engine = viewer.getEngine();
        
        Button showHTMLButton = new Button("Load Content in Browser");
        root.setAlignment(Pos.CENTER);
        showHTMLButton.setOnAction(arg0 -> {
            engine.loadContent(htmlEditor.getHtmlText());

            //テキストエリアにHTMLソースコードを表示
            htmlCode.setText(htmlEditor.getHtmlText());
        });
        
         // ページのロードが終了したときの処理。HTMLEditorにコンテンツを読み込む。
        Platform.runLater(() -> {
            webEngine.getLoadWorker().stateProperty().addListener((observableValue, oldValue, newValue) -> {
                if (newValue == Worker.State.SUCCEEDED) {

                    Document doc = webEngine.getDocument();
                    DOMSource source = new DOMSource(doc);
                    StringWriter result = new StringWriter();
                    TransformerFactory transFactory = TransformerFactory.newInstance();
                    try {
                        Transformer transformer = transFactory.newTransformer();
                        transformer.transform(source, new StreamResult(result));
                        htmlCode.setText(result.toString());
                        htmlEditor.setHtmlText(result.toString());
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        });

        webEngine.load("http://soutoku.hatenablog.com/");

        root.getChildren().addAll(htmlEditor, showHTMLButton, htmlCode);
        //左からオリジナルページのブラウザ、エディタ、編集後のページのブラウザを並べる
        base.getChildren().addAll(browser, root, viewer);
        scene.setRoot(base);

        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }    
}