現在、勉強がてらJavaFxでAsciiDocエディタを製作中です。エディタ部分にJavaScript製のAceを採用しましたが、タイトル通り挙動がおかしくなってしまいました。
なにが起こったか
環境
- macOS Mojave 10.14.3
- Intellij IDEA 2018.3.4
- JavaFX 11
$ java -version
openjdk version "11.0.1" 2018-10-16
OpenJDK Runtime Environment 18.9 (build 11.0.1+13)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.1+13, mixed mode)
そして起こったのがこちら
見ての通り変換して確定すると改行されてしまいます。しかもこの後は日本語入力すらできない...
じゃあGoogle 日本語入力を使ってみよう!
えぇ...
こちらはスペースキーで変換する場合にはいいのですが、方向キーで変換候補を選択してしまうとJapeneseIMと同様日本語が打てなくなります。
コントローラクラスとWebviewがロードするHTMLのソースはこちら
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Ace Editor</title>
<style type="text/css" media="screen">
#editor {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
</style>
</head>
<body>
<div id="editor"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.2/ace.js" type="text/javascript" charset="utf-8"></script>
<script>
var editor = ace.edit("editor");
editor.setTheme("ace/theme/monokai");
</script>
</body>
</html>
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.web.WebView;
import java.net.URL;
import java.util.ResourceBundle;
public class SampleController implements Initializable {
@FXML
WebView aceEditor;
@Override
public void initialize(URL location, ResourceBundle resources) {
aceEditor.getEngine().setJavaScriptEnabled(true);
aceEditor.getEngine().load(getClass().getResource("/ace.html").toExternalForm());
}
}
ちなみに、Aceはemacsやvimのキーバインドを用意してくれていて
editor.setKeyboardHandler("ace/keyboard/emacs");
とすれば変えられるのですが、これを使っても少し挙動が変わるだけで実用は無理でした。
加えて、僕はJavaScriptが書けないのでJavaScript側でどうにかするのは諦めました。
上記のHTMLをブラウザで見た場合、chrome、safariでは良かったものの、firefoxではうまく動きませんでした(日本語入力ができなくなることはなかった)。JavaFXだけではなくAceの問題もあるのかもしれません。
解決策
じゃあどうしたかというと、IME使用中はJavaFXのKeyEvent
を無効化しました。
public class SampleController implements Initializable {
@FXML
WebView aceEditor;
private boolean isComposing;
@Override
public void initialize(URL location, ResourceBundle resources) {
aceEditor.getEngine().setJavaScriptEnabled(true);
aceEditor.getEngine().load(getClass().getResource("/ace.html").toExternalForm());
aceEditor.addEventFilter(InputMethodEvent.ANY, new EventHandler<InputMethodEvent>() {
@Override
public void handle(InputMethodEvent event) {
isComposing = !(event.getComposed().size() == 0);
}
});
aceEditor.addEventFilter(KeyEvent.ANY, new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
if (isComposing) event.consume();
}
});
}
}
JavaFXのイベント処理の流れはググればいっぱい分かりやすい記事がでできますので割愛させていただきます。
KeyEvent
にはKEY_PRESSED
、KEY_RELEASED
、KEY_TYPED
があります。これらの発生する順番は
KEY_PRESSED
-> KEY_TYPED
-> KEY_RELEASED
なのですがInputMethodEvent
中は基本的にKEY_RELEASED
しか発火しません。
しかし特定のキー(方向キーやshiftキーなど)に限ってはKEY_PRESSED
やKEY_TYPED
が発火します。そんなわけで最初のGIFのような動作になっていたのでした。
InputMethodEvent
のgetComposed
メソッドはObservableList
を返してくれるので、size
メソッドを使えばIME使用中か否かを判断できます。これで動きます
改良後
まとめ
Aceは日本語対応が微妙と評判?でしたがなんとかJavaFXで実用できるようになりました。
Aceを使っているJavaFX製AsciiDocエディタAsciidocFXも僕の環境では最初のGIFと同じように動いてしまって困っていました。ちなみにParalles DesktopのWindows 10上でAsciidocFXを動かしても不具合はなかったので、Macだけだと思います。修正したコードがWindowsでどうなるのかはわかりません。
もしかしたらAceやJavaFXのAPIを見落としているのかもしれません。「もっと良い実装があるよ!」、「Aceの使い方間違ってるよ!」という場合はぜひコメントで教えていただけると嬉しいです。
(製作中のAsciiDocエディタも形になったらまた投稿したいと思います)