« 2012年3月 | トップページ | 2012年5月 »

2012年4月

2012年4月29日 (日)

テキスト作成 -2-

FlatTable のテキスト(コメント)作成機能で未実装だったものを実装し、テキスト・メニューの追加・修正もしました。今回実装したのは、テキストの移動、テキスト枠のリサイズおよび適正化、そして台の回転への対応です。自動折り返し機能は未実装のままです。

テキストの移動は、そのメニュー項目を選択した後に仮想的マウスパッド(exPad)の上でドラッグ(ボタンを押したままでのマウス移動をドラッグと呼ぶことにします)することで実行さるようになっています。しかし、ドラッグという操作は球の移動でも使われます。したがって、ドラッグという操作に対して、その応答を文脈に応じて切り分ける必要が有ります。FlatTable では、その切り分けは操作モードを表す変数 opMode で制御することにしました。JavaScript はオブジェクト指向と言われているようなので、そういう方向で行った方がカッコ良いのかも知れませんが、私はオブジェクト指向が苦手なので、単純に変数での制御にしました。

opMode は5つのビットを使用し、それらのビットには次のような意味を持たせています。

ビットクリック時の動作ドラッグ時の動作
bit4テキストの表示
bit3テキスト枠リサイズ
bit2テキストの移動
bit1経路の生成 /
ゴーストの生成
球の移動
bit0球の移動

これらのビットは上位ほど優先順位が高くなっています。例えば、bit0 と bit2 が立っている時ドラッグすると、テキストの方が優先的に移動します。ビットの on/off はコードのあちこちに散らばっていて、一言では説明できません。どんどんスパゲッティになって来ています。

「球を移動するつもりでドラッグしたらテキストが移動した」というような間違いを起こすことが考えられます。そこで、その間違いを減らすために、テキスト枠のリサイズやテキストの移動に関するビットが立っていると、メニューのその項目が赤文字になってその操作モードであることが分かるようにしました。

配置図例

2012年4月23日 (月)

テキスト作成

FlatTable にテキスト(コメント)作成機能を追加しました。ただし、まだ予定の機能の一部だけです。テキストは、コメントを記述したり、台の位置に記号を振るのに使用します。

配置図例

テキストを作成するには、球をクリックしてからボタン[ Ball ]のメニューで[ Text ]をクリックします。すると、テキスト入力のダイアログが表示されるので、そこにテキストを入力します。その後、テキストを表示したい位置(ただし、ラシャの上)をクリックすると、その位置にテキストが表示されます。最初に球をクリックするのはテキストの色を球の色で指定するためです。

表示されているテキストをクリックすると、テキスト操作のメニューが表示されます。メニューの項目は

  • Move Text
  • Edit Text
  • Resize Text
  • Border ON/OFF
  • Delete Text
  • Cancel
です。ただし、[ Move Text ][ Resize Text ]は未実装です。

上記テキスト操作メニューのルーチンはそれぞれ数行しか無く、特に説明するほどのものでもありません。少し難しいのはラシャをクリックした時にテキストを表示するルーチンです。それを下に示します。これは dragStop() の中で実行します。

    curTxt = document.createElementNS(svgns, "g");
    // (1)
    var ctm = flattable.getCTM();
    curTxt.x = (evt.clientX - ctm.e)*mag - centerX;
    curTxt.y = (evt.clientY - ctm.f)*mag - centerY;
    curTxt.color = txtColor;

    var txtFrame = document.createElementNS(svgns, "rect");
    txtFrame.setAttribute("x", centerX);
    txtFrame.setAttribute("y", centerY);
    txtFrame.setAttribute("height", FONTHEIGHT + 2);
    txtFrame.setAttribute("style",
        "fill:" + txtColor + ";fill-opacity:0.01;stroke:" + txtColor);
    curTxt.appendChild(txtFrame);

    var txtTxt = document.createElementNS(svgns, "text");
    txtTxt.setAttribute("x", centerX + 2);
    txtTxt.setAttribute("y", centerY + FONTHEIGHT);
    txtTxt.setAttribute("style", "font-size:12;fill:" + txtColor);
    txtTxt.textContent = txtData;
    txtData = "";

    addEvent(curTxt, "click", clickTxt);
    curTxt.setAttribute("transform",
        "translate(" + curTxt.x + "," + curTxt.y +")");
    curTxt.appendChild(txtTxt);

    curPage.appendChild(curTxt);
    txtFrame.setAttribute("width",
        txtTxt.textLength.baseVal.value + 4);    // (2)

