テキストWidget

テキストWidgetは、既に説明したタグの機能の他にも
いくつかの重要な機能を持っています。
しかし、使うにあたってコツが必要であることも事実です。

インデックス

テキストWidgetは、テキストの位置を示すインデックス機能を持っています。
インデックスは、以下の形式になっています。
インデックス修飾子は複数指定できますが、省略も可能です。

ベース ?インデックス修飾子? ?インデックス修飾子?

ベースの一覧
line.char テキストの行とカラムの位置。行は1からカラムは0から始まります。
charendの時は行端のニューラインの位置を示す。
@x,y テキストのX座標とY座標(ピクセル単位)の位置。
end テキストの終端のニューラインの位置。
mark markという名前のマークの後ろの位置。
tag.first tagという名前のタグの先頭の位置。
tag.last tagという名前のタグの最後の位置。
pathName pathNameという名前の埋め込まれたWidgetの位置
imageName imageNameという名前の埋め込まれたイメージの位置
insert キャレットの置かれている位置

インデックス修飾子の一覧
+count chars count文字分先の位置
-count chars count文字分前の位置
+count lines count行分先の位置
-count lines count行分前の位置
linestart 行の先頭の位置
lineend 行の最後のニューラインの位置
wordstart ワードの先頭の位置。ワードは英数字とアンダーバー
wordend ワードの最後の位置。ワードは英数字とアンダーバー

ベースとインデックス修飾子を組み合わせた例です。

# テキストの終端の1文字前の位置のインデックスを返す
.t index "end -1 chars"
# カレット位置のワードの先頭から1文字前の位置のインデックスを返す
.t index "insert wordstart -1 c"
# テキストの終端に文字列を挿入する
.t insert end "Hello World\n"
# 先頭行の1行を取得する
.t get "1.0 linestart" "1.0 lineend"

Widgetの埋め込み

テキストWidget内にWidgetを埋め込むことができます。

pack [text .t]
button .b -text Push -command exit
.t window create end -window .b

イメージの埋め込み

テキストWidget内にイメージを埋め込むことができます。

pack [text .t]
image create photo foo -file hana.gif
.t image create end -image foo -name bar

ワード認識

Tcl8.3からテキストWidget内でダブルクリックした時にセレクトされる
ワードの定義をtcl_wordcharsとtcl_nonwordchars変数に正規表現で設定できるようになっています。
ワードのセパレータのデフォルトは空文字になっていますが、
たとえば、ドット('.')もセパレータに加えたい場合は、以下の設定を行います。

# ワードのセパレータの設定をする
set tcl_wordchars {[^ \t\n.]+}
set tcl_nonwordchars {[ \t\n.]+}

しかし、ここに落とし穴があります。なぜならば、
tcl_wordcharsとtcl_nonwordcharsは、-wrap word指定での折り返しと
インデックス修飾子のwordstartとwordendには効果がないからです。
両者のワード認識は相変わらず英数字とアンダーバーだけでになっています。

行の終端

行の終端をインデックスで指定する時にニューラインを含めるかどうかで、
以下の様にハイライトしたイメージが異なります。

pack [text .t]
.t insert end "Hello World\nHello World\nHello World\n"
.t tag configure foo -background blue -foreground white
# ニューラインを含めない
.t tag add foo 1.0 1.end
# ニューラインを含める
.t tag add foo 2.0 "2.end +1 chars"
# ニューラインを含める
.t tag add foo "3.0 linestart" "3.0 lineend +1 chars"

1行の範囲の違い

文字列のサーチ

テキストWidget内のテキストを正規表現を使ってサーチできます。
searchは文字列が見つかると文字列の先頭のインデックスを返します。
コンパイルできない正規表現を指定するとエラーが発生するので注意しましょう。

pack [text .t]
.t insert end "Hello World\n"
# テキスト全体から文字列を検索
.t search -forwards -count length -- "World" 1.0 end
=>1.6
# テキスト全体から正規表現を使って文字列を検索
.t search -forwards -count length -regexp -- "Wo.*ld" 1.0 end
=>1.6

マーク

インデックスにマークで名前を付けると、マーク名でインデックスの指定ができます。

# マークで名前を付ける
.t mark set foo 1.0
# インデックスにマーク名を指定する
.t index foo
=>1.0
# マーク名一覧を取得する
.t mark names
=>foo insert anchor current

実は、insertはデフォルトのマーク名だったようです。
ということは、insert, anchor, currentは予約語ということになります。

幅と高さの取得

テキストWidgetの幅と高さは以下のように取得できます。
デフォルトの幅と高さは、80x24文字であることが取得できます。

pack [text .t]
.t cget -width
=>80
.t cget -height
=>24

しかし、ここに落とし穴があります。なぜならば、
テキストWidgetはリサイズされても80x24を返してくるのです。これは厄介です。
では、リサイズ後の幅と高さを取得するにはどうしたらよいのでしょうか?

以下の例は、テキストWidgetのスペーシングを考慮して、ウィンドウサイズと
フォントサイズから何文字分かを計算して幅と高さを求めています。
テキストWidgetが-wrap noneで折り返さない設定でかつfixedフォントであればうまくいくはずです。

# テキストWidgetの幅を返すプロシジャ
proc textwidth {path} {
    set width [winfo width $path]
    set font [$path cget -font]
    set measure [font measure $font "A"]
    return [expr $width / $measure]
}

# テキストWidgetの高さを返すプロシジャ
proc textheight {path} {
    set height [winfo height $path]
    set font [$path cget -font]
    set space [expr [$path cget -spacing1] + [$path cget -spacing3]]
    set metrics [font metrics $font -linespace]
    return [expr $height / ($metrics + $space)]
}

Disabled状態

テキストWidgetは、-stateでnormalとdisabledを選択できます。
disabledにすると、エディット禁止モードになりますが、
disabledでは、テキストWidgetにfoucsを当てないとセレクトできないとか、
キャレットを置けないという欠点があります。
では、セレクトできてキャレットが置けるにはどうしたらよいのでしょうか?

以下の例は、テキストWidgetのバインドの優先順位を変更し、
Textにフックをかけることにより、disabledの問題を解決します。
とりあえず、カーソル移動はできます。

pack [text .t]
# bindの優先順位を変更する
bindtags .t {.t Text . all}
# 入力を無視する
bind .t <Any-Key> {break}
bind .t <Up> {continue}
bind .t <Down> {continue}
bind .t <Left> {continue}
bind .t <Right> {continue}
bind .t <Control-p> {continue}
bind .t <Control-n> {continue}
bind .t <Control-f> {continue}
bind .t <Control-b> {continue}