読者です 読者をやめる 読者になる 読者になる

プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

Javaでコマンドラインから受け取った引数処理するクラス作ってみた

 コマンドラインから受け取った引数を簡単に処理したいという話。
 
 結論から言えば、すでに作られているライブラリ使うといいよ!!!
 参考:Javaでコマンドライン引数を処理する


 
 まぁ~、車輪の再々再発明なんだけど、MITライセンスのライセンスすら嫌じゃん。URLとか、ライセンス書かないといけないし。ぶっちぇけここしか外部ライブラリ使ってないし、そのためにいちいちライブラリ情報書くのもなー。
 というわけで、著作権放棄のライセンス(CC0 1.0 Universal)で作ってみた。よっぽどライセンスが緩すぎるものしか使いたくねぇ!って人は選択肢になるかもしれないよ。


 nodamushi/NArguments · GitHub

 Java6対象。(ワークステーションのLinuxがJava6だったのよっ。)
 ただし、Pathを使ってるから、コンパイルするときは、JDK7以上にするか、Path関連全部削除するか。

 基本的な思想はたぶん、arg4jと同じ。(使ったことないけど)
 簡単な使い方はDemo.javaを見ればわかるかなーってくらい、単純。ドキュメントなんていらない。
 私しか使わねぇから、ドキュメントも用意するの面倒くさいとか、そういう理由では断じてない。私は嘘はつかないっ(`・д´・ )



public static void main(String[] args){
    final NArgument<Args> nArgument = new NArgument<Args>(new Args());
    nArgument.setIgnoreConvertException(false);
    try {
      nArgument.addArguments(args);
    } catch (final ConvertException e) {
      System.out.println("Parse error:"+e.getMessage());
      System.out.println(nArgument.getDescription());
      return;
    } catch (final ReflectionException e) {
      e.printStackTrace();
      return;
    }

    final Args a = nArgument.get();
    System.out.println(a);
    if(a.help){
      System.out.println();
      System.out.println(nArgument.getDescription());
    }

  }


 Argsってのは、自分で定義するデータ格納クラスです。こんな(↓)感じで定義。
 使える型はString以外に、intなどのprimitiveと、Path,File,Charsetが使えます。
 Argumentsで正規表現で受け取る受けとらないを選択できるのが地味に便利かなぁと思っています。


@Description(
    "demo arguments.\n"
    + "[-a String]* [(-b|--bbb|--bbbb)] [-c] [-d] [(-e|--encoding) File Encoding] "
    + "[-i Integer] [(-h|--help)] [-n Number] [InputFiles]+")
class Args{
  @Option(value="-a",description="multi string option")
  private List<String> strs;

  @Option(value="-b",alias={"--bbb","--bbbb"},description="flag option")
  private boolean flagB;

  @Option("-c")
  private boolean flagC;

  @Option("-d")
  private boolean flagD;

  @Option(value="-e",alias="--encoding",description="text file charset")
  private Charset charset;

  @Option(value="-i")
  private String integer;
  //setterがある場合はsetterを優先的に使う。たとえprivateであっても。
  //従って、integerはStringであるが、setterがintになっているので、int型のOptionと判定される
  private void setInteger(final int i){
    integer = "input integer value is '"+String.valueOf(i)+"'";
  }

  //@Optionはmethodにも付加可能
  public boolean help;
  @Option(value="-h",alias="--help",description="show this help")
  private void setHelp(final boolean b){
    help = b;
  }

  @Option("-n")
  private double number;

  @Arguments(match="\\.txt$")
  private List<Path> inputTextFiles;

  @Arguments
  private List<File> inputFiles;


  @Override
  public String toString(){
    final StringBuilder sb=new StringBuilder();
    sb.append("Options\n");
    sb.append("-a = ").append(strs).append("\n");
    sb.append("-b = ").append(flagB).append("\n");
    sb.append("-c = ").append(flagC).append("\n");
    sb.append("-d = ").append(flagD).append("\n");
    sb.append("-e = ").append(charset).append("\n");
    sb.append("-i = ").append(integer).append("\n");
    sb.append("-h = ").append(help).append("\n");
    sb.append("-n = ").append(number).append("\n\n");
    sb.append("Arguments\n");
    sb.append("text files = ").append(inputTextFiles).append("\n");
    sb.append("else files = ").append(inputFiles);
    return sb.toString();
  }
}


 Demo.javaに次のような引数を与えて実行するとこんな感じになる。

-a a1 -a a2 -a a3 --bbb -cd --encoding shift-jis -i 200 --help -n 20.1 test/test1.txt img/img.jpg test/test2.txt

f:id:nodamushi:20151103211142p:plain

Options
-a = [a1, a2, a3]
-b = true
-c = true
-d = true
-e = Shift_JIS
-i = input integer value is '200'
-h = true
-n = 20.1

Arguments
text files = [test\test1.txt, test\test2.txt]
else files = [img\img.jpg]

demo arguments.
[-a String]* [(-b|--bbb|--bbbb)] [-c] [-d] [(-e|--encoding) File Encoding] [-i Integer] [(-h|--help)] [-n Number] [InputFiles]+

   Options:
      -a        :multi string option

      -b
      --bbb
      --bbbb    :flag option

      -c        :

      -d        :

      -e
      --encoding:text file charset

      -h
      --help    :show this help

      -i        :

      -n        :

WindowsでもEmacsをDaemonのように使いたい

 Windowsで何のエディタ使っていますか?私はなぜか結局Emacsです。


 ところで、WindowsのEmacsはdaemonフラグが有効ではありません。
 server使えば似たようなことできるからいいといえばいいのですが、サーバー用のウィンドウが邪魔くさいです。
 最小化してもタスクバーに残るしね。HideItとか使ってみたことあるけど、自分でウィンドウ隠すという作業が面倒くさい。あと、タスクバーにいなくなっても、タスクトレイに存在するのが希に気になる。タスクトレイ実行ツールも試したけど、駄目だった。仮想デスクトップ作って見えないように押し込んだりとかいろいろ試したけど、あぁ、Daemonがほしい。


 というわけで、WindowsでもEmacsをDaemon化したいなーとかずっと思ってたので、ふと何を思ったのか似たような動作になるようにPowerShellでやってみました。

 方法としては

  1. emacs.exeを実行
  2. しばらく待機してから
  3. ウィンドウを非表示にする

ってだけ。まぁ、要するに今までHideItとかタスクトレイ実行ツールや仮想デスクトップに頼ってたのを自分で書いただけですね。とりあえず、自動でウィンドウが見えなくなるので、なんかDeamonっぽいです。
 ※serverの起動は.emacsに書くなりしてください。

コマンド 引数 説明
Emacs-Daemon [ bin-directory [ wait-time [work-directory]] デーモンっぽく起動してみる。
bin-directory:emacs.exeがあるディレクトリ。デフォルトは空文字(Pathに登録されている場合にだけうまく動く)
wait-time:待機ミリ秒
work-directory:作業ディレクトリ
Emacs-Kill Emacsの画面全部閉じる。Emacs開いてなくて、Power Shellだけ開いてるときに使うといいんじゃないかな

 コードは下にはっつけとくので、使ってみたいという希有な人は、適宜profile.ps1にでも貼り付けるなり、スクリプトにするなりしてください 

powershell -Command emacs-daemon

 とかを適当にWindows起動後に実行するようにすれば勝手に動いてくれるからよりデーモンライクだね。ちろちろ画面が出るのがうざいけど。



 ちなみに、「しばらく待機」ってのがやっかいで、早すぎるとウィンドウがとれなくて非表示に失敗するし、かといって待ちすぎるのもあれだし。
 とりあえず、私の環境では2秒じゃだめだけど、5秒だと安定して大丈夫っぽいから5秒待ってる。
 Process.WaitForInputIdleとかでできるかなぁとか思ってた時期が私にもありましたけど、全然無理だった。

 だれかうまい方法ご存じないですかね。誰かWindowsのEmacsでもDaemon化してくれませんかね。


PowerShellのコード

 まぁ、PowerShellというか、ほとんどC#だけど。

$CSCODE=@'
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace EmacsDaemon
{
    public class WindowControl
    {
        [DllImport("user32.dll")]
        public static extern bool SendMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
        [DllImport("user32.dll")]
        public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
        [DllImport("user32.dll")]
        public static extern uint GetWindowThreadProcessId(IntPtr hWnd, ref uint lpdwProcessId);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool EnumWindows([MarshalAs(UnmanagedType.FunctionPtr)] EnumWindowsProc lpEnumFunc, IntPtr lParam);

        public static bool KillWindow(IntPtr hWnd) { return SendMessage(hWnd, 0x0112, 0xf060, 0); }
        public static bool HideWindow(IntPtr hWnd) { return ShowWindow(hWnd, 0); }

        class CloseProcess
        {
            Process process;
            public CloseProcess(Process p) { process = p; }
            public bool method(IntPtr hWnd, IntPtr lParam)
            {
                uint p = 0;
                GetWindowThreadProcessId(hWnd, ref p);
                if (p == process.Id)
                {
                    KillWindow(hWnd);
                }
                return true;
            }
        }
        public static void CloseAllWindow(Process process)
        {
            CloseProcess cp = new CloseProcess(process);
            EnumWindowsProc ewp = new EnumWindowsProc(cp.method);
            IntPtr i = new IntPtr();
            EnumWindows(ewp, i);
        }
    }
}
'@
Add-Type -Language CSharp -TypeDefinition $CSCODE

function Emacs-Daemon(){
  param($dir="",$wait=-1,$workDir=$env:HOME);
  $emacsexe="emacs.exe";
  if($dir -ne ""){
    $emacsexe = $dir +"\emacs.exe";
  }
  if($workDir -eq ""){
    $workDir = ".";
  }
  if($wait -eq -1){
    $wait = 5000;
  }
  $eproc=Start-Process $emacsexe -WindowStyle Hidden -PassThru  -WorkingDirectory $workDir
  [Threading.Thread]::Sleep($wait);#誰かもっといい方法教えて
  [EmacsDaemon.WindowControl]::HideWindow($eproc.MainWindowHandle);
}
function Emacs-Kill(){
  $eprocess = Get-Process "emacs";
  foreach($pr in $eprocess){
    [EmacsDaemon.WindowControl]::CloseAllWindow($pr);
  }
}

 てか、はてな記法ってPowerShellに対応していないんだね…。

KeySnailのKillLineをEmacs風に改良してみた

 f:id:nodamushi:20151019022132p:plain

 KeySnailネタ。moozさんのアイコンかわいいよね。
 というわけで、なんか、Firefoxがバージョンアップしたら.keysnail.jsの「以前にコピーしたテキスト一覧から選択して貼り付け」のletでエラーが出るらしく、久しぶりに.keysnail.jsを編集しました。

すでに修正されてた(´・д・`)

 自分で修正しんたんすけど、もしかしてって見てみたら、keysnail/functions.js at f22a53fac0c693ccb939b5398494be63d7e1e011 · mooz/keysnail · GitHubにもう一ヶ月ぐらい前に更新されてましたよー。
 うん、Firefox自動更新してないんだ。ごめんね。

 というわけで、コピって貼り付け。

    if (!command.kill.ring.length)
        return;
    
    let ct = command.getClipboardText();
    if (!command.kill.ring.length || ct != command.kill.ring[0]) {
        command.pushKillRing(ct);
    }
    
    prompt.selector(
        {
            message: "Paste:",
            collection: command.kill.ring,
            callback: function (i) { if (i >= 0) key.insertText(command.kill.ring[i]); }
        }
    );