テキストは recttext で構成されており、それらを g でまとめています。

クリック位置は evt.clientX, evt.clientY で取得できますが、この値はウィンドウ固有(ビューポート)の座標系に基づく値です。これを FlatTable の座標系に変換しているのが (1) です。getCTM() で現在の変換行列(CTM)と言うものを取得して座標変換に利用しています。mag は別の場所で 1/ctm.a が代入されています。

textLength には表示された文字列の長さ(文字数という意味ではなく、表示領域の長さ)の情報が入っていて、その値は textLength.baseVal.valueで取得できます(2)。これでテキストの枠を文字列の長さに合わせることが出来ます。

ここまでの実装では、テキストの移動、寸法変更は未実施です。また、台が回転すると文字が逆立ちするという問題が有り、それも未解決です。また、SVG のテキストには自動折り返し機能が無いので、現時点では1行のみの表示になっています。これらの問題は近日中に解決したいと思っています。

2012年4月18日 (水)

タイトルの入力(修正)

FlatTable のタイトルの入力ルーチンに問題が見つかったので修正しました。それにしても、その解決までには幾つもの試行錯誤を繰り返すことになりました。

まず、修正前のコードは次のようになっていました。

// タイトルの SVG コード
    <g id="titleBar"
        onclick="setText(evt)">
        <rect x="252" y="12" width="216" height="24"
            style="fill:#ddd;stroke:#999;stroke-width:1" />
        <text id="title" x="360" y="30"
            style="text-anchor:middle">test title</text>
    </g>
// JavaScript コード
function setText(evt){
    var el = evt ? evt.target : event.srcElement;
    var txt = el.parentNode.childNodes[3].firstChild;
    txt.data = prompt("Text:", txt.data);
}
この時は、タイトル表示の具合を確認するためにダミーとして「test title」を SVG コードに埋め込んでいました。しかし、完成品ではこの部分がもちろん削除されます。

上記 JavaScript コードの firstChild すなわち txt がテキストオブジェクト「test title」を指しており、このダミー部分が無くなると txtnull になります。その時 txt.data がエラーになるのです。

このエラーを回避するために、 txtnull の時は、テキストオブジェクト(の積りの) ""appendChild() してみました。しかし、それでは上手く行かないので、次に String("")appendChild() してみました。しかし、これもダメでした。

テキストオブジェクトは何者で、どうやって指定すれば良いのでしょうか?

ブラウザ内臓のデバッガでダミー部分を見てみると、「Text」となっています。そこで、ダミー部分に createChild("Text") を実行してみたのですが、結果は <Text></Text> となって、これもダメ。

インターネットで「テキストオブジェクト SVG」で検索したのですが、それでも解決しません。

innerHTML が使えるかと試したのですが、ダメでした。

もう最後の手段で、ダミー部分にスペースを入れて誤魔化すしか無いのでしょうか?

夜も遅くなり、もう寝なければならない頃になって、買っておいた本「HTML5 + JavaScript」(古旗一浩)を調べて、やっと答えが見つかりました。 firstChild ではなく textContent を使えば良いということです。

結局、タイトル入力ルーチンは、こうなりました。

// タイトルの入力
function setTitle(){
    var title = document.getElementById("title");
    var s = prompt("Title:", title.textContent);
    if (s != null){
        title.textContent = s;
    }
}
textContentnull だとしてもエラーには成りません。なお、ルーチンをタイトル入力に限定したので、関数名を変え、要素の取得を getElementById() に変えました。

ところで、textContent が分かったので、これを「SVG 1.1 仕様 (第2版) 日本語訳」で調べたのですが、満足できる記述を見付けることは出来ませんでした。本当に役に立たない仕様書です。(追記: textContent は SVG ではなく DOM で規定されているのかも知れません)

2012年4月16日 (月)

PCと携帯端末の判別

携帯端末(スマートフォン、タブレット)のタッチパネル操作で発生するイベントはPCのマウス操作で発生するイベントとは異なるそうです。マウス操作のイベント mousedown, mousemove, mouseup に相当するタッチパネルのイベントは touchstart, touchmove, touchend とのことです。これらの情報は以下のサイトが参考になりました。

