pythonでキーバインド管理クラス
現在制作中のオリジナルエディタで使用しているキーバインド管理用クラス、だいたいまとまってきたので、
ご紹介します。
以下のクラス(クラス名はちょっと変かもしれませんけど。。)は、コンストラクタが呼び出された時に
引数bindDictで渡された情報を内部用の情報に展開して保持します。
簡単な例をあげると、
「control + aのキーの入力があったら、funcを呼び出す」
といった設定を保持しています。
このクラスから情報を取り出すためには、入力されたキーの情報を指定してget_key_handler()関数を
呼び出します。
たとえば、
handled, func = manager.get_key_handler('
a')とか handled, func = manager.get_key_handler('
a')とか
といった感じで、キーの組み合わせを表す文字列を渡して、それに対応する関数を取得します。
詳しくは下記の関数コメントを参照してください。
あと、このクラスは複合キーにも対応させてあります。
どういうことかというと、emacsのようにC-x, C-cといった感じで複数のキーが押された場合に実行する関数も登録できます。
get_key_handler()関数は1度の呼び出しにつき、キーの組み合わせを表す文字列を1つしか指定できません。
その場合、例えば次のような場合には、
control + x → control + cの順でキーが押された場合に実行される関数を取得する
このようにします。
handled, func = manager.get_key_handler('
c')
handled, func = manager.get_key_handler('x')
上記のように呼び出すと、戻り値はそれぞれ
(True, None)
(True, function)
となります。
つまり、キーが入力されるたびに、それらを文字列に変換してget_key_handler()を呼び出し、
関数が取得できた時点でそれを呼び出せばいいわけです。
# -*- coding: utf-8 -*- import types # # class KeymapMgr: ''' キーバインドを管理するマネージャクラス。 このクラスでは、キーの組み合わせと、ハンドラ関数を管理する。 情報は以下の形式の辞書として管理される。 辞書: (1)単一キーの場合: キー: キーの組み合わせを表すタプル。 (Controlキーフラグ, Mod1キーフラグ, Mod2キーフラグ, Mod3キーフラグ, Mod4キーフラグ, Mod5キーフラグ, キー文字列) 値 : ハンドラ関数 (2)複合キーの場合: 入れ子の辞書となる。 例)「<control>a + <control>b --> functionを実行」の場合 {"<control>"a: {"<control>b": function}} キー: キーの組み合わせを表すタプル。(単一キーの場合と同じ) 複合キー用の関数に対応するため、get_key_handler()が呼び出された際、 関数の取得に至らなかった場合には、self.pendingに部分的にマッチングした 辞書が記憶され、次にget_key_handler()が呼び出された時には、self.pendingの 辞書がマッチング対象の辞書となる。 self.pendingは、入力されたキーのマッチングが終わった際(関数が見付かった、 見付からなかったにかかわらず)にクリアされる。 ''' MOD_KEY_CONTROL = '<control>' MOD_KEY_MOD1 = '<mod1>' MOD_KEY_MOD2 = '<mod2>' MOD_KEY_MOD3 = '<mod3>' MOD_KEY_MOD4 = '<mod4>' MOD_KEY_MOD5 = '<mod5>' def __init__(self, bindDict): ''' 初期化 ''' self.keyMap = {} self.pending = None if bindDict == None: return for wName, keyDict in bindDict.items(): self.keyMap[wName] = {} for keyDef, funcInfo in keyDict.items(): defType = type(keyDef) # キー定義部が文字列の場合の処理 if defType == types.StringType: bindKey = self.__str_to_bindkey(keyDef) if bindKey == None: continue self.keyMap[wName][bindKey] = funcInfo # キー定義部がシーケンスの場合の処理(複号キー対応) # 複合キーの場合には、シーケンスとして指定されることを # 期待している # 例えば('<control>a', '<control>b') elif defType == types.TupleType or defType == types.ListType: parent = self.keyMap[wName] lastIdx = len(keyDef) - 1 # 入れ子の辞書を作成する for idx, kd in enumerate(keyDef): bindKey = self.__str_to_bindkey(kd) if bindKey == None: continue if idx == lastIdx: parent[bindKey] = funcInfo else: if bindKey not in parent: parent[bindKey] = {} parent = parent[bindKey] def get_key_handler(self, wName, keyStr): ''' キーハンドラ(関数)を取得 複合キー用関数(複数のキーが入力されてはじめて実行される関数) が登録されている場合には、入力が確定するまでは戻り値として (True, None)つまり、「キーが登録されているが、関数はない」という 戻り値を返す。入力が確定すると(True, func)を返す。 wName: ウィジェット名文字列 keyStr: キー文字列 例)"<control><shift><alt>a"など 下記の指定に関しては同じ結果を取得する。 ・"<control><mod1>a" ・"<mod1><control>a" つまり、モディファイアキーの順序に影響されない。 RETURN: タプル(キー入力がハンドリングされたかどうかのフラグ, キーに割り当てられている関数) ハンドリングされたかどうかのフラグ True -> 該当するキーが登録されている False-> 該当するキーが登録されていない キー文字列に割り当てられている関数があればそれを返す。 そうでない場合にはNone。 ''' if self.keyMap == None: return False, None if wName == None or len(wName) == 0: return False, None if keyStr == None or len(keyStr) == 0: return False, None # マッチング途中の辞書がある場合には、それをマッチング # 対象に。そうでない場合には、自身が保持しているトップレベル # の辞書をマッチング対象にする。 keyDict = self.pending if keyDict == None: if wName not in self.keyMap: self.pending = None return False, None keyDict = self.keyMap[wName] # キー文字列を内部用のマッチングキーに変換 mapKey = self.__str_to_bindkey(keyStr) if mapKey not in keyDict: self.pending = None return False, None dictVal = keyDict[mapKey] if type(dictVal) == types.DictType: # マッチしたキーの値が辞書だった場合、複合キー用の設定。 # マッチング途中の辞書として覚えておく。 self.pending = dictVal return True, None else: # マッチング完了。 # 処理関数を返す self.pending = None return True, dictVal[0] def __str_to_bindkey(self, keyStr): ''' キー文字列を内部保持用の辞書キーへ変換する。 keyStr: キー文字列 例)"<control><mod1>a"など RETURN: タプル。 (Controlキーフラグ, Mod1キーフラグ, ..., Mod5キーフラグ, キー文字列) keyStr中に<Control>, <Mod1><Mod2><Mod3><Mod4><Mod5>が含まれる場合、 該当する要素にTrueが設定される。 ''' if keyStr == None or len(keyStr) == 0: return # mapkeyはモディファイアキー用フラグリスト # [control, Mod1, Mod2, Mod3, Mod4, Mod5]のならびになっており、 # 該当するモディファイアキーが押されている要素がTrueに設定される mapKey = [False, False, False, False, False, False] modStrs = [self.MOD_KEY_CONTROL, self.MOD_KEY_MOD1, self.MOD_KEY_MOD2, self.MOD_KEY_MOD3, self.MOD_KEY_MOD4, self.MOD_KEY_MOD5] # フラグを設定 for idx, mod in enumerate(modStrs): if mod in keyStr: mapKey[idx] = True keyStr = keyStr.replace(mod, '') # フラグリストの末尾にモディファイア以外のキーコードを追加し、 # タプルとして返す。 mapKey.append(keyStr) return tuple(mapKey) # # if __name__ == '__main__': def on_control_a(): print 'on_control_a' def on_control_shift_a(): print 'on_control_shift_a' def on_control_shift_alt_a(): print 'on_control_shift_alt_a' def on_x_c(): print 'x_c' def on_x_k(): print 'x_k' keyconfigs = {'aaaa': {'<control>a': (on_control_a, None), '<control>A': (on_control_shift_a, None), ('<control>x', '<control>c'):(on_x_c, None), ('<control>x', '<control>k'): (on_x_k, None),}} mgr = KeymapMgr(keyconfigs) print mgr.keyMap print mgr.get_key_handler('aaaa', '<control>x') print mgr.get_key_handler('aaaa', '<control>c') print mgr.get_key_handler('aaaa', '<control>x') print mgr.get_key_handler('aaaa', '<control>k') print mgr.get_key_handler('aaaa', '<control>a') print mgr.get_key_handler('aaaa', '<control>A') print mgr.get_key_handler('aaaa', '<control><alt>A') print mgr.get_key_handler('aaaa', '<alt><control>A')