ラベル Tcl/Tk 入門 の投稿を表示しています。 すべての投稿を表示
ラベル Tcl/Tk 入門 の投稿を表示しています。 すべての投稿を表示

2013-10-14

【備忘録】after コマンド

Tcl/Tk で一定間隔で何かを処理させようと思ったときに after コマンドってどうやって使ったっけ?といつもマニュアルを見て考え込むので、備忘録として簡単なスクリプトにまとめました。今回は短くまとめることにこだわっています。

proc every {ms body} {eval $body; after $ms [info level 0]}
pack [label .l -bg white -textvariable now]
every 1000 {set ::now [clock format [clock seconds] -format %H:%M:%S]}

関連サイト

2009-08-22

リストの要素を変数に代入する(2)


匿名さんから lassign で良いのでは、というコメントをいただきました。ハイ、その通りです。ご指摘をありがとうございました。

Tcl/Tk リリース履歴 の Tcl 8.5a1 and Tk 8.5a1 (2004-03-03) における特記事項に、自分でちゃんとこのコマンドを書いておりました。そればかりか、恥ずかしいことに、なんと「要素の値を変数へ代入」で lassign の説明すらしています。それなのに、全然利用していませんでした。利用しないと忘れます。未だに、サブルーチン(proc コマンド)から戻り値をリストで受けとると、値を lindex コマンドで変数に振り分けていました。少なくとも最近書いている Tcl のコードは書き直す必要があります。未熟な知識に反省です。

$ tclsh
% lassign [list 1 2 3] a b c
% puts $a
1
% puts $b
2
% puts $c
3
% exit

2009-08-21

リストの要素を変数に代入する


perl で、リストの要素を変数に代入する場合は次のように記述します。

$ perl
($a, $b, $c) = (1, 2, 3);
print $a, "\n";
print $b, "\n";
print $c, "\n";

^D1
2
3

$

とてもスマートにできます。


Tcl の場合だと次のようにします。perl の表現に比べると、とても武骨です。

$ tclsh
% set z [list 1 2 3]
1 2 3
% set a [lindex $z 0]
1
% set b [lindex $z 1]
2
% set c [lindex $z 2]
3
% exit
$

もっと簡単にできないかと考えたところ、名案が浮かびました。perl ほどではありませんが…。

$ tclsh
% foreach {a b c} [list 1 2 3] {}
% puts $a
1
% puts $b
2
% puts $c
3
% exit
$

考えてみると、だれか他の人の Tcl コードで既にこのように使われていた気がします。その時にはちゃんと理解できず見逃していたのでしょう。
 

2009-08-11

chan create

chan は、Tcl 8.5 で追加されたコマンドです。チャネルを操作するコマンドで、従来の open や socket コマンドと同じことが出来るだけでなく、chan create コマンドでチャネルを独自に定義することもできます。

簡単なサンプルとして、チャネルの出力先を text ウィジェットにした例を紹介します。

# -----------------------------------------------------------------------------
# NAMESPASE textOut
# -----------------------------------------------------------------------------

namespace eval textOut {
variable subCmds [list initialize finalize watch write]
variable m; # pathName of text widget to display message

namespace ensemble create -subcommands $subCmds
namespace export regist

# initialize
proc initialize {channelId mode} {
variable subCmds

return $subCmds
}

# finalize
proc finalize {channelId} {
# NOP
}

# watch
proc watch {channelId eventspec} {
# NOP
}

# write
proc write {channelId data} {
variable m

set msgout [encoding convertfrom [encoding system] $data]
$m configure -state normal
$m insert end $msgout
$m see end
$m configure -state disable
update
return [string length $data]
}

# regist
# The namespace specific command to remember pathName

proc regist {pathName} {
variable m

if {![winfo exists $pathName]} {
error "$pathName does not exist."
}
if {[winfo class $pathName] ne "Text"} {
error "$pathName is not text widget."
}
set m $pathName
$m configure -state disable
return $m
}
}

chan create コマンドで利用する、チャネルを操作するコマンド(この例では textOut)では、定められた名前のサブコマンドを用意する必要があります。前回紹介した、namespace ensemble コマンドを利用すると簡単にできます。(これが唯一の方法というわけではありません。)

上記のスクリプトを textOut.tcl というファイル名で保存した後、コンソール上で wish あるいは tclsh を起動、source コマンドで読み込んで実行した例を以下に示します。

$ tclsh
% package require Tk
8.5.6
% pack [text .t]
% source textOut.tcl
% textOut::regist .t
.t
% set out [chan create write textOut]
rc0
% chan configure $out -buffering line
% puts $out "これは出力テストです。"
%



出力先の text ウィジェットのパス名を textOut::regist で名前空間内の変数に登録してから利用します。なお、chan configure コマンドで -buffering line を指定して、バッファに出力が溜まらないようにしています。デフォルトでは、flush しないと(バッファが一杯になるまで)出力はバッファに溜まったままになります。

この例は、特定のチャネルを通じて、text ウィジェットに文字列を出力するだけの簡単なものですが、chan create コマンドによるチャネルの定義は、いろいろ応用できる分野がありそうです。
 

2009-08-07

namespace ensemble

namespace ensemble(アンサンブル)は Tcl 8.5 で加わった namespace のサブコマンドです。

名前空間をコマンド名、proc コマンドで定義した名前空間内のユーザーコマンド(手続き)をサブコマンド名として、名前空間の外から扱えるようになります。サブコマンドとして扱うコマンドは -subcommands オプションにリストで指定します。

簡単なサンプルを紹介します。

namespace eval test {
namespace ensemble create -subcommands [list abcd efgh]

proc abcd {} {
puts "-- subcommand 'abcd'! --"
}
proc efgh {} {
puts "-- subcommand 'efgh'! --"
}
proc ijkl {} {
puts "-- command 'ijkl' in namespace test --"
}
}

このサンプルを test.tcl というファイル名で保存し、tclsh を起動して source コマンドで保存したファイルを読み込んで、コマンドを実行してみます。

$ tclsh
% source test.tcl
% test
wrong # args: should be "test subcommand ?argument ...?"
% test abcd
-- subcommand 'abcd'! --
% test efgh
-- subcommand 'efgh'! --
% test ijkl
unknown or ambiguous subcommand "ijkl": must be abcd, or efgh
% test::ijkl
-- command 'ijkl' in namespace test --

 

2009-03-08

Tcl の正規表現


Tcl の正規表現は、Henry Spencer のルーチンをベースに実装されているので、Perl の正規表現と出発点は同じです。もちろん POSIX 1003.2 の拡張正規表現にも準拠しています。文字列の内部処理が Unicode なので、日本語文字に対する処理も問題がありません。しかし、昔の Tcl の貧弱な正規表現が強烈だったせいか、今でも Tcl で正規表現を扱った話題が少ないような気がしてなりません。一方で、Tcl の正規表現に関するす情報が少ないことが、注目されない一因であるとも考えられるので、Tcl 入門編の中に、整理したページを用意しました。

・正規表現

入門編にしては、まだサンプルが少なく、現在のところ、ちょっと重い内容をサンプルにして説明しています。今後、もっと基本的な内容をカバーしたサンプルを(不定期になりますが)追加していきます。
 

2009-02-11

なつかしのソンブレロ


8bit パソコン全盛の頃、愛機 MZ-80K2E を、320x200 ドットの解像度でグラフィック表示が出きるように改造して、ソンブレロを表示させて遊んでいました。キャリーラボの WICS で記述した記憶があります。懐かしくなり、C プログラミングの本の、陰線消去法の説明で紹介されていたソンブレロのサンプルを Tcl/Tk 用に書き直してみました。配列をプロシージャー(ユーザーコマンド)に渡す場合、Tcl では綺麗に記述できないので、全てリストに置き換えました。