そこで、FlatTable に次のコードを追加しました。

// PCと携帯端末の判別
var mouseDown = "touchstart";
var mouseMove = "touchmove";
var mouseUp = "touchend";

addEvent(flattable, "mouseover", checkMouse);

function checkMouse(){
    mouseDown = "mousedown";
    mouseMove = "mousemove";
    mouseUp = "mouseup";
    removeEvent(flattable, "mouseover", checkMouse);
}

イベントの文字列を最初は携帯端末用に設定しておいて、マウスが FlatTable の上に乗ったら(すなわち、マウスが有れば)その端末はPCであると判断するというものです。(追記:この方法では判別できないことが分かりました)

私の使用しているPCはタッチパネルになっているので、マウスが FlatTable に乗らないようにして動作確認してみました(携帯端末は持っていないのでPCで代用)。しかし、PCのタッチパネルは指で触ってもマウスイベントしか発生せず、携帯端末のイベントの動作確認は出来ませんでした。因みに、PCのタッチパネルに触れると、その位置にマウスが置かれたことになるようです。

2012年4月14日 (土)

領域表示機能

FlatTable に領域表示機能を追加しました。領域表示機能とは、図のように台に枠を描く機能です(閉じた枠に限定せず開いた線でも可能)。この機能は、経路作図機能を少し変更して実現しました。

配置図

図のような枠線を引くには次のようにします。

  1. 表示したい線の色の基礎球を適当な位置に置き、そこから枠線の開始位置まで経路を作図します。
  2. 枠線を作図します。
  3. 基礎球を台の外へ移動します。
この操作をすると、基礎球は StandBy 位置に移動し、基礎球から枠線開始位置までの線と矢印球が非表示になって、図のように領域表示が完成します。基礎球を台の内に移動すると矢印球が表示されて領域表示は解消されます。

今回のルーチンは主に dragStop() にコーディングされています。ただし、それ以外にもこまごまとした修正は必要でした。


function dragStop(){
    ・・・
    var baseBall = curBall.baseBall ? curBall.baseBall : curBall;
    if (baseBall が台の内){
        if (baseBall.arrow &&
            (baseBall.arrow.getAttribute("display") == "none")
        ){
            // 領域表示の解消
            baseBall.arrow.setAttribute("display", "inline");
            straight(baseBall.next);    // 直線
        }
        baseBall.fLive = true;
    } else if (baseBall.next){
        // 領域表示
        var i = baseBall.id.lastIndexOf("b");
        i = baseBall.id.slice(++i);
        if (tableAngle){
            baseBall.x = -20*i + 288;
            baseBall.y = -195;
        } else {
            baseBall.x = 20*i - 288;
            baseBall.y = 195;
        }
        ballMove(baseBall, tableAngle);
        jump(baseBall.next);    // ジャンプ
        baseBall.arrow.setAttribute("display", "none");
        baseBall.fLive = true;
    } else {
        standBy(false);
    }
    ・・・
}

基礎球・枠線開始位置間の線の非表示化は jump() を流用しています。また、領域表示を解消するための線の再表示は単純に straight() を流用しています。そのため、領域表示前は基礎球・枠線開始位置間がカーブだった場合でも、領域表示解消後は直線になってしまいます。しかし実際問題として、領域表示のために操作した経路で基礎球・枠線開始位置間をカーブにすることは無いと思われるので、カーブを復元できない問題は無視することにします。

2012年4月12日 (木)

台の回転

FlatTable に台の回転機能を追加しました。回転と言っても、台がぐるぐる回るのではなく、台の向きが入れ替わるということです。

台の画像を回転させること自体は、そんなに難しくはないのですが、それに付随して、球の向きを直したり、場外の StandBy 位置に有る球が台と一緒に移動するのを元に戻したりと、雑用に煩わされることになりました。もっと面倒だったのは、ページ切り替え時にも同様の雑用が付いて回ることでした。

ルーチンは以下の通りです。

// 台の回転
function tableTurn(){
    ・・・
    tableAngle = tableAngle ? 0 : 180;
    document.getElementById("table").setAttribute("transform",
        "rotate(" + tableAngle + "," + centerX + "," + centerY + ")");
    revise(tableAngle);
}

