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

プログラムdeタマゴ

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

JavaFXで柔軟なキーバインドを作ろうとしたけど挫折した話

動機と目的

 みなさんはキーボード何使ってますか?私は東プレのRealForceを………という話ではなくて、JISキーボードですか?USキーボードですか? US!US!なギーク(笑)には関係ない話ですが、私はJISキーボードです。前の研究室はUSキーボードだったので両方使えますが、JISのがしっくりきます。


 で、JISは世界の標準ではないので、色々問題起こります。


 「Ctrl+@」

 このキー操作を定義しようとしたときに問題が発生しました。我々JIS民は@って@を押せば出るんですよ。でも世界民はShift+2なんですよね。で、世界民はJIS民が@と呼ぶキーは[と呼ぶんですよね。


 なるほど、じゃぁ、JavaFXも@は[と呼ぶから困ると私は言いたいのか。残念ながらもっと面倒で、何故か`と判定されました。KeyCodeにATって定義されてるくせに…


 これだと、ユーザーが柔軟にキーバインドを定義するようなアプリケーション作れねーじゃん、ということで、emacsのキーバインドのように柔軟な表現で表せるようにしようと思ったわけです。柔軟な、というのは例えばJISで「C-S-@」または「C-`」は「Control+Shift+@」であり、USなら「C-S-@」は「Control+Shift+2」である等、柔軟な対応がしたいということです。


断念した実装

 キー入力「Ctrl+@」をKeyCodeという列挙型で判断しようとすると、Ctrl+バッククォートになる為、KeyCodeのみに頼ることは出来ない。しかし、KeyEventのgetText()を利用すると、このKeyCodeはバッククォートでも「@」という文字列が返ってくる。これとKeyCodeの二つ両方利用して何とかしようとした。


 これでかなり柔軟に対応するところまでは出来ました。しかし、「Ctrl+Shift+@」というショートカットを定義することが現状できないという結論に至りました。Shift+@によりgetText()の値は「`」になり、@かどうか判別することが出来ない為です。

 キーボードがJISなのか、USなのか(はたまた別の言語なのか)判断して処理を分けれればいいのですが、私にはやり方分からないし、っていうか分かっても全部やるとか面倒くさくてあり得ないし。


 というわけで、私のもくろみは散ってしまったとさ。



 でもまぁ、使い方を気をつければ、JavaFXのKeyCombinationよりは柔軟な物を作ることは出来たんじゃないかな。↓

package nodamushi.event;

import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;

public class MyKeyCombination implements Cloneable{
    
    //修飾キーの値
    private static final int CONTROL=1,ALT = 1<<1,META = 1 << 2,SHIFT = 1 << 3;
    //よく分からんキーのコード
    private static final int UndefinedKeyCode = -10000;
    
    
    private static int getModifiers(boolean control,boolean alt,boolean meta,boolean shift){
        return (control?CONTROL:0) | (alt ? ALT :0) | (meta?META:0) | (shift? SHIFT:0);
    }
    
    private int modifiers;
    private int keycode=UndefinedKeyCode;
    
    
    
    public boolean match(MyKeyCombination kc){
        return modifiers==kc.modifiers&&keycode==kc.keycode;
    }
    
    @Override
    public boolean equals(Object obj){
        return (obj==this)?true:(obj instanceof MyKeyCombination)? match((MyKeyCombination)obj):false;
    }
    
    //for clone
    private MyKeyCombination(int modifiers ,int keycode){
        this.modifiers=modifiers;
        this.keycode=keycode;
    }
    
    public MyKeyCombination(boolean control,boolean alt,boolean meta,boolean shift,String key){
        if(key.length()==1){
            int ch = key.charAt(0);
            keycode = ch;
            if('A'<=ch && ch<='Z'){
                shift = true;
                keycode = ch+'a'-'A';
            }
            modifiers = getModifiers(control, alt, meta, shift);
        }else{
            modifiers = getModifiers(control, alt, meta, shift);
            switch(key.toLowerCase()){
                case "enter":
                    keycode = 13;break;
                case "tab":
                    keycode = 9;break;
                case "space":
                case "sp":
                    keycode = 32;break;
                case "pageup":
                case "pgup":
                case "pup":
                    keycode = -33; break;
                case "pagedown":
                case "pgdown":
                case "pdown":
                    keycode = -34; break;
                case "end":
                    keycode = -35; break;
                case "home":
                    keycode = -36;break;
                case "left":
                    keycode = -37;break;
                case "up":
                    keycode = -38;break;
                case "right":
                    keycode = -39;break;
                case "down":
                    keycode = -40; break;
                case "f1":
                    keycode=-112;break;
                case "f2":
                    keycode=-113;break;
                case "f3":
                    keycode=-114;break;
                case "f4":
                    keycode=-115;break;
                case "f5":
                    keycode=-116;break;
                case "f6":
                    keycode=-117;break;
                case "f7":
                    keycode=-118; break;
                case "f8":
                    keycode=-119;break;
                case "f9":
                    keycode=-120;break;
                case "f10":
                    keycode=-121;break;
                case "f11":
                    keycode=-122;break;
                case "f12":
                    keycode=-123;break;
                case "f13":
                    keycode=-124;break;
                case "f14":
                    keycode=-125;break;
                case "f15":
                    keycode=-126;break;
                case "f16":
                    keycode=-127;break;
                case "f17":
                    keycode=-128; break;
                case "f18":
                    keycode=-129;break;
                case "f19":
                    keycode=-130;break;
                case "f20":
                    keycode=-131;break;
                case "f21":
                    keycode=-132;break;
                case "f22":
                    keycode=-133;break;
                case "f23":
                    keycode=-134;break;
                case "f24":
                    keycode=-135;break;
                default:
                    keycode = UndefinedKeyCode;
                    modifiers=0;
                    break;
            }
        }
    }
    
    public boolean match(KeyEvent event){
        KeyCode code = event.getCode();
        String text = event.getText();
        boolean isC=event.isControlDown();
        boolean isA=event.isAltDown();
        boolean isS=event.isShiftDown();
        boolean isM=event.isMetaDown();
        int modifiers,keycode=UndefinedKeyCode;

        if(code.isModifierKey() || code == KeyCode.UNDEFINED){//shiftなど
            return false;//常にfalse
        }
        
        if(code.isLetterKey()){//a-z
            modifiers = getModifiers(isC, isA, isM, isS);
            keycode = code.ordinal()-KeyCode.A.ordinal()+'a';
        }else if(code.isFunctionKey()){//F1,F2,F3....,F24
            keycode = -112 - (code.ordinal()-KeyCode.F1.ordinal());
            modifiers = getModifiers(isC, isA, isM, isS);
        }else if(code.isNavigationKey()){//homeや矢印など
            if(code.isArrowKey() && code.isKeypadKey()){//キーパッドの矢印
                keycode = -33 - (code.ordinal()-KeyCode.KP_UP.ordinal());
            }else{//それ以外
                keycode = -33 - (code.ordinal()-KeyCode.PAGE_UP.ordinal());
            }
            modifiers = getModifiers(isC, isA, isM, isS);
        }else if(code.isWhitespaceKey()){//enter,tab,space
            switch(code){
                case ENTER:
                    keycode = 13;break;
                case TAB:
                    keycode = 9;break;
                case SPACE:
                    keycode = 32;break;
            }
            modifiers = getModifiers(isC, isA, isM, isS);
        }else if(code.isMediaKey()){//音量上げ下げのボタンとからしい
            //よく分からんから現状放棄
            return false;
        }else if(code.isDigitKey()){//数値
            if(code.isKeypadKey()){
                keycode = '0'+(code.ordinal()-KeyCode.NUMPAD0.ordinal());
            }else{
                keycode = '0'+code.ordinal()-KeyCode.DIGIT0.ordinal();
            }
            modifiers = getModifiers(isC, isA, isM, isS);
            if(keycode==this.keycode && modifiers==this.modifiers)
                return true;
            
            //↓例外処理。例えばShift+1はjisでは「!」など数字ではない。
            if(text.length()==1 && keycode!=text.charAt(0)){
                isS=false;
                keycode = text.charAt(0);
            }
            modifiers = getModifiers(isC, isA, isM, isS);
            return keycode==this.keycode && (modifiers|SHIFT)==(this.modifiers|SHIFT);
        }else{//わからん
            
            //TODO ここどーしよう。
            //現状の問題
            //たとえば、jis配列でCtrl+Shift+@を判定したい場合
            //KeyCodeはback_quoteが返ってくるし、textは`になってしまっている
            //要するに、Shift+@は取得できない
            
            
            
            //(↑が実装できた上での話)
            //↓マッチしなかった場合、Shiftにより文字が変わっていることを考慮する
            int tlength = text.length();
            if(tlength == 1){
                char ch = text.charAt(0);
                keycode = (int)ch;
                modifiers = getModifiers(isC, isA, isM, isS);
                return keycode==this.keycode && (modifiers|SHIFT)==(this.modifiers|SHIFT);
            }else{
                //それでも判別できない物は扱わない。
                return false;
            }
        }
        
        
        return keycode==this.keycode && modifiers==this.modifiers;
        
        
    }
    
    public MyKeyCombination clone(){
        return new MyKeyCombination(modifiers, keycode);
    }
}