namespace eval sombrero {
namespace export *

variable DX 640
variable DY 400
variable SOX [expr $DX / 2 - 1]
variable SOY [expr $DY / 2 - 1]
variable MAX [lrepeat $DX -1]
variable MIN [lrepeat $DX $DY]
variable Pi 3.1415926535

proc getWidth {} {
variable DX
return $DX
}
proc getHeight {} {
variable DY
return $DY
}
proc getRad {x} {
variable Pi
return [expr $x * $Pi / 180.0]
}
proc initVal {x y z} {
variable sx; variable sy; variable sz
variable cx; variable cy; variable cz

set x [getRad $x]; set y [getRad $y]; set z [getRad $z]
set sx [expr sin($x)]; set sy [expr sin($y)]; set sz [expr sin($z)]
set cx [expr cos($x)]; set cy [expr cos($y)]; set cz [expr cos($z)]
return
}
proc rotate {x y z} {
variable sx; variable sy; variable sz
variable cx; variable cy; variable cz

set x1 $x
set y1 [expr $y * $cx - $z * $sx]
set z1 [expr $y * $sx + $z * $cx]
set x2 [expr $x1 * $cy + $z1 * $sy]
set y2 $y1
set z2 [expr -$x1 * $sy + $z1 * $cy]
set x3 [expr $x2 * $cz - $y2 * $sz]
set y3 [expr $x2 * $sz + $y2 * $cz]
set z3 $z2
return [list $x3 $y3 $z3]
}
proc loop {a b c} {
if {$c > 0} {
return [expr $a <= $b]
} else {
return [expr $a >= $b]
}
}
proc func {x z} {
variable Pi

set l [expr hypot($x, $z)]
return [expr 50 * cos($Pi * $l / 100) + 5 * sin($Pi * $l / 25)]
}
proc draw {w x y} {
variable SOX; variable SOY
variable MAX; variable MIN
variable ix0; variable iy0
variable lsw

set ix [expr int($SOX + $x)]; set iy [expr int($SOY - $y)]
if {$lsw == 1} {
set lsw 0
set ix0 $ix; set iy0 $iy
}
if {$iy > [lindex $MAX $ix]} {
lset MAX $ix $iy
$w create line $ix0 $iy0 $ix $iy
}
if {$iy < [lindex $MIN $ix]} {
lset MIN $ix $iy
$w create line $ix0 $iy0 $ix $iy
}
set ix0 $ix; set iy0 $iy
return
}
proc main {w rx ry rz} {
variable DY
variable lsw

initVal $rx $ry $rz
set retList [rotate 0.0 0.0 [expr -$DY / 2]]
set za [lindex $retList 2]
set retList [rotate 0.0 0.0 [expr $DY / 2]]
set zb [lindex $retList 2]
set zz1 [expr -$DY / 2]; set zz2 [expr $DY / 2]; set stz 5
if {$za > $zb} {set zz1 -$zz1; set zz2 -$zz2; set ztz -$stz}
set xx1 [expr -$DY / 2]; set xx2 [expr $DY / 2]; set stx 1
for {set z $zz1} {[loop $z $zz2 $stz]} {incr z $stz} {
set lsw 1
for {set x $xx1} {[loop $x $xx2 $stx]} {incr x $stx} {
set dataList [rotate $x [func $x $z] $z]
set xa [lindex $dataList 0]
set ya [lindex $dataList 1]
draw $w $xa $ya
}
}
}
}

namespace import sombrero::*
# ------------------------------------------------------------------------------
# MAIN
# ------------------------------------------------------------------------------
wm title . "sombrero"
set w [canvas .can -width [getWidth] -height [getHeight]]
pack $w

main $w -45 20 0
# ---
# PROGRAM END

参考資料


[1] C アルゴリズム全科 基礎からグラフィックスまで (1995)
千葉則茂、村岡一信、小沢一文、海野啓明
近代科学社 ISBN4-7649-0239-7
 

2008-12-28

Tcl and the Tk Toolkit (2nd Edition)


もっとTcl/Tk のサイトを運営されている、いまいさんのブログ、つれづれなるままに − つれづれブログ を久しぶりに読んでいたら、耳寄りな情報を発見!

2008年12月06日の記事によると、Tcl/Tk を考案した John Ousterhout 氏が、Tcl and the Tk Toolkit を 15年ぶりに改訂するらしいのです。なんと 600 ページもあるらしい。発売予定が来年の 6 月ということで、ずいぶん先ですが、Amazon.com に予約を入れておきました。

Order #: XXX-XXXXXXX-XXXXXXX
Shipping Method: Expedited International Shipping
Shipping Preference: Group my items into as few shipments as possible
Subtotal of Items: $42.70
Shipping & Handling: $13.98
------
Total for this Order: $56.68

Delivery estimate: June 18, 2009 - June 30, 2009
Shipping estimate for these items: June 8, 2009
1 "Tcl and the Tk Toolkit (2nd Edition) (Addison-Wesley Professional Computing Series)"
John K. Ousterhout; Paperback; $42.70

Sold by: Amazon.com, LLC


 

2008-11-29

TclOO - Tcl の標準オブジェクトシステム(3)


オブジェクト指向プログラミングを採用する大きなメリットは、開発したソフトウェアをコンポーネント(部品)として再利用できることでしょう。再利用性が高ければ、生産性向上が期待できます。

しかし、そもそもプログラムを再利用するとはどういうことでしょうか。既存のプログラムソースからコピーして利用することも再利用には違いありません。しかし、毎回そうするのは効率的ではありません。なんらかの形で再利用するプログラムを共有できるようにできれば、いちいち必要な部分をコピーする手間が省けます。

Tcl では、プログラムをパッケージ化してライブラリとして利用できます。オブジェクト指向とパッケージ化とは同じではありませんが、パッケージ化することでオブジェクト指向プログラミングの高い再利用性というメリットを享受しやすくなります。ここでは Tcl のスクリプトのパッケージ化を支援する機能を紹介し、前回のサンプルに応用してみます。

プログラムのパッケージ化


Tcl では、パッケージに関する操作を package コマンドでおこないます。Tcl プログラムでパッケージを読み込む場合は、

package require パッケージ名 ?バージョン番号?

のようにします。Tcl は変数 auto_path に格納されているパス内から指定されたパッケージを検索し、パッケージ名が存在すれば、パッケージがスクリプトであれば source コマンドで、バイナリの共有ライブラリであれば load コマンドで読み込みます。

変数 auto_path のデフォルト値は、Tcl がインストールされている場所に依存した値が格納されています。自分が使っている Fedora Linux 10 の RPM パッケージ、Tcl 8.5.3 では、次のようになっています。

$ tclsh
% puts $auto_path
/usr/share/tcl8.5 /usr/lib/tcl8.5 /usr/lib/tk8.5
%

TclOO がビルトインパッケージに組み込まれた Tcl/Tk 8.6 系はまだα版ですので、$HOME 内にインストールして使用しています。この場合は、次のようになっています。

$ tclsh8.6
% puts $auto_path
/home/bitwalk/lib/tcl8.6 /home/bitwalk/lib
%

$HOME 内にインストールされていれば、パッケージのインストールパスは、自分でプログラムの編集に利用できる領域ですが、Linux ディストリビューションから配布されているパッケージの場合は、/usr 以下の領域ですのでそうはいきません。

auto_path は、プログラム側で変更可能な変数です。そこで、適当な名前の環境変数を用意してプライベートなパッケージ領域のパスを設定し、Tcl 側の配列 env でその値を読み、変数 auto_path にそのパスを追加することにします。

環境変数を .bashrc に次にように追加しました。

export TCLPKG=$HOME/tclpkg

環境変数名は TCLPKG でなければならない、というわけではありません。しかし、本ブログでの説明では、今後、この環境変数名を使います。

ちなみに csh/tcsh をシェルに使っている場合は、.cshrc あるいは .tcshrc に、

setenv TCLPKG $HOME/tclpkg

と追加します。

Tcl では、配列 env で、この環境変数の値を利用できます。

$ tclsh8.6
% parray env
env(COLORTERM) = gnome-terminal
env(CVSROOT) = :ext:bitwalk@mingw-cross.cvs.sourceforge.net:/cvs...
env(CVS_RSH) = ssh
(中略)
env(TCLPKG) = /home/bitwalk/tclpkg
env(TERM) = xterm
env(USER) = bitwalk
env(USERNAME) = bitwalk
env(WINDOWID) = 48240455
(中略)
%

これで準備ができました。

前のサンプル・プログラム tkwidget_canvas_2.tcl で利用したクラス Draw の部分を分離してパッケージ化します。