// 球の向き・位置を修正する
function revise(angle){
    var ball;
    var b;
    var arrow;
    for (var i = 0; i < 16; i++){    // 手球から 15 番まで
        ball = curPage.childNodes[i*2 + 1];
        if ((-180 <= ball.y)&&(ball.y <= 180)){    // (1)
            if (arrow = ball.arrow){
                for (b = ball.next; b && (b != arrow); b = b.next){
                    ballRot(b, angle);    // 球の回転
                }
            }
        } else {    // (2)
            if (angle){
                ball.x = -20*i + 288;
                ball.y = -195;
            } else {
                ball.x = 20*i - 288;
                ball.y = 195;
            }
        }
        ballRot(ball, angle);    // 球の回転
    }
}

centerX, centerY は台のセンタースポットです。回転はこのセンタースポットを中心に行われます。

ball.y の絶対値が 180 以内ならば、その球は台の内側に位置していることを意味します(1)。その場合は、ゴーストの向きも修正する必要が有ります。また、ball.y の絶対値が 180 を超えていると、それは場外の StandBy 位置に有ることを意味するので、台の回転による移動を元に戻します(2)。なお、この 180 は回転角度の 180 とは無関係です。

2012年4月10日 (火)

ページの複製&・・・

FlatTable にコマンド「Dup & GtE(複製 & 終点への移動)」を実装しました。FlatTable は複数の配置を扱うことが出来、それぞれの配置はページと呼んでいます。「Dup & GtE」は、このページを複製し、且つ、基礎球を経路の終点へ移動するコマンドです。このコマンドはボタン[ Tot ]から実行できます。

当初は「Dup」だけを考えていたのですが、「GtE(Go to the End)」を付け加えて1つのコマンドにまとめることにしました。「Dup」だけだと実装が面倒だというのがその理由です。「Dup」は球の配置だけでなく、経路に関する情報も複製しなけらばならず、それが面倒なのです。「GtE」だと経路は考えずに済むので楽です。

もう1つの理由として「Dup & GtE」の方が FlatTable 使用時の自然な流れに沿っているということが有ります。ページを複製する状況を Flash 版 FlatTable を使って説明します。(ページの移動は FlatTable 右上の「▲」をクリックします)

SAMPLE のように連続したショットを配置図にするには、まず、最初のページを作図します(1ページ目)。次に、そのページを複製し(2ページ目になる)、基礎球を経路の終点へ移動させます。そして、そのページで2番目のショットを作図します。その次のショットは、2ページ目を複製し(3ページ目になる)、後は2ページ目と同じ処理をしてそのショットを作図します。それ以降も同様に進めて行って連続したショットを完成させます。この例のように、ページを複製してもすぐ経路を御破算にして基礎球を終点へ移動するのが「Dup」の主な使い方です。

このような理由から、新たな FlatTable では「Dup」ではなく、「Dup & GtE」を採用することにしました。

なお、「Dup」はコマンドとしては実装しない訳ですが、後に出て来る配置コードを利用することで「Dup」と同じ効果を得ることが可能となります。

「Dup & GtE」のルーチンは次の通りです。

// Dup & Go to the End
function dupGtE(){
    ・・・
    var oldPage = curPage;
    var oldBall = null;
    genPage(curPage, false);

    for (var i = 0; i <= 15; i++){
        ball = curPage.childNodes[i*2 + 1];
        oldBall = oldPage.childNodes[i*2 + 1];
        src = oldBall.arrow ? oldBall.arrow : oldBall;
        ball.x = src.x;
        ball.y = src.y;
        ball.setAttribute("transform",
            "translate(" + ball.x + "," + ball.y + ")"
        );
        addEvent(ball, "click", ballClick);
    }
}
現行ページを旧ページとして記憶しておき、新たにページを作成(genPage())します。この genPage() により新たに作成されたページが現行ページになります。その後で、各基礎球を経路の終点へ移動し、イベントハンドラーを設定します。ずいぶん簡単な実装になりました。

2012年4月 8日 (日)

ページの生成・削除、ページ移動

FlatTable のプログラム・コードが次第に増えて来て、コードの配置がカオスに成りつつあります。そのうち複数のファイルに分割しようと思っています。