Kill Lineがなんか変

 日頃からどーもなんか気にくわないな、とおもっていたKeySnailのkill-line。


 というのもこれ。ここ(↓)で、Ctrl+Kを押すと………
f:id:nodamushi:20151019012224p:plain
f:id:nodamushi:20151019012230p:plain
 うん、問題ないね。

 でも、ここ(↓)でCtrl+Kを押すと………
f:id:nodamushi:20151019012243p:plain
f:id:nodamushi:20151019012247p:plain
 あっるぅぇえ?(´・д・`) なぜか私のパソコンではこんなんになってなまいます。


 私のパソコンでもこう(↓)なってほしいんですよね。

f:id:nodamushi:20151019012252p:plain


 あと、気になるのこれ。
f:id:nodamushi:20151019031717p:plain
f:id:nodamushi:20151019031726p:plain

 論理行で削除するんじゃなくって、テキストエリアの折り返し行までが削除されるの。これ気に入らないの。




 というわけで、ついでだったので、そうなるように修正したのがこちら。

key.setEditKey('C-k', function (ev) {
    if (command.marked(ev))
            command.resetMark(ev);
    let ta = ev.originalTarget;
    let str = ta.value;

    let i = ta.selectionStart;
    for(let e=ta.value.length;i<e;i++){
        let c = str.charAt(i);
        if(c == "\r" || c == "\n" || c=="\u0085"){
            break;
        }
    }
    ta.selectionEnd = i;
    let cstr = ta.value.substring(ta.selectionStart,ta.selectionEnd).trim();
    command.copySelectedText(ta);
    goDoCommand('cmd_delete');
    if(!cstr){
        goDoCommand('cmd_beginLine');
        let pos = ta.selectionStart;
        goDoCommand('cmd_endLine');
        if(pos != ta.value.length){
            goDoCommand('cmd_deleteCharForward');   
        }else{
            goDoCommand('cmd_deleteCharBackward');
        }
    }
}, 'カーソルから先を一行カット (Kill line)');

 Emacsのkill-lineは削除した文字が空(空白のみの文字列含む)だと、改行文字も消すっぽいので、その処理を追加してあります。


 ちなみに、どうもうまく動かない戦犯は"cmd_deleteToEndOfLine"らしく、こいつを使わないようにすることが重要。なんか、このコマンドって条件はよくわからないんだけど、正しく動作しないことがあるっぽいです。
 最初は、if(!str)ブロックの中身も"cmd_deleteToEndOfLine"を呼び出してただけなんだけど、なぜか2行目にカーソルがあるときに、正しく動かないという状況がまれに発生しました。発生条件は全くもって不明。というわけで、上記の形になっています。文字列処理は嫌ので、キャレットの位置変化から最後の行かどうか判定しています。



 あ、なお、これはFirefoxのバグか、私のプライグイン環境との相性のバグなのかのどっちかです。
 KeySnailのバグではありません。





 あと、ついでに、Ctrl+Jで次の行を生成して移動するってのを作っておいた。
 単に改行だけにしたい人は、goDoCommandを削除してください。

key.setEditKey("C-j",function(ev){
    goDoCommand("cmd_endLine");
    key.generateKey(ev.originalTarget,KeyEvent.DOM_VK_RETURN,true);
},"新しい行を作成",true);


 以上です。




 ところで、EdgeでFirefoxのプラグインが動かせるかもという噂ですが、KeySnailがEdgeで動くかなぁ。
 動けばいいなぁ~ヽ( ・∀・)ノ

かゆいところに手が届かないATOKタッチキーボード

 すっかり忘れてたんですが、私Surface Pro3を随分前に買っていました。
 Pro4が出るらしいですね。Pro4は筆圧1024段間検知だとか。めっちゃ欲しい。


 さて、普段IMEにAtokを愛用している私。Surfaceでも利用しようとインストールしたんですが、

 SurfaceのタッチキーボードでのAtokの使いにくいのなんのって

 ダントツで使いにくいです。
 というわけで、愚痴がほぼすべてのレポートを記しておきます。




予測変換が邪魔くさい

普通に入力すると、予測変換が出てきます。

f:id:nodamushi:20151011233517j:plain

 最初はこれすら「うぜぇ」とか思っていたんですが、意外と慣れました。
 慣れると、まぁ、使い勝手が良いときもあるし、お節介が過ぎるときもある半々な感じです。

 が、問題は次。

f:id:nodamushi:20151011234330j:plain

 「予測変換」を確定した後に、何も押していないのに出てくるこれね。
 (ちなみに、デスクトップに増えていっているのは自動キャプチャ画像。)
 これで、空白を開けようとスペースを押そうものならこうなる。

f:id:nodamushi:20151011234458j:plain
 だあああ うぜぇええええ
 


 
 スペースって検索ボックスでは良く打つよね。その度にこうなるわけ。ストレスマッハ。

JustSystemsに聞いてみた

 これを解決する方法をJustSystemsに聞いてみました。返事によると、以下の自動表示の項目を「しない」に設定しろとのこと。

f:id:nodamushi:20151011235318j:plain

 するとこうな………
f:id:nodamushi:20151011235606j:plain

 変わらん。(デスクトップのキャプチャ画像が増えてるので時系列はちゃんと指示通り設定した後である)
 どうも、一回ソフトごと起動し直す必要があるらしい。
 というわけで、もう一回メモ帳を起動し直し、トライ。


f:id:nodamushi:20151011235815j:plain
 なんかよくわからん何もないウィンドウが表示される。うざい。

f:id:nodamushi:20151011235908j:plain
 「よそく」と打っても、予測変換は出てこない。
 違うんだよ。ここの予測変換は出てきてもいいんだよ。害悪ではなかったんだよ。

f:id:nodamushi:20151012000140j:plain
 やりました。予測変換、と打ち込んだ後に、余計な連中が出てきません。
 ですが、ちょっとキャプチャのタイミング上移りませんでしたが………

f:id:nodamushi:20151012000240j:plain
 入力後になんか何もないウィンドウが出てきます。うぜぇ


かゆいところに手が届かないAtok

 最大のストレス源である、変換確定後に余計な予測変換が出てくる機能を抑制することはできました。
 が、出てきてもいい予測変換まで抑制されるし、なんかうざいウィンドウは出てくるし、どうもかゆいところに手が届きません。
 あと、日本語入力用のATOKタッチキーボードが選択不可になったりして使いにくいことこの上ないです。(いったんMicrosoft IMEに変更してから、ATOKにすると選択できたり、よくわからん)

 あぁ、かゆいところに手が届きませんな。

 まぁ、結局Surfaceでは私はMicrosoftを使うことにして、ATOKはアンインストールしたんで、もう関係ないんですけどね。(この記事のためにわざわざインストールし直した)



 バージョンアップする度に余計な機能が追加されて馬鹿になっていくともっぱら評判のATOKさんですが、私の信仰心はいつまで持つのか。今年か来年中に見切りをつけようかと思っています。


 ところで、最近のMicrosoftは結構頑張ってるよね。
 今年度中に行われると噂のMicrosoftのEdgeで本当にFirefox,Chromeの既存拡張機能取り込みが来れば、Firefoxへのかすかに残っていた信仰心は完全に捨てる予定です。
 仮想デスクトップもいいね。MacやLinuxの仮想デスクトップにはまだ負けるけど。アイコンクリック一つで移動できる便利さが足りない。あとはGnuEmacsがWindowsでもdaemon使えるようになればもう何も怖くないね。serverは使えるのになんでdaemonは使えないんだろ?

 あと、環境変数のPathの設定をしやすくするみたい。地味にいい機能。

 Microsoftを信仰することにしましょうか。

JavaFXでConsole作ってみた

 JavaFXってConsole的な物なくね?
 ググっても出てこなくね?


 と、いうわけで、作ってみました、こんなもの。
f:id:nodamushi:20150926211739p:plain


 System.outを今回作ったSimpleConsole.outに変更可能。
 マルチスレッド対応(たぶん)


 最低限の機能はあるんでない?誰かがしっかりした物を作ってくれるまではこれで我慢しましょうぞ。

NodamushiFXControls/SimpleConsole.java at master · nodamushi/NodamushiFXControls · GitHub

NodamushiFXControls/ConsoleTest.java at master · nodamushi/NodamushiFXControls · GitHub

JavaScriptで作ったちょっとしたスクリプトからファイルを保存したい

 JavaScriptはちょこっと何かを作ろうと思うと、一番使いやすいと思っている。なんせ特に何もいらない。エディタとブラウザがあればGUIを持った簡単なスクリプトなんかすぐ作れる。最近は簡単な物ならブラウザ内で全部完結して、エディタすらいらない。
 Excelマクロを習ってGUIの作成を習うよりずっと簡単だと私は思っている。なお、異論は認める。

 が、このJavaScriptは簡単ではあるが、ファイルの保存が出来ない。File APIもChromeしかない。
 私ならJavaScriptで何かして保存したいときはGreasemonkey使ったり、keysnail使ったりするって手段もあるっちゃある。
 でも、あまりプログラミングに詳しくない人が簡単に作って試して保存まで勢いで出来なければ意味が無い。


 だが、そんな方法はない。と、思っていたら意外とHTML5には別の手段で保存する方法があるようだ。
 以下のsaveTextを実行するだけで、とりあえずローカルに保存できる。ただし、ブラウザ設定によっては何も聞かずにDownloadフォルダに勝手に保存しちゃうのが玉にキズだけどね。

function saveText(text, fileName) {
  var a, blob, event;
  if (fileName == null) {
    fileName = "textfile.txt";
  }
  blob = new Blob([text], {
    type: "text/plain"
  });
  if (window.navigator.msSaveBlob != null) {
    window.navigator.msSaveBlob(blob, fileName);
  } else {
    a = document.createElement("a");
    a.href = URL.createObjectURL(blob);
    a.target = "_blank";
    a.download = fileName;
    event = document.createEvent("MouseEvents");
    event.initEvent("click", false, true);
    a.dispatchEvent(event);
  }
};


CoffeeScriptバージョン

saveText = (text,fileName)->
  if !fileName? then fileName = "textfile.txt"
  
  blob = new Blob [text],{type:"text/plain"}
  if window.navigator.msSaveBlob? then window.navigator.msSaveBlob blob,fileName
  else
    a = document.createElement "a"
    a.href = URL.createObjectURL blob
    a.target = "_blank"
    a.download = fileName
    event = document.createEvent "MouseEvents"
    event.initEvent "click",false,true
    a.dispatchEvent event
  return


参考:
JavaScriptだけでファイルの保存機能を実装する - 新人Webエンジニアの記録。
JavaScriptのクリックイベントを発火させる方法 - ゆうなんとかさんの雑記帳的な。yuuxxxx.hatenablog.com





ちょっと根性あるならnode-webkitという選択肢も

 去年ぐらいからちょくちょく耳にすることがあるnode-webkit。要するにHTML5とJavaScriptとそれの実行環境をひとまとめにしてアプリケーションにしましょうというものですかね。
 node.jsの機能が使えるのでファイルの保存とか出来ます

 詳しくはよく知らないけどね!(え 個人的には単体で動くブラウザのプラグインだと思っています。

 liginc.co.jp




まだ根性あるなら好みの関数を持つブラウザ作っちゃおうぜ

 回りくどいことせずに、JavaScriptからダイレクトに保存関数を呼べるブラウザを自作しちゃうってのも一つの手だ(え?


 例えば、Qtとか、JavaFXにはWebEngineがあるから、それを使うと、意外に簡単にJavaScriptで保存することが出来るブラウザを作れる。
 まぁ、確かに、最初のブラウザを作るのはちょい骨折れるけど、一回作ってしまったら、node-webkitの用に毎回アプリケーションにする必要なんて無いぞ!これは楽だ!




 ………いや、やっぱそれぐらいならJavaやらC#やらExcel VBAを覚えた方が絶対早いよ




 最後に載せたJavaFXのWebEngineを使った例に次のHTMLファイルを開かせると、ファイルの保存画面が開いてテキストファイルが保存される。





 さぁ、私の匙は投げられた。好きな方法を選ぶがよい。


test用HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script>
Files.saveText("内容");
</script>
</body>
</html>


自作ブラウザ

import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.*;
import java.util.*;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.web.*;
import javafx.stage.*;
import javafx.stage.FileChooser.ExtensionFilter;
import netscape.javascript.JSObject;

public class Browser extends Application{

  public static void main(final String[] args){
    System.setProperty("prism.lcdtext", "false");
    launch(args);
  }
  private WebView view;
  private TextField url;

  @Override
  public void start(final Stage stage) throws Exception{
    final WebView v = view=new WebView();
    JSFiles.install(v);

    final TextField url = this.url = new TextField();
    final Button open = new Button("Open");
    url.setOnAction(this::openURL);
    open.setOnAction(this::openURL);

    final BorderPane p = new BorderPane(v);
    HBox.setHgrow(url, Priority.ALWAYS);
    p.setTop(new HBox(url,open));
    p.setPrefSize(800, 600);

    stage.setScene(new Scene(p));
    stage.show();
  }


  public void openURL(final ActionEvent e){
    final String text = url.getText().trim();
    if(text == null) {
      return;
    }

    String url;
    try{
      url = Paths.get(text).toUri().toURL().toString();
    }catch(final Exception e1){
      url = text;
    }


    final WebEngine engine = view.getEngine();
    if(url.equals(engine.getLocation())){
      engine.reload();
    }else{
      engine.load(url);
    }

  }



  public static class JSFiles{

    public static void install(final WebView v){
      final JSFiles jsf = new JSFiles(v);
      final WebEngine engine = v.getEngine();
      final JSObject window = (JSObject)engine.executeScript("window");
      window.setMember("JavaFiles", jsf);

      final JSObject eval = (JSObject)engine.executeScript(
          "var Files = {};"
          + "Files.saveText = function(str,charset){"
          + "JavaFiles.__saveText__(str,charset);"
          + "};");
      System.out.println(eval);
    }

    private WebView view;

    public JSFiles(final WebView view){
      this.view = view;
    }


    public boolean __saveText__(final String str,final String charset){
      final FileChooser chooser = new FileChooser();
      chooser.getExtensionFilters().addAll(TEXTFILTER,ALL);
      final Window w = view.getScene().getWindow();
      final File file = chooser.showSaveDialog(w);

      if(file!=null){

        Charset c =null;
        try{
          if(charset != null){
            c= Charset.forName(charset);
          }
        }catch(final Exception e){}
        if(c == null) {
          c = Charset.defaultCharset();
        }

        @SuppressWarnings("resource")
        final Scanner scan = new Scanner(str);


        final Iterator<CharSequence> i = new Iterator<CharSequence>(){
          @Override
          public CharSequence next(){
            return scan.nextLine();
          }
          @Override
          public boolean hasNext(){
            return scan.hasNextLine();
          }
        };

        try {
          Files.write(file.toPath(),()->i, c);
          return true;
        } catch (final IOException e) {
          e.printStackTrace();
          return false;
        }
      }else{
        return false;
      }
    }
    private static ExtensionFilter TEXTFILTER=new ExtensionFilter("*.txt", "*.txt");
    private static ExtensionFilter ALL=new ExtensionFilter("*", "*");
  }

}

ファイル選択のControl作った

 ウィーっす。
 さて、昨日は入力候補が出るTextFieldを作ってみました。nodamushi.hatenablog.com

 で、これを利用してファイル選択用のConrolを作ってみました。
 こんな感じに動作します。
f:id:nodamushi:20150814173654p:plain

 うん、使いやすい。私はやっぱりマウスでクリックするより、キーボードで打ち込んだ方が楽だからねー。
 昨日と同じくソースコードはこちら。NodamushiFXControls
 クラス名はnodamushi.jfx.path.PathChooserが今回のControlです。

 FileChooserとかDialogChooserとか自分で操作しなくても勝手にやってくれます。
 もともとこれを作りたいが為に、CompletionTextFieldを作りました。こう言うのって、ありそうで、検索してみても出てこなかったんだよね。
 (既にあったらすみません)






 しかし、まー、だいたい、目的の動作はするんだけど、まだ微妙にバグってたりはします。
 時々ListViewが上手くレンダリングされなかったり、ScrollBarが表示されているかどうかの判定にミスってるみたいなんだよねぇ。
 f:id:nodamushi:20150814175004p:plain
 f:id:nodamushi:20150814175006p:plain

 どっちも常に起こるわけじゃないんだよね。その発生条件もよく分かってない。
 直し方が分からんし、根性が切れたので、ここでいったん公開して、記事にしました。