まず、パッケージを格納する場所を決めましょう。$HOME/tclpkg 以下に Draw というディレクトリを作成しておきます。

$ mkdir ~/tclpkg
$ mkdir ~/tclpkg/Draw

サンプル・プログラム tkwidget_canvas_2.tcl から分離したプログラムは次のようになります。スクリプトの先頭にある package provide Draw 1.0 は、パッケージ名(Draw)とバージョン(1.0)を、呼び出す側のプログラムで実行した package require コマンドに(間接的に)知らせるためのコマンドです。パッケージ名は先頭を大文字にします。

# ----------------------------------------------------------------------------
# 拡張パッケージ Draw
# ----------------------------------------------------------------------------

package provide Draw 1.0

package require Tk;
package require TclOO; # ビルトインパッケージなので省略可
namespace import oo::*

# ----------------------------------------------------------------------------
# クラス Draw
# ----------------------------------------------------------------------------

class create Draw {
# ------------------------------------------------------------------------
# コンストラクタ
# ------------------------------------------------------------------------

constructor {} {
my variable conf
set conf(color) black
set conf(width) 1
}

# ------------------------------------------------------------------------
# 設定
# ------------------------------------------------------------------------

method init {color {width 1}} {
my variable conf
set conf(color) $color
set conf(width) $width
return
}

# ------------------------------------------------------------------------
# 描画起点の座標を取得
# ------------------------------------------------------------------------

method first {x y} {
my variable conf
set conf(x0) $x
set conf(y0) $y
return
}

# ------------------------------------------------------------------------
# 線を描画
# ------------------------------------------------------------------------

method line {w x y} {
my variable conf
$w create line $conf(x0) $conf(y0) $x $y \
-fill $conf(color) \
-width $conf(width) \
-tags mLine
my first $x $y
return
}

# ------------------------------------------------------------------------
# 線を消去
# ------------------------------------------------------------------------

method erase {w} {
$w delete mLine
return
}
}

# ---
# Draw.tcl


これを Draw.tcl というファイル名で、先ほど作成した ~/tclpkg/Draw に保存します。

次に、呼び出すプログラム側がパッケージを検索するのに必要なインデックスファイル pkgIndex.tcl を生成します。このファイルは、Tcl の pkg_mkIndex コマンドを使うと簡単に生成できます。

$ cd ~/tclpkg/Draw
$ ls
Draw.tcl
$ tclsh8.6
% pkg_mkIndex . *.tcl
% exit
$ ls
Draw.tcl pkgIndex.tcl
$

生成された pkgIndex.tcl は次のようになっています。

# Tcl package index file, version 1.1
# This file is generated by the "pkg_mkIndex" command
# and sourced either when an application starts up or
# by a "package unknown" script. It invokes the
# "package ifneeded" command to set up package-related
# information so that packages will be loaded automatically
# in response to "package require" commands. When this
# script is sourced, the variable $dir must contain the
# full path name of this file's directory.


package ifneeded Draw 1.0 [list source [file join $dir Draw.tcl]]

これで Draw パッケージの作成完了です。

さて、クラスの部分を分離した本体の tkwidget_canvas_2.tcl(tkwidget_canvas_3.tcl にファイル名を変更)は次のようにします。

#!/bin/sh
# the next line restarts using wish \
exec wish8.6 "$0" "$@"

# 環境変数 TCLPKG が定義されていれば auto_path に追加
if {[info exist env(TCLPKG)]} {
lappend auto_path $env(TCLPKG)
}
package require Draw

# ----------------------------------------------------------------------------
# メイン
# ----------------------------------------------------------------------------

wm title . "canvas"

set color_bg "#f0fff8"
set color_grid "#c0e0d0"
set color_draw "#804040"

# キャンバスの生成
set cw 200
set ch 200
canvas .can \
-width $cw \
-height $ch \
-borderwidth 0 \
-highlightthickness 0 \
-background $color_bg
pack .can

# 方眼
for {set y 0} {$y < $ch} {incr y 10} {
.can create line 0 $y $cw $y -fill $color_grid
}
for {set x 0} {$x < $cw} {incr x 10} {
.can create line $x 0 $x $ch -fill $color_grid
}

# クラス Draw のインスタンス pen を生成
set pen [Draw new]

# メソッド init で描画色を設定
$pen init $color_draw

# マウス左ボタンを .can 上で押した時、メソッド first で座標を取得
bind .can <Button-1> {$pen first %x %y}

# マウス左ボタンを押しながら .can 上を移動した時、メソッド line を実行
bind .can <B1-Motion> {$pen line %W %x %y}

# ---
# tkwidget_canvas_3.tcl

2008-11-23

TclOO - Tcl の標準オブジェクトシステム(2)


Tcl/Tk8.6a1 から、TclOO が標準のオブジェクトシステムとしてコアパッケージに取り込まれましたが、その TclOO を利用したサンプルプログラムを紹介します。オブジェクト指向プログラミングの利点を効果的に活用しているかというと疑問ですが、とにかくいろいろなサンプルを不定期で紹介していきたいと考えています。

サンプル・プログラム


今回は、canvas ウィジェットにマウスで曲線を描画するサンプルです。Tk 入門の canvas の説明で紹介しているサンプルを TclOO を利用したプログラムに書き換えました。

#!/bin/sh
# the next line restarts using wish \
exec wish8.6 "$0" "$@"

package require TclOO; # ビルトインパッケージなので省略可
namespace import oo::*

# ----------------------------------------------------------------------------
# クラス Draw
# ----------------------------------------------------------------------------

class create Draw {
# ------------------------------------------------------------------------
# コンストラクタ
# ------------------------------------------------------------------------

constructor {} {
my variable conf
set conf(color) black
set conf(width) 1
}

# ------------------------------------------------------------------------
# 設定
# ------------------------------------------------------------------------

method init {color {width 1}} {
my variable conf
set conf(color) $color
set conf(width) $width
return
}

# ------------------------------------------------------------------------
# 描画起点の座標を取得
# ------------------------------------------------------------------------

method first {x y} {
my variable conf
set conf(x0) $x
set conf(y0) $y
return
}

# ------------------------------------------------------------------------
# 線を描画
# ------------------------------------------------------------------------

method line {w x y} {
my variable conf
$w create line $conf(x0) $conf(y0) $x $y \
-fill $conf(color) \
-width $conf(width) \
-tags mLine
my first $x $y
return
}
}

# ----------------------------------------------------------------------------
# メイン
# ----------------------------------------------------------------------------

wm title . "canvas"

set color_bg "#f0fff8"
set color_grid "#c0e0d0"
set color_draw "#804040"

# キャンバスの生成
set cw 200
set ch 200
canvas .can \
-width $cw \
-height $ch \
-borderwidth 0 \
-highlightthickness 0 \
-background $color_bg
pack .can

# 方眼
for {set y 0} {$y < $ch} {incr y 10} {
.can create line 0 $y $cw $y -fill $color_grid
}
for {set x 0} {$x < $cw} {incr x 10} {
.can create line $x 0 $x $ch -fill $color_grid
}

# クラス Draw のインスタンス pen を生成
set pen [Draw new]

# メソッド init で描画色を設定
$pen init $color_draw

# マウス左ボタンを .can 上で押した時、メソッド first で座標を取得
bind .can <Button-1> {$pen first %x %y}

# マウス左ボタンを押しながら .can 上を移動した時、メソッド line を実行
bind .can <B1-Motion> {$pen line %W %x %y}

# ---
# tkwidget_canvas_2.tcl




関連情報
[1] Tcl 入門編 - TclOO
 

2008-08-17

TclOO - Tcl の標準オブジェクトシステム


Tcl/Tk8.6a1 から、TclOO が標準のオブジェクトシステムとしてコアパッケージに取り込まれました。[TIP #257]
Incr Tcl/Tk や XOTcl など、従来から様々なオブジェクトシステム用の拡張パッケージがありましたが、最新の Tcl に対応しているかどうかや、開発が今後も確実に続けられているのかなどを気にする必要があり、あまり安心して使い続けられない状況でした。コアパッケージに取り込まれていれば、そういうことを心配しなくとも良いので安心です。

実は TclOO については存在を知っていましたが、使ってみたことがなかったので、この機に少し使ってみることにしました。
と言うより、今後は 8.6 系を中心に情報をまとめ、ビットウォーク版の Tcl/Tk パッケージなどを作っていこうかという考えに傾いています。8.6 はまだαバージョンということが気になるところですが、Tcl 8 系では最後のマイナーバージョンですし、8.5 よりは寿命が長そうですので、今のうちに準備を始めようとしています。
とりあえず、TclOO について(一部未完成ですが)以下にまとめてみました。

・TclOO

このサイト (FC2) は何のサイトかというと、以前 banana-milk.net で細々と維持していた Tcl/Tk のサイトを引き継いだサイトです。

FC2 のサイトのアカウントは、ずっと以前から持っていて、ほんの一時期、ビットウォークのメインサイトにも使用しておりました。しかし、当時は FTP でファイルをアップロードできないなど、使いずらい点が多々あり積極的にはほとんど使用していませんでした。

今回、banana-milk.net の閉鎖で、あらためて FC2 のサイトを見直したところ、改善が続けられており、しかもサイトが長きにわたって運営されてきていることから、ふたたびこのサイトを拠点にすることにしました。
と言っても、サイトの中身がまだ不完全なので、『リニューアル・オープン』をするのはもう少し先にしようと思っています。

とにかく、TclOO とはどんな感じのオブジェクトシステムか、ご興味のある方は一度上記サイトを覗いてみてください。


関連情報
[1] European Tcl/Tk User Meeting 2008 - Schedule
 

2008-08-01

入門 Tcl/Tk から十年

すぐりふひと(須栗歩人)の処女作、入門 Tcl/Tk の初版第一刷が出版されて、今月で十年になりました。本書は幸い何刷かの増刷を重ね、僅かばかりの印税が入りましたが、版を重ねることなく廃版となってしまっています。

しかし Tcl/Tk との付き合いは、少し疎遠になったとは言え、今も続いています。エンジニアとしてもっと手軽に GUI を扱いたいという欲求から Tcl/Tk に出会ったからでしょう。もしその時、職業プログラマとして仕事になるプログラミング言語を求めていたとしたら、Tcl/Tk は選択肢にも挙がらなかっただろうし、その結果、数多のプログラマが群がる人気プログラミング言語では執筆するチャンスにも恵まれなかったように思います。

チャンスがあれば、また書きたいものだといつも思っていますが、この手の本の寿命は驚くほど短いので、そこそこ売れて増刷や、二版、三版と改訂でも重ねないかぎり、すぐに書店から姿を消し、世間からは忘れ去られてしまいます。だいたい、Tcl/Tk なんてポピュラーなプログラミング言語ではないので、版を重ねるほどの需要はないような気もします。

せめて、インターネット上では情報を発信しつづけようと思っています。

参考サイト

  1. TIOBE Index - TIOBE

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ オープンソースへ
にほんブログ村

オープンソース - ブログ村ハッシュタグ
#オープンソース



このエントリーをはてなブックマークに追加

2008-02-26

C による Tcl/Tk 拡張 : カスタマイズした wish (3)

SourceForge.net Logo
今回は、Tcl における expr コマンドで利用できる関数を追加した拡張 wish, bwish を作ります。追加する関数は、サンプルとして立方根を計算する cbrt という C の関数を利用します。もし、お使いの Linux で最新の gcc(正確に言えば、glibc)の開発環境でない場合は念のため man cbrt でマニュアルが表示されるかどうか確認してください。利用できない場合は、適当に他の利用可能な関数に置き換えてください。MinGW クロスコンパイラの方は C99 に準拠していますので、cbrt を利用できます。

プログラム


プログラムのソースは、kwish をビルドした時と同じような構成をとります。すなわち bwish_main.c、bwish_sub.c および bwish.h です。

今回のプログラムでは、前回 kwish をビルドするときに利用した API、Tk_MainLoop を利用せず、代わりに Tk_Main を使いました。どのやり方が正しい、ということではありませんが、wish あるいは tclsh を拡張して、イベントループを扱うプログラムを作成する際には、Tk_Main あるいは Tcl_Main を利用する方法がポピュラーだと思います。

/* bwish_main.c */
#include "bwish.h"

/* type of math value definition */
Tcl_ValueType ArgDouble[] = { TCL_DOUBLE };

/*
* Tk_Main acts as the main program for most Tk-based applications.
* Starting with Tk 4.0 it is not called main anymore because it is part of
* the Tk library and having a function main in a library (particularly
* a shared library) causes problems on many systems. Having main in the Tk
* library would also make it hard to use Tk in C++ programs, since C++
* programs must have special C++ main functions.
*
* Normally each application contains a small main function that does nothing
* but invoke Tk_Main. Tk_Main then does all the work of creating and running
* a wish-like application.
*/

int
main (int argc, char *argv[])
{
Tk_Main (argc, argv, AppInit);
return EXIT_SUCCESS;
}

/*
* This procedure provides a “hook” for the application to perform its own
* initialization, such as defining application-specific commands.
* The procedure must have an interface that matches the type Tcl_AppInitProc:
*
* typedef int Tcl_AppInitProc(Tcl_Interp *interp);
*/

int
AppInit (Tcl_Interp * interp)
{
if (Tcl_Init (interp) == TCL_ERROR)
return TCL_ERROR;

if (Tk_Init (interp) == TCL_ERROR)
return TCL_ERROR;

/* Define application-specific commands and math functions here */
Tcl_CreateMathFunc (interp, "cbrt", 1, ArgDouble, func_cbrt,
(ClientData) NULL);

return TCL_OK;
}

/* bwish_main.c */

コメントに記載してある内容は Tcl/Tk のマニュアルからそのまま写しただけです。要は main 関数では Tk_Main を実行してイベントループ状態にするだけで、Tcl/Tk のカスタマイズに関わる処理は AppInit 関数に記述するようにします。

今回のサンプルで Tcl に追加する立方根の算術関数は、cbrt という名前で、その処理は、func_cbrt 関数が担います。この算術関数 cbrt では倍精度実数の引数がひとつ必要なので、Tcl_Value 型で TCL_DOUBLE を一つ持つ配列をあらかじめ定義しておきます。もし倍精度実数の引数が2つ必要な場合には、例えば次のようにします。

Tcl_ValueType ArgDouble2[] = { TCL_DOUBLE, TCL_DOUBLE };

なお、TCL_DOUBLE の他に TCL_INT, TCL_WIDE_INT あるいは TCL_EITHER を指定できます。

立方根の計算処理部である func_cbrt 関数は bwish_sub.c に記述しました。立方根の計算に関わる引数と計算結果は、インタープリタ interp に対しポインタでやりとりをします。func_cbrt 関数は int 型で、計算の成否を TCL_OK (= 0) あるいは TCL_ERROR (= 1) で返すだけです。

/* bwish_sub.c */
#include "bwish.h"

/*****************************************************************************
* int func_cbrt
* 拡張数学関数 3√x(立方根)の処理部
*
* Tcl 側のコマンド
* expr cbrt($n)
*****************************************************************************/

int
func_cbrt (ClientData clientData, Tcl_Interp * interp,
Tcl_Value * args, Tcl_Value * resultPtr)
{
double x;

x = args->doubleValue;

/* 計算結果 */
resultPtr->type = TCL_DOUBLE;
resultPtr->doubleValue = cbrt (x);

return TCL_OK;
}

/* bwish_sub.c */

インクルードファイル bwish.h には、このプログラムで使用するインクルードファイルやプロトタイプ関数宣言をまとめました。

/* bwish.h */
#if !defined(BWISH_H)
#define BWISH_H

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <tcl.h>
#include <tk.h>

/* prototype functions */
extern int AppInit (Tcl_Interp *);
extern int func_cbrt (ClientData, Tcl_Interp *, Tcl_Value *, Tcl_Value *);

#endif /* BWISH_H */
/* bwish.h */

Windows 用のビルドでは、アイコンをリソースとして用意しました。リソーススクリプト bwish.rc は、Tcl/Tk のソースファイルにある win/tclsh.rc などをベースにしています。なおブログの表示の関係で、80文字を越える行は80文字で改行しています。

// bwish.rc
// RCS: @(#) $Id: bwish.rc,v 1.2 2008/02/25 10:48:08 bitwalk Exp bitwalk $
//
// Version Resource Script
//


#include <winver.h>
#include <tcl.h>

//
// build-up the name suffix that defines the type of build this is.
//

#if TCL_THREADS
#define SUFFIX_THREADS "t"
#else
#define SUFFIX_THREADS ""
#endif

#if STATIC_BUILD
#define SUFFIX_STATIC "s"
#else
#define SUFFIX_STATIC ""
#endif

#if DEBUG && !UNCHECKED
#define SUFFIX_DEBUG "g"
#else
#define SUFFIX_DEBUG ""
#endif

#define SUFFIX SUFFIX_THREADS SUFFIX_STATIC SUFFIX_DEBUG

LANGUAGE 0x9, 0x1 /* LANG_ENGLISH, SUBLANG_DEFAULT */

VS_VERSION_INFO VERSIONINFO
FILEVERSION TCL_MAJOR_VERSION,TCL_MINOR_VERSION,TCL_RELEASE_LEVEL,TCL_RELEASE
_SERIAL
PRODUCTVERSION TCL_MAJOR_VERSION,TCL_MINOR_VERSION,TCL_RELEASE_LEVEL,TCL_RELEA
SE_SERIAL
FILEFLAGSMASK 0x3fL
#ifdef DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS__WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "FileDescription", "Wish Application\0"
VALUE "OriginalFilename", "bwish" STRINGIFY(TCL_MAJOR_VERSION) STRI
NGIFY(TCL_MINOR_VERSION) SUFFIX ".exe\0"
VALUE "CompanyName", "bitWalk Co., Ltd.\0"
VALUE "FileVersion", TCL_PATCH_LEVEL
VALUE "LegalCopyright", "Copyright \251 2008 by bitWalk Co., Ltd.,
et al\0"
VALUE "ProductName", "bWish " TCL_VERSION " for Windows\0"
VALUE "ProductVersion", TCL_PATCH_LEVEL
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END

//
// Icon
//


bwish ICON DISCARDABLE "bwish.ico"

// bwish.rc

適当なアイコンファイル bwish.ico が必要になります。下の関連資料に示した素材サイトなどからダウンロードして適当な ico ファイルを bwish.ico と名前を変えて使うか、アイコン作成ツールで用意してください。

関連資料で紹介した素材サイトは、利用条件が明記されていたので載せました。他の多くのサイトでもアイコン素材が公開されていますので、興味があれば検索してみてください。

最後に、Linux 用と Windows 用クロスコンパイルのメイクファイルを示します。

# Makefile

CC = gcc
RM = rm -f

TARGET = bwish

OBJEXT = o
TARGETEXT =

DEFS =
INCLUDE = -I/usr/include
CFLAGS = -Wall -O2 -std=c99 $(DEFS) $(INCLUDE)
LDFLAGS = -L/usr/lib
TK_LIBS = -ltk -ltcl
LIBS = $(TK_LIBS) -lX11 -lm

OBJS = \
bwish_main.$(OBJEXT) \
bwish_sub.$(OBJEXT)

all: $(TARGET)$(TARGETEXT)

$(TARGET)$(TARGETEXT): $(OBJS)
$(CC) -o $@ $(OBJS) $(LDFLAGS) $(LIBS)

.SUFFIXES: .$(OBJEXT)
.c.$(OBJEXT):
$(CC) -c $(CFLAGS) $< -o $@

$(OBJS): $(TARGET).h

clean:
$(RM) $(TARGET)$(TARGETEXT) $(OBJS) *~

# Makefile

MinGW クロスコンパイル環境でビルドするためのメイクファイル Makefile.win では、windres でリソースをコンパイルしますが、出力の拡張子は .res.o にしています。
なお、このサンプルでは、拡張した expr コマンドの算術関数の動作を確認するために、敢えて -mconsole スイッチをつけてリンクするようにしています。

# Makefile.win

PREFIX = i386-mingw32-
CC = $(PREFIX)gcc
RC = $(PREFIX)windres
RM = rm -f

TARGET = bwish

OBJEXT = o
TARGETEXT = .exe
RES = res.o
TARGET_RES = bwish.$(RES)

DEFS =
INCLUDE = -I/usr/local/i386-mingw32/include
CFLAGS = -Wall -O2 -std=c99 $(DEFS) $(INCLUDE)
LDFLAGS = -L/usr/local/i386-mingw32/lib
TK_LIBS = -ltk -ltcl
WIN32_LIBS = -lws2_32 -lgdi32 -lcomdlg32 -limm32 \
-lcomctl32 -lshell32 -luuid -lole32 -loleaut32
LDFLAGS_CONSOLE = -mconsole
LDFLAGS_WINDOW = -mwindows
LIBS = $(TK_LIBS) $(WIN32_LIBS) $(LDFLAGS_CONSOLE)

OBJS = \
bwish_main.$(OBJEXT) \
bwish_sub.$(OBJEXT) \
$(TARGET_RES)

all: $(TARGET)$(TARGETEXT)

$(TARGET)$(TARGETEXT): $(OBJS)
$(CC) -o $@ $(OBJS) $(LDFLAGS) $(LIBS)

.SUFFIXES: .$(OBJEXT)
.SUFFIXES: .$(RES)
.SUFFIXES: .rc

.c.$(OBJEXT):
$(CC) -c $(CFLAGS) $< -o $@

.rc.$(RES):
$(RC) -o $@ $<

$(OBJS): $(TARGET).h

clean:
$(RM) $(TARGET)$(TARGETEXT) $(OBJS) *~

# Makefile.win


ビルドと実行


Linux ではビルドして、bwish を実行すると、wish を実行したときと同様に、トップレベルウィジェットが表示され、コンソール上は % のプロンプトが表示され入力待ちになります。

$ make
gcc -c -Wall -O2 -std=c99 -I/usr/include bwish_main.c -o bwish_main.o
gcc -c -Wall -O2 -std=c99 -I/usr/include bwish_sub.c -o bwish_sub.o
gcc -o bwish bwish_main.o bwish_sub.o -L/usr/lib -ltk -ltcl -lX11 -lm
$ ./bwish
% expr cbrt(27)
3.0
%

Windows 用のクロスコンパイルでは、ビルド後に Tcl/Tk の bin ディレクトリへ bwish.exe をコピーして実行しても、コンソール上にプロンプトが表示されません。仕方がないので、ビルドしたバイナリを Windows へ移して動作確認をしました。コンソールは、コマンドプロンプトのウィンドウが表示されます。

$ make -fMakefile.win clean
rm -f bwish.exe bwish_main.o bwish_sub.o bwish.res.o *~
$ make -fMakefile.win
i386-mingw32-gcc -c -Wall -O2 -std=c99
-I/usr/local/i386-mingw32/include bwish_main.c -o bwish_main.o
i386-mingw32-gcc -c -Wall -O2 -std=c99
-I/usr/local/i386-mingw32/include bwish_sub.c -o bwish_sub.o
i386-mingw32-windres -o bwish.res.o bwish.rc
i386-mingw32-gcc -o bwish.exe bwish_main.o bwish_sub.o bwish.res.o
-L/usr/local/i386-mingw32/lib -ltk -ltcl -lws2_32 -lgdi32 -lcomdlg32 -limm32
-lcomctl32 -lshell32 -luuid -lole32 -loleaut32 -mconsole
$ cp bwish.exe ../mingw-tcltk-8.5.1-2/bin/
$ cd ../mingw-tcltk-8.5.1-2/bin/
$ ./bwish.exe
err:winedevice:ServiceMain driver L"Cdr4_2K" failed to load
err:winedevice:ServiceMain driver L"Cdralw2k" failed to load
fixme:font:WineEngCreateFontInstance Untranslated charset 255
fixme:comm:set_queue_size insize 4096 outsize 4096 unimplemented stub
fixme:imm:ImmReleaseContext (0x10026, 0x158f08): stub



このところ、立て続けにカスタマイズした wish のビルドの方法について説明をしてきましたが、いかがだったでしょうか。Tcl/Tk を拡張してアプリケーションを構築する際、wish や tclsh を直接拡張する方法の他に、拡張パッケージを作成し、標準の wish や tclsh からロードして拡張機能を利用する方法があります。拡張パッケージの作り方については、過去にホームページで掲載していた資料がありますので、それをベースに書き直して、あらためて現在のホームページの方に掲載しようと考えています。

関連情報


Tcl/Tk C Library
[1] Tcl Library
[2] Tk Library

ico ファイル素材&ツール
[1] [素材]Windows XP ファイル用アイコン
[2] @icon変換 - 画像とアイコンの相互変換ツール

C 言語
[1] プログラミング言語 C の新機能
 

2008-02-24

C による Tcl/Tk 拡張 : カスタマイズした wish (2)

SourceForge.net Logo

今回は、クロスコンパイラ編ということで、MinGW クロスコンパイラ環境で、Win32 用 Tcl/Tk の拡張パッケージをビルドしてみます。

コンパイル環境


コンパイル環境は、以下の通り MinGW Cross Compiler プロジェクトで公開している、コアパッケージと Tcl/Tk のパッケージを使用しています。なお、make は、Linux のものを使います。また、ビルド後に動作を確認するための wine もインストールしておきます。

Fedora 8 (i386)
mingw-binutils-2.18.50-3.fc8
mingw-gcc-core-4.2.1-4.fc8
mingw-runtime-3.14-2.fc8
mingw-w32api-3.11-2.fc8
mingw-tcltk-8.5.1-2.fc8
make-3.81-10.fc8
wine-core-0.9.55-1.fc8 と、依存しているパッケージ群

プログラム


プログラムのソースは、前回使用したものと同じ、kwish_main.c、kwish_sub.c および kwish.h です。ビルドに使用するメイクファイルには、以下の Makefile.win を使います。

# Makefile.win

PREFIX = i386-mingw32-
CC = $(PREFIX)gcc
RM = rm -f

TARGET = kwish

OBJEXT = .o
TARGETEXT = .exe

DEFS =
INCLUDE = -I/usr/local/i386-mingw32/include
CFLAGS = -Wall -O2 $(DEFS) $(INCLUDE)
LDFLAGS = -L/usr/local/i386-mingw32/lib
TK_LIBS = -ltk -ltcl
WIN32_LIBS = -lws2_32 -lgdi32 -lcomdlg32 -limm32 \
-lcomctl32 -lshell32 -luuid -lole32 -loleaut32
LDFLAGS_CONSOLE = -mconsole
LDFLAGS_WINDOW = -mwindows
LIBS = $(TK_LIBS) $(WIN32_LIBS) $(LDFLAGS_WINDOW)

OBJS = \
kwish_main$(OBJEXT) \
kwish_sub$(OBJEXT)

all: kwish$(TARGETEXT)

kwish$(TARGETEXT): $(OBJS)
$(CC) -o $@ $(OBJS) $(LDFLAGS) $(LIBS)

.c.$(OBJEXT):
$(CC) -c $(CFLAGS) $< -o $@

$(OBJS): kwish.h

clean:
$(RM) kwish$(TARGETEXT) $(OBJS) *~

# Makefile.win

リンク時に使用するマクロ WIN32_LIBS にセットしたライブラリのリストは、tk8.5.1 をビルドした時に使用した Makefile のマクロ LIB に記述されているものをそのままコピーしました。

ビルドと実行


Makefile.win を、ソースと同じディレクトリ内に保存し、make コマンドで kwish.exe をビルドします。

$ make -fMakefile.win clean
rm -f kwish.exe kwish_main.o kwish_sub.o *~
$ make -fMakefile.win
i386-mingw32-gcc -Wall -O2 -I/usr/local/i386-mingw32/include
-c -o kwish_main.o kwish_main.c
i386-mingw32-gcc -Wall -O2 -I/usr/local/i386-mingw32/include
-c -o kwish_sub.o kwish_sub.c
i386-mingw32-gcc -o kwish.exe kwish_main.o kwish_sub.o
-L/usr/local/i386-mingw32/lib -ltk -ltcl -lws2_32 -lgdi32 -lcomdlg32
-limm32 -lcomctl32 -lshell32 -luuid -lole32 -loleaut32 -mwindows
$ ./kwish.exe
err:module:import_dll Library tcl85.dll (which is needed by
L"Z:\\home\\bitwalk\\work\\kwish\\kwish.exe") not found
err:module:import_dll Library tk85.dll (which is needed by
L"Z:\\home\\bitwalk\\work\\kwish\\kwish.exe") not found
err:module:LdrInitializeThunk Main exe initialization for
L"Z:\\home\\bitwalk\\work\\kwish\\kwish.exe" failed, status c0000135


Linux 用にビルドした kwish の時と同じようにして kwish.exe を実行しようとしても、Tcl/Tk の DLL が見つからないというエラーメッセージが出て実行できません。念のため objdump で kwish.exe がリンクしている DLL を調べてみましょう。

$ i386-mingw32-objdump -p kwish.exe | grep "DLL Name"
DLL Name: KERNEL32.dll
DLL Name: msvcrt.dll
DLL Name: tcl85.dll
DLL Name: tk85.dll

実を言うと、この拡張 wish を実行させるためには、tcl85.dll と tk85.dll の他に、wish アプリケーションを初期化する Tcl スクリプト init.tcl が必要になります。そのため、仮に Tcl/Tk のスタティックライブラリを利用してビルドして DLL に依存しないようにしても、拡張 wish を単体で動作させることはできません。しかし、とりあえず動作確認をするだけであれば、次のようにして実行させることができます。

$ cd /usr/local/i386-mingw32/bin
$ /home/bitwalk/work/kwish/kwish.exe
fixme:font:WineEngCreateFontInstance Untranslated charset 255
fixme:imm:ImmReleaseContext (0x10026, 0x158190): stub


wine を利用した Windows 版 kwish の実行例

この例では、/home/bitwalk/work/kwish/kwish.exe が、ビルドした拡張 wish です。

MinGW Cross Compiler プロジェクトのサイトでは、RPM パッケージ用にクロスコンパイルした Tcl/Tk のインストールファイルを zip ファイルにしてアップしてありますので、これを利用する方法もあります。[LIB] tcltk から、ここでは mingw-tcltk-8.5.1-2.zip をダウンロードします。

Linux 上では、ダウンロードした zip ファイルを次のように展開します。次に bin ディレクトリ内に、先ほどビルドした kwish.exe をコピーして実行します。

$ unzip mingw-tcltk-8.5.1-2.zip
Archive: mingw-tcltk-8.5.1-2.zip
creating: mingw-tcltk-8.5.1-2/
creating: mingw-tcltk-8.5.1-2/include/
creating: mingw-tcltk-8.5.1-2/bin/
creating: mingw-tcltk-8.5.1-2/lib/
inflating: mingw-tcltk-8.5.1-2/include/tk.h
:
(snip)
:
inflating: mingw-tcltk-8.5.1-2/lib/reg1.2/tclreg12.dll
inflating: mingw-tcltk-8.5.1-2/lib/reg1.2/pkgIndex.tcl
finishing deferred symbolic links:
mingw-tcltk-8.5.1-2/lib/libtk.dll.a -> /usr/local/i386-mingw32/lib/libtk85.a
mingw-tcltk-8.5.1-2/lib/libtcl.dll.a -> /usr/local/i386-mingw32/lib/libtcl85.a
$ cd mingw-tcltk-8.5.1-2/bin
$ cp ~/work/kwish/kwish.exe ./
$ ./kwish.exe
fixme:font:WineEngCreateFontInstance Untranslated charset 255
fixme:comm:set_queue_size insize 4096 outsize 4096 unimplemented stub
fixme:imm:ImmReleaseContext (0x10026, 0x158178): stub

Windows 上でも同様に、ダウンロードした zip ファイルを適当な解凍ツールで展開後、kwish.exe を bin フォルダ内へ移し、ダブルクリックして起動します。

Windows Vista における Windows 版 kwish の実行例

mingw-tcltk のインストールファイルは、kwish.exe を実行するための最小セットではありません。目的に応じて、不必要なファイルを削除してください。なお、単独のファイルで Tcl/Tk のアプリケーションを実行したい場合には、以下の関連情報を参照してみてください。

関連情報


[1] Starkit deployment technology
[2] freewrap
 

2008-02-22

C による Tcl/Tk 拡張 : カスタマイズした wish (1)


昨日 koji さんから、Linux 上の MinGW クロスコンパイラ環境を利用した、C による Tcl/Tk 拡張に関するご質問を、コメントとして頂きました【コメント】。いい機会ですので、C による Tcl/Tk の拡張の話題を、クロスコンパイルする話題に絡めて、しばらく取り上げていきたいと思います。
最初の話題として、koji さんがコメント欄に掲載したサンプルをベースに、Linux における Tcl/Tk の拡張例を紹介したいと思います。

koji さんが掲載したサンプルは、Tcl のスクリプトを wish に埋め込んだ wish アプリケーションです。Tcl/Tk では、標準で wish (windowing shell) という Tcl のコマンドシェルが利用できますが、Tcl/Tk の C API を利用して、標準の wish とは別に、カスタマイズした wish を作成できます。
カスタマイズできることは、koji さんが示したサンプルのように、スクリプトを埋め込んで、wish アプリケーションを即起動できるようにしたり、あるいは Tcl のコマンドを追加、あるいは拡張して、標準の wish にない機能を備えた拡張 wish をビルドすることです。

ここでは koji さんのサンプルコードをベースにして、kwish という名前の、スクリプトが埋め込まれた拡張 wish をビルドしてみましょう。

コンパイル環境


コンパイル環境は、以下の通り Fedora 8 に収録されている Tcl/Tk のパッケージを使用しています。なお、一般的な gcc の開発環境が整っていることを前提にしています。

Fedora 8 (i386)
tcl-8.4.17-1.fc8
tcl-devel-8.4.17-1.fc8
tk-8.4.17-2.fc8
tk-devel-8.4.17-2.fc8

プログラム


まず main 関数を含んだソース kwish_main.c を示します。このプログラムは、Tcl/Tk のインタープリタを初期化し、Tcl のスクリプトを処理する関数 app_custom を呼び出したあとに、Tk_MainLoop を実行して GUI のためのイベントループ状態に入ります。

/* kwish_main.c */
#include "kwish.h"

int
main (int argc, char *argv[])
{
Tcl_Interp *interp;

interp = Tcl_CreateInterp ();
Tcl_FindExecutable (argv[0]);

if (Tcl_Init (interp) != TCL_OK)
{
fprintf (stderr, "FATAL: Tcl initialization error: %s\n",
Tcl_GetStringResult (interp));
return EXIT_FAILURE;
}

if (Tk_Init (interp) != TCL_OK)
{
fprintf (stderr, "FATAL: Tk initialization error: %s\n",
Tcl_GetStringResult (interp));
return EXIT_FAILURE;
}

if (app_custom (interp) != TCL_OK)
{
printf ("%s\n", "Program End with script error!");
return EXIT_FAILURE;
}

Tk_MainLoop ();

return EXIT_SUCCESS;
}
/* kwish_main.c */

次の kwish_sub.c では、kwish を起動したときに実行するスクリプトを処理する関数 app_custom を記述しています。koji さんのサンプルでは、定義したスクリプトを UTF-8 にエンコードしています。これはスクリプトに日本語文字など ASCII 以外の文字を含み、かつ、(MS Windows のように)システムのエンコーディングが UTF-8 と異なる場合に役に立ちます。今回使用するスクリプトでは関係がありませんが、そのまま残しました。

/* kwish_sub.c */
#include "kwish.h"

int
app_custom (Tcl_Interp * interp)
{
Tcl_DString ds;
char script[] = "\n\
wm title . {kwish}\n\
label .1lbl -text {Enter your name (and click OK):}\n\
entry .ent2\n\
button .cmd3 -text OK -command ok_clicked\n\
button .cmd4 -text {Exit this program} -command exit\n\
pack .1lbl -anc w\n\
pack .ent2 -fill x\n\
pack .cmd3 -fill x\n\
pack .cmd4 -fill x\n\
proc ok_clicked {} {\n\
tk_messageBox -message \"[.ent2 get], you are here!\"\n\
}\n\
";
char *script_utf = Tcl_ExternalToUtfDString (NULL, script, -1, &ds);

if (Tcl_Eval (interp, script_utf) != TCL_OK)
{
fprintf (stderr, "Error: %s\n", Tcl_GetStringResult (interp));
return TCL_ERROR;
}

Tcl_DStringFree (&ds);

return TCL_OK;
}
/* kwish_sub.c */

インクルードファイル kwish.h には、このプログラムで使用するインクルードファイルやプロトタイプ関数宣言をまとめました。

/* kwish.h */
#if !defined(KWISH_H)
#define KWISH_H

#include <stdio.h>
#include <stdlib.h>
#include <tcl.h>
#include <tk.h>

extern int app_custom (Tcl_Interp *);

#endif /* KWISH_H */
/* kwish.h */

最後に Makefile を示します。リンクでは意図的に、-ltcl -ltk として、Tcl/Tk のバージョンに依存しないようにしました。そのため、tcl-devel と tk-devel パッケージがビルド時に必要になります。
【注意】本ブログ上では、Makefile におけるコマンド行の先頭部分のタブが表現できないので、コピーして利用する場合はタブ文字に変更してください。

# Makefile

CC = gcc
RM = rm -f

TARGET = kwish

OBJEXT = .o
TARGETEXT =

DEFS =
INCLUDE = -I/usr/include
CFLAGS = -Wall -O2 $(DEFS) $(INCLUDE)
LDFLAGS = -L/usr/lib
TK_LIBS = -ltk -ltcl
LIBS = $(TK_LIBS) -lX11 -lm

OBJS = \
kwish_main$(OBJEXT) \
kwish_sub$(OBJEXT)

all: kwish$(TARGETEXT)

kwish$(TARGETEXT): $(OBJS)
$(CC) -o $@ $(OBJS) $(LDFLAGS) $(LIBS)

.c.$(OBJEXT):
$(CC) -c $(CFLAGS) $< -o $@

$(OBJS): kwish.h

clean:
$(RM) kwish$(TARGETEXT) $(OBJS) *~

# Makefile

ビルドと実行


以上のソースファイルと Makefile を、同じディレクトリ内に保存し、make コマンドで kwish をビルドします。以下に実行例を示しました。

$ make
gcc -Wall -O2 -I/usr/include -c -o kwish_main.o kwish_main.c
gcc -Wall -O2 -I/usr/include -c -o kwish_sub.o kwish_sub.c
gcc -o kwish kwish_main.o kwish_sub.o -L/usr/lib -ltk -ltcl -lX11 -lm
$ ./kwish


kwish の実行例

このように、簡単に wish にスクリプトを埋め込んだ、拡張 wish をビルドすることができます。次のステップとして、Linux 上の MinGW クロスコンパイル環境下で、同じソースファイルを使って、Winodws 用のバイナリをビルドしてみたいと思います。


wine を利用した Windows 版 kwish の実行例

参考資料


[1] Tcl Library
[2] Tk Library
 

2008-01-03

ttk::progressbar


今回は Ttk の話題に戻り、ttk::progressbar を紹介します。このウィジェットは、時間が掛かる処理の進捗具合を表示するウィジェットです。



#!/bin/sh
# the next line restarts using wish \
exec wish "$0" "$@"


# ----------------------------------------------------------------------------
# dummy work
# ----------------------------------------------------------------------------

proc dummyStart {varName} {
upvar #0 $varName x; # widget variable has gloval scope
for {set i 1} {$i <= 100} {incr i} {
set x $i
update
after 100
}
}

# ----------------------------------------------------------------------------
# MAIN
# ----------------------------------------------------------------------------

wm title . "ttk::progressbar"
ttk::style theme use "clam"

ttk::progressbar .p1 -length 200 -variable p -mode determinate
ttk::progressbar .p2 -length 200 -variable p -mode indeterminate
ttk::button .start -text "START" -command {dummyStart "p"}

grid .p1 -pady 5 -padx 10
grid .p2 -pady 5 -padx 10
grid .start -padx 10 -pady 5 -sticky news
grid columnconfigure . all -weight 1

# ---
# ttkwidget_progressbar.tcl


拡張パッケージ Incr Widgets の feedback ウィジェットでも同じような機能を実現できます。以下に簡単な例を示しました。



#!/bin/sh
# the next line restarts using wish \
exec wish "$0" "$@"


package require Iwidgets

# ----------------------------------------------------------------------------
# dummy work
# ----------------------------------------------------------------------------

proc dummyStart {w} {
$w reset
for {set i 0} {$i < 100} {incr i} {
$w step
after 100
}
}

# ----------------------------------------------------------------------------
# MAIN
# ----------------------------------------------------------------------------

wm title . "iwidgets::feedback"

iwidgets::feedback .fb \
-labeltext " percentage of completion " \
-steps 100 \
-barheight 10

button .start -text "START" \
-command {dummyStart .fb}

pack .fb -padx 4 -pady 4
pack .start -padx 4 -pady 4
# ---
# iwidgets_feedback.tcl

2008-01-02

Tcl 8.5 の任意精度計算


このところ、立て続けに Tcl/Tk 8.5.0 の新機能を紹介していますが、今回は、Tcl の任意精度計算について紹介します。
Tcl 8.5.0 から、整数計算を任意精度でできるようになりました。任意精度計算処理を行うエンジンには、libtomath (LTM) というライブラリが採用されています。

任意精度計算ライブラリというと、GMP が有名ですが、LGPL で配布されているため、Tcl のライセンスを変更せずにはこのライブラリを使用できません。しかし LTM はパブリック・ドメインのソフトウェアとして配布されており、ドキュメントもよく整備されていることから Tcl のコアに採用されたようです。詳細は以下をご覧下さい。

TIP #237: Arbitrary-Precision Integers for Tcl

ためしに、簡単な計算をしてみました。



任意精度の整数を扱えると何が嬉しいかと言うと…、いろいろあるとは思いますが、すぐに思いつくのは公開鍵暗号に関わる整数処理などでしょうか。

2008-01-01

ttk::notebook


今回は、テーマ・ウィジェット Ttk の ttk::notebook を紹介します。

このウィジェットは、タブをクリックすることで複数のウィジェットの表示を切り替えることができます。
BWidget や Incr Widgets、BLT など多くの拡張パッケージでも同様なウィジェットが用意されていますが、それらのウィジェットと比べると、この ttk::notebook には比較的シンプルな機能しか備わっていません。考えようによれば逆に使いやすいかもしれません。簡単なサンプルを以下に示しました。


ttk::notebook


#!/bin/sh
# the next line restarts using wish \
exec wish "$0" "$@"


# ----------------------------------------------------------------------------
# add a tab to notebook widget
# ----------------------------------------------------------------------------

proc addTab {w n} {
set m "${w}.tab${n}"
set tlab "TAB ${n}"

ttk::frame $m
$w add $m -text $tlab

return $m
}

# ----------------------------------------------------------------------------
# MAIN
# ----------------------------------------------------------------------------

wm title . "tk::notebook"
ttk::style theme use "clam"

# make the notebook
ttk::notebook .note
pack .note -fill both -expand 1 -padx 2 -pady 2

# TAB 1
set tab1 [addTab .note 1]
$tab1 configure -width 200 -height 200

# TAB 2
set tab2 [addTab .note 2]

# TAB 3
set tab3 [addTab .note 3]

# ---
# ttkwidget_notebook.tcl


変数 tab1, tab2, tab3 は各タブのフレーム・ウィジェットです。このフレーム上に表示したい子ウィジェットを配置します。

参考として、拡張パッケージの BWidget の NoteBook ウィジェットを使った同等なサンプルを示しました。ただし、タブの位置を下にしました。個人的には好んでタブの位置を下にしますが、Ttk の ttk::notebook では、残念ながらタブの位置を指定するオプションが用意されていません。


NoteBook ウィジェット(Bwidget)


#!/bin/sh
# the next line restarts using wish \
exec wish "$0" "$@"


package require BWidget

# ----------------------------------------------------------------------------
# MAIN
# ----------------------------------------------------------------------------

wm title . "NoteBook"

# make the NoteBook
NoteBook .nb -side bottom
pack .nb

# TAB 1
set tab1 [.nb insert end 1 -text "TAB 1"]
pack [frame $tab1.f -width 200 -height 200]

# TAB 2
set tab2 [.nb insert end 2 -text "TAB 2"]

# TAB 3
set tab3 [.nb insert end 3 -text "TAB 3"]

# show TAB 1
.nb raise 1

# ---
# bwidget_notebook.tcl

2007-12-31

ttk::treeview


前回、Tcl/Tk 8.5.0 で追加されたテーマ・ウィジェットを紹介しましたが、このテーマ・ウィジェット Ttk では、Tk にないウィジェットも利用できます。
ttk::treeview はそのうちのひとつです。 今回は、Tk のデモプログラムをベースに、単体で動作するように修正したサンプルを紹介します。このサンプルは、ルートディレクトリ以下のファイルの階層構造をツリー状に表示します。

#!/bin/sh
# the next line restarts using wish \
exec wish "$0" "$@"


# ----------------------------------------------------------------------------
# Code to populate the roots of the tree (can be more than one on Windows)
# ----------------------------------------------------------------------------

proc populateRoots {tree} {
foreach dir [lsort -dictionary [file volumes]] {
set node [$tree insert {} end -text $dir -values [list $dir directory]]
populateTree $tree $node
}
}

# ----------------------------------------------------------------------------
# Code to populate a node of the tree
# ----------------------------------------------------------------------------

proc populateTree {tree node} {
if {[$tree set $node type] ne "directory"} return

set path [$tree set $node fullpath]
$tree delete [$tree children $node]

foreach f [lsort -dictionary [glob -nocomplain -dir $path *]] {
set type [file type $f]
set id [$tree insert $node end \
-text [file tail $f] \
-values [list $f $type]]

if {$type eq "directory"} {
# Make it so that this node is openable
$tree insert $id 0 -text dummy ;# a dummy
$tree item $id -text [file tail $f]
} elseif {$type eq "file"} {
set size [file size $f]
# Format the file size nicely
if {$size >= 1024*1024*1024} {
set size [format %.1f\ GB [expr {$size/1024/1024/1024.}]]
} elseif {$size >= 1024*1024} {
set size [format %.1f\ MB [expr {$size/1024/1024.}]]
} elseif {$size >= 1024} {
set size [format %.1f\ kB [expr {$size/1024.}]]
} else {
append size " bytes"
}
$tree set $id size $size
}
}

# Stop this code from rerunning on the current node
$tree set $node type processedDirectory
}

# ----------------------------------------------------------------------------
# MAIN
# ----------------------------------------------------------------------------

wm title . "Directory Browser"
ttk::style theme use "clam"

# Create the tree and set it up
ttk::treeview .tree \
-columns {fullpath type size} \
-displaycolumns {size} \
-yscroll ".vsb set" \
-xscroll ".hsb set"

if {[tk windowingsystem] ne "aqua"} {
ttk::scrollbar .vsb -orient vertical -command ".tree yview"
ttk::scrollbar .hsb -orient horizontal -command ".tree xview"
} else {
scrollbar .vsb -orient vertical -command ".tree yview"
scrollbar .hsb -orient horizontal -command ".tree xview"
}

.tree heading \#0 -text "Directory Structure"
.tree heading size -text "File Size"
.tree column size -stretch 0 -width 70

populateRoots .tree
bind .tree <<TreeviewOpen>> {populateTree %W [%W focus]}

# Arrange the tree and its scrollbars in the toplevel
grid .tree -column 0 -row 0 -sticky news
grid .vsb -column 1 -row 0 -sticky ns
grid .hsb -column 0 -row 1 -sticky ew
grid columnconfigure . 0 -weight 1
grid rowconfigure . 0 -weight 1

# ---
# ttkwidget_treeview.tcl



Windows XP で実行した例




Fedora 8 (Linux) で実行した例


ちなみに階層構造を扱うウィジェットには、拡張パッケージ BWidget の Tree、Incr Widgets の hierarchy などが利用できます。