さて FlatTable にページの生成・削除とページ移動を実装しました。

ページの生成・削除はボタン[ Tot ]から実行できます(New Page, Delete Page)。

ページの生成とは、基礎球一式を新たに創ることです。SVG 部分で基礎球が <g id="balls">・・・</g> によってグループ化されているので、ページの生成は、そのグループを clonNode(true) することで出来ます。

ページの生成ルーチンは次の通りです。

// ページの生成
function genPage(prevPage){
    var newPage = document.getElementById("balls").cloneNode(true);
    ページのリンクリストの処理
    基礎球の配置
    flattable.appendChild(newPage);
    ページカウンタの処理
}
ページカウンタの処理は後で説明するページ移動の処理ルーチンを利用しています。

ページの削除ルーチンは、次の通りです。

// ページの削除
function delPage(oldPage){
    ページカウンタの処理
    ページのリンクリストの処理
    現行ページの削除
    その他少々
}

ページ移動は FlatTable 画面左上の「▲」をクリックして行います。2つの「▲」に挟まれた2つの数はページカウンタで、左側は現行ページ番号、右側はページ総数を表します。

ページ移動のルーチンは次の通りです。

// ページダウン
function clickBtnPgDn(){
    ・・・
    curPage.setAttribute("display", "none");
    curPage = curPage.prev;
    curPage.setAttribute("display", "inline");

    カウンタを1つ減少させる
}
// ページアップ
function clickBtnPgUp(){
    if (curPage){
        if (curPage.next == curPage) return;

        curPage.setAttribute("display", "none");
        curPage = curPage.next;
        curPage.setAttribute("display", "inline");
    }

    カウンタを1つ増加させる
}

カウンタへのアクセスは、

    document.getElementById("pgCur").firstChild.data
で出来ます。

ところで、curPage が未定義の時、

curPage = genPage(curPage);
は正常に動作するのに、
    if (curPage.next == curPage)
curPage.next でエラーになるんですね。一貫性に欠けた仕様なので間違えました。

2012年4月 1日 (日)

タイトルの入力

SVG の参考文書としては、「SVG 1.1 仕様 (第2版) 日本語訳」で必要充分・・・な、はずなのですが、こんな解りにくい文書は見たことが有りません。検索機能が無いので、何かキーワードを調べるのもままなりません。

そこで、他の参考書(サイト)で、最近見つけて非常に重宝しているのが「SVG 実習マニュアル」です。記述も平易で、例文も多く、上記仕様書よりも遥かに役に立っています。

FlatTable ではタイトルを入力・表示できることになっています。Flash では、テキストフィールドに入力機能が付いているので、悩むこと無くタイトルの入力が出来たのですが、SVG のテキストには同様の機能が有りません。どうしたものかと悩んでいましたが、「SVG 実習マニュアル」にその解が載っていました。prompt() を使って文字列を入力できるとのことです。

「SVG 実習マニュアル」を参考にして、タイトルの SVG コードおよび JavaScript コードは次のようになりました。

    <!-- タイトルの SVG コード -->
    <g id="titleBar"
        onclick="setText(evt)">
        <rect x="252" y="12" width="216" height="24"
            style="fill:#ddd;stroke:#999;stroke-width:1" />
        <text id="title" x="360" y="30"
            style="text-anchor:middle">test title</text>
    </g>
// JavaScript コード
function setText(evt){
    var el = evt ? evt.target : event.srcElement;
    var txt = el.parentNode.childNodes[3].firstChild;
    txt.data = prompt("Text:", txt.data);
}

タイトル領域は recttext の2つから成っています。このタイトル領域をクリックした時 el が2つのどちらになるかはクリック位置次第です。そこで、

<text id="title" x="360" y="30"
	style="text-anchor:middle">test title</text>    (1)
を取得するために、一旦 parentNode に上がります。そして、3番目の子供 childNodes[3] で (1) を取得しています。(1)の firstChild が「test title」という Text Object で、この Text Object の data が文字列
test title
になっています。

なお、FlatTable では、コメントや配置コードに関する操作でも文字列を入力する場面が有りますが、そのルーチンは setText() を共有して使用することにします。

« 2012年3月 | トップページ | 2012年5月 »