PyGTK実験 gtk.Entryに入力フィルター機能を追加


さて、以前にもgtk.Entryについて書いたことがあるのですが、
その時はgtk.Entryの作り方と配置の仕方といったごく簡単な内容でした。


今回は、gtk.Entryに「数値しか入力できない」などのフィルター機能を付加してみようと
思います。


以下のソースコードは本当は1つのファイルなのですが、gtk.Entryのサブクラスと
フィルタ用のクラスが一緒になっているので、途中で分けることにしました。


まず、gtk.Entryのサブクラスとして、FilterEntryというものを作りました。
そして、テキストが入力された際にフィルタ用のオブジェクトで判定を行い、
「入力可」と判定された場合のみその入力を受け付けます。
入力を取り消す場合、stop_emission('insert-text')を使用する部分がポイントです。

# -*- coding: utf-8 -*-

import sys
import types

# GTK関連モジュールのインポート
import pygtk
if sys.platform != 'win32':
    pygtk.require('2.0')
import gtk

#
# Entry
#
class FilterEntry(gtk.Entry):
    '''
    gtk.Entryに入力フィルター機能を不可するためのクラス。

    文字列が入力されると、指定されたフィルタオブジェクトで判定を
    行い、入力可と判定された場合にのみエントリーにテキストを挿入する。
    
    フィルタインスタンスは本クラス生成時に渡すことができる。(複数指定可能)
    '''
    def __init__(self, maxLen=0, *filterIns):
        '''
        初期可処理。

        maxLen:
            入力文字数の上限。
        filterIns:
            フィルタ判定オブジェクトのインスタンス。複数指定可。
        '''
        gtk.Entry.__init__(self, maxLen)
        
        self.filterList = list(filterIns)
        self.connect('insert-text', self.on_insert_text)

    def add_filters(self, *ins):
        '''
        フィルタ判定オブジェクトのインスタンスを登録する。

        ins:
            フィルタ判定オブジェクトのインスタンス。複数指定可。            
        '''
        self.filterList += list(ins)
        
    def clear_filter(self):
        '''
        フィルタ判定オブジェクトを全て末梢する。
        '''
        self.filterObjs = []

    #
    # ハンドラ
    #
    def on_insert_text(self, editable, text, len, pos, data=None):
        '''
        テキストが入力された場合のハンドラ。
        入力されたテキストをフィルタオブジェクトのメソッドを使用して
        判定し、入力不可と判定された場合には、入力を取り消す。

        editable:
            gtk.Editable
        text:
            入力されたテキスト
        len:
            入力されたテキストの文字数
        pos:
            位置(この情報はpygtkからは利用できない)
        '''
        for filt in self.filterList:
            if not filt.isAvailable(editable, text, len):
                editable.stop_emission('insert-text')
                break


さて、次は入力フィルタようのクラスです。
「フィルタ用のクラス」と書くと、そのクラス自身が余分な文字列を削除してくれそうですが、
今回作成したものはそうではありません。


今回作成したフィルタ用のクラスには、文字列が有効かどうかを判定するだけです。(笑
判定結果をもとに、文字列の入力を取り消したりするのは、Entry側の仕事ということに
しました。


以下のソースは、フィルタを実現するためのベースクラス(FilterBase)と、
サンプルとして、数値しか入力できないようにするフィルタ(NumericFilter)も載せました。

class FilterBase:
    '''
    フィルタオブジェクトのベースクラス。
    サブクラスは_validate()メソッドをオーバーライドすることで
    判定処理をカスタマイズ可能。

    利用する側はisAvailable()を呼び出すことで入力可/不可を
    判定する。
    '''
    def __init__(self):
        '''
        初期化処理。特になにも行わない
        '''
        pass

    def isAvailable(self, editable, text, textLen):
        '''
        入力可能なテキストかどうかを判定する。

        editable:
            gtk.Editableオブジェクト
        text:
            判定対象の文字列
        textLen:
            判定対象文字列の長さ
        
        RETURN:
            True --> 入力可能
            False--> 入力不可
        '''
        if not editable or not text or not textLen:
            return True
        return self._validate(editable, text, textLen)

    def _validate(self, editable, text, textLen):
        '''
        サブクラスでオーバーライト。
        フィルタ処理本体。フィルタはentryで渡されたgtk.Entry
        の入力内容を直接編集する。

        editable:
            gtk.Editableオブジェクト
        text:
            判定対象の文字列
        textLen:
            判定対象文字列の長さ

        RETURN:
            True --> フィルター不要
            False--> フィルター必要
        '''
        return False


class NumericFilter(FilterBase):
    '''
    数値以外をフィルタするためのフィルタ
    '''
    def __init__(self, allowNegative=False):
        '''
        初期化処理。

        allowNegative:
            負の数値も許すかどうか。(True/False)
        '''        
        self.allowNegative = allowNegative
        FilterBase.__init__(self)        

    def _validate(self, editable, text, textLen):
        '''
        文字列が数文字列かどうかを判定する。

        editable:
            gtk.Editableオブジェクト
        text:
            判定対象の文字列
        textLen:
            判定対象文字列の長さ

        RETURN:
            True --> 数値のみだった
            False--> 数値以外が含まれていた
        '''
        flag = True
        # 負の数値を許す場合、先頭の文字に限って'-'の入力を許す。        
        if editable.get_position() == 0:
            if self.allowNegative and text[0] == '-':
                return flag
            
        # 数値に変換してみてエラーかどうかを判定する。
        try:
            test = int(text)
        except ValueError:
            flag = False
        return flag

上記のソースをfilterEntry.pyとして保存したとすると、
実際の使いかたとしては、以下のようになります。

# -*- coding: utf-8 -*-

import sys
import types

# GTK関連モジュールのインポート
import pygtk
if sys.platform != 'win32':
    pygtk.require('2.0')
import gtk

from filterEntry import FilterEntry, NumericFilter

# いろいろと省略。。

vbox = gtk.VBox()
# 3桁までの数値が入力可能なEntry
entry = FilterEntry(3, NumericFilter())
vbox.pack_start(entry)

# いろいろと省略。。