バリデータと入力補完機能

バージョン0.9から、バリデータが作れるようになりました。バリデータは、マップの内容が一定のルールに従っているかチェックする機能です。オプションとして、入力補完機能も作ることができます。厳密にルールに従ったマップを描きたいとき、ソフトウェアによって補助することができます。

バリデータも、アプリの一種です。他のAPIと組み合わせて応用できます。ルールも、プログラムで記述できるものなら何でもかまいません。

マップ・オブジェクトには型を設定することができます。バリデータは、この型を参照してマップの内容を検証します。

マップ・オブジェクトのコンテキストメニューか、メニューの「編集」から、「オブジェクトのプロパティ」を選び、プロパティ編集ダイアログを表示させ、「オブジェクトの情報」タブを選ぶと、そこで型を選択できます。

型は、バリデータによって与えられているリストの中からしか選択できません。また、型の入力はバリデータが有効になっているときだけ可能になります。


サンプル

importPackage(Packages.jp.carabiner.inkpod.pi);
importPackage(Packages.jp.carabiner.inkpod.pi.model);
importPackage(Packages.jp.carabiner.inkpod.pi.model.event);
importPackage(Packages.jp.carabiner.inkpod.pi.selector);

/**
 * initialize
 *
 */
function init()
{
    // バリデータの追加
    validator = new ModelValidator() {
        // バリデータの識別子を返す
        getId : function() {
            return "jp.carabiner.inkpod.apps.SampleValidtor"
        },
        // バリデータのラベルを返す
        getLabel : function() {
            return "Sample Validator"
        },
        // ノードの型の一覧を返す
        getNodeTypes : function() {
            return ["Document", "Section"];
        },
        // 関連の型の一覧を返す
        getRelationTypes : function() {
            return ["Section"];
        },
        // バリデーション処理
        validate : validate,
        // 入力補完処理
        complete : complete,
    };
    inkpod.addModelValidator(validator);
}

/**
 * dispose
 *
 */
function dispose()
{
}

/**
 * plugin description
 *
 */
plugin =
{
    init : init,
    dispose : dispose,
}

/**
 *  validate
 */
function validate(request, result)
{
    var model = request.model;
    
    var s = new Selector(model.flatObjectList);

    // セレクタを使って、Section型のノードを持たない、Document型のオブジェクトを探します。
    // Document型のノードを列挙します。
    s.select(new IsType("Document", Condition.Target.SELF))                   
        // Section型の関連でつながるSection型のノードの数が 0 であるノードを選びます。
    .select(new HasNextNode("Section", "Section", 0, Condition.Target.SELF)) 
        // 見つかった全てのノードに対して、エラーメッセージを表示します。
     .each(function(index, node) {
         result.addResult("DocumentにはSectionが必要です", node.object);
         return true;
     });
}

/**
 * complete
 */
function complete(request)
{
    var model = request.model;
    var event = request.triggerEvent;
    
    var obj = request.object;
    if(!(event.type == MapEvent.OBJECT_APPENDED ||
         (event.type == MapEvent.OBJECT_PROPERTY_CHANGED && obj && obj.startObject && obj.endObject))) {
        // 入力補完のトリガとなった編集が、オブジェクトの追加、
        // または関連線の接続である可能性がない場合は、ここで終了
        request.completed();
        return;
    }
    
    if(!obj) {
        // 特定のオブジェクトに結びついた編集イベントではなかった場合は、終了
        request.completed();
        return;
    }
    
    if(obj instanceof PRelationObject) {
        if(obj.startObject && obj.endObject) {
            // 関連オブジェクトで、DocumentからSection、またはSectionからSectionの場合に関連の型を設定する
            var stype = obj.startObject.type;
            var etype = obj.endObject.type;
            if(stype == "Document" && etype == "Section") {
                obj.type = "Section";
            } else if(stype == "Section" && etype == "Section") {
                obj.type = "Section";
            }
        }
    } else if(obj instanceof PNodeObject) {
        // ノードオブジェクトの場合で、Document型のノードからつながっていた場合は、Section型に設定する
        var cnt = new Selector(obj.node).select(
            new HasPreviousNode(null, "Document", Condition.Target.SELF)).getCount();
        if(cnt > 0) {
            obj.type = "Section";
        } else {
            // Section型のノードからつながっていた場合は、Section型に設定する
            cnt = new Selector(obj.node).select(
                new HasPreviousNode(null, "Section", Condition.Target.SELF)).getCount();
            if(cnt > 0) {
                obj.type = "Section";
            } else {
                // どの既知の型のオブジェクトともつながっていたい場合
                // ここで、ポップアップメニューを出して、型を選択させることもできます。
            }
        }
    }
    // 入力補完処理の終了
    request.completed();
}

バリデータの作成は、jp.carabiner.inkpod.pi.model.ModelValidtorインタフェースを実装することで行います。作成したModelValidatorオブジェクトを、jp.carabiner.inkpod.pi.PInkpod#addModelValidtor() メソッドで登録します。

バリデータは、識別子、ユーザーインタフェース上の名前、ノードの型の一覧、関連の型の一覧を返す必要があります。バリデータを登録すると、Inkpodのメニューの「編集」-「バリデータの選択」で、選べるようになります。


バリデータは、ここでチェックを入れたもののみが有効になります。1度に有効にできるバリデータは1つのマップで1つです。選択されたバリデータは、マップファイルに記憶されます。

ユーザーの編集をきっかけにして、ModelValidatorのvalidateメソッドが呼び出されます。このメソッドでバリデーションを行います。

validateの第1引数は、jp.carabiner.inkpod.pi.model.ValidationRequestオブジェクトで、バリデーションの対象のモデルを得ることができます。

第2引数は、jp.carabiner.inkpod.pi.model.ValidationResult オブジェクトで、このオブジェクトを通して、バリデーションの結果、問題があったときのメッセージを表示します。

メッセージは、以下のように表示されます。


validateメソッドと同様に、ModelValidtorのcompleteメソッドが呼び出されます。ここで、入力補完処理を行います。

第1引数は、jp.carabiner.inkpod.pi.model.CompletionRequest オブジェクトです。このオブジェクトから、入力補完の対象となるモデル、オブジェクトを取得します。

「入力補完が確定した」、「キャンセルされた」、「エラーになった」、「入力補完の必要がない」等のすべての場合において、入力補完処理を終えたら、必ず、request.completed() を呼ぶようにしてください。requst.completed() がいつまでも呼ばれないと、Inkpodが入力補完の完了を認識できず、それ以降の編集操作に支障を来します。

このサンプルには含まれませんが、入力補完処理では入力内容を選択肢からユーザーに選ばせたい場合もあります。その場合は、Swingのポップアップメニュー等を使って、ユーザーインタフェースを作りユーザーに問い合わせる事もできます。ただし、この場合は、complete メソッドを、request.complete() を呼ばずに終了してください。

Inkpodは request.completed() が呼ばれることで、入力補完処理が完了したことを検知して次の処理に進みます。ユーザーの選択が終わるまで、それを保留するためです。しかし、ポップアップメニュー等が閉じる時には、必ずrequest.complete()を呼ぶようにしてください。

関連するクラス/インタフェース