gtk2-perl #1
Linux の GNOME 環境用の GUI を Perl で書きたいと突然思い立って勉強を始めますた。GNOME 環境で GUI をやるときには GTK+ を使う。と言われてもなんのこっちゃという感じなので、自分なりの解釈で説明してみる。
GTK+ とはなんぞや
GNOME 環境で GUI なアプリを作るための ツールキット。Windows プログラミングをしたことがある人なら、MFC や Win32API を想像していただけると大体あってると思う。ウィジェットと呼ばれる部品(ボタンとか)を配置して見た目を作る。GTK+ 自体は C言語のツールキットだが、C++ や python 用のバインディングもある。当然今回は、Perl バインディングを使う。
シグナル駆動プログラミング
メッセージ駆動プログラミングやイベント駆動プログラミングとも言う。通常のプログラミングとは異なり、ウィジェットに対する操作を行い、シグナル受信をトリガとして処理を実行することになる。ちなみにシグナルとは大体こんな感じ。
- マウスがクリックされた
- 規定時間が経過した
- キーが入力された
など、他にもいっぱいあるけど。
GTK+ 自体の説明はこの辺にして、Hello, World!を表示し、ボタンをクリックすると終了する簡単なサンプル。
#!/usr/bin/env perl use strict; use warnings; use utf8; use Gtk2 -init; binmode STDOUT, ':utf8'; &main; sub on_destroy { Gtk2->main_quit; } sub on_clicked { Gtk2->main_quit; } sub main { my $window = Gtk2::Window->new('toplevel'); $window->signal_connect(destroy => \&on_destroy); $window->set_title('初めてのGtk2'); my $vbox = Gtk2::VBox->new(0, 10); $window->add($vbox); my $label = Gtk2::Label->new('Hello, World!'); $vbox->add($label); my $button = Gtk2::Button->new('close'); $button->signal_connect(clicked => \&on_clicked); $vbox->add($button); $window->set_border_width(5); $window->show_all(); Gtk2->main(); }
プログラムを実行するとこんな感じの Window が起動する。×ポンか close ボタンを押すとプログラムが終了する。
簡単にソースコードの説明。
コンテナウィジェット を作成する
my $window = Gtk2::Window->new('toplevel'); $window->signal_connect(destroy => \&on_destroy); $window->set_title('初めてのGtk2');
Gtk2::Window->new で コンテナウィジェット を作るためのオブジェクトを生成し、$window->signal_connect でシグナルのハンドラを設定する。ここでは、コンテナウィジェット が破棄されるシグナルに対してハンドラを設定し、Gtk2->main_quit を呼び出しメインループを終了させている。
パッキングボックスを作成し、ウィジェットを追加する
パッキングボックスとは、ウィジェットを配置するための箱。コンテナウィジェットには一つしかウィジェットを配置できない。複雑な GUI アプリを作成するのに必要になる。垂直パッキングボックスと水平パッキングボックスがある。
my $vbox = Gtk2::VBox->new(0, 10); $window->add($vbox); my $label = Gtk2::Label->new('Hello, World!'); $vbox->add($label); my $button = Gtk2::Button->new('close'); $button->signal_connect(clicked => \&on_clicked); $vbox->add($button); $window->set_border_width(5);
Gtk2::VBox 垂直パッキングボックスのオブジェクトである。$window->add($vbox) でコンテナウィジェットにパッキングボックスを追加している。以下、ラベルとボタンのウィジェットを作成して、パッキングボックスに追加している。
GUIを表示しメッセージループを開始する
$window->show_all();
Gtk2->main();
初回ということで、この辺でおしまいにする。これから勉強をしていく身なので、間違えたことも書いていると思うし、そういったことがあるとご指摘していただけるとありがたいです。作りたいものがあるけど、まずはウィジェットの操作、シグナル駆動のプログラミングに慣れていきたい。学生時代に MFC でツールをいっぱい作ってたので、ある程度は対応できると思うけど、Windows と Linux だと作法も違うと思うしはまる箇所もいっぱい出てくると思う。
会社の仕事も落ち着いて?きたので、勉強日記の更新頻度もがんばって上げていきます。
Parse::RecDescent 使ってみた
最近、字句解析やら構文解析に興味があるので、perl の強力なパーサ Parse::RecDescent を使ってみた。まずは簡単に足し算の文法を考えてみる。
バッカス・ナウア記法を使って書いていますが、勉強を始めたばかりなので誤りがあるかもしれません。
expression ::= atom + expression | atom atom ::= [0-9]+
これを、Parse::RecDescent を使って実装したのが、以下のソースコードとなる。
#!/usr/bin/env perl use strict; use warnings; use feature qw/say/; use Parse::RecDescent; my $grammar = <<'GRAMMAR'; expression : atom "+" expression { $return = $item[1] + $item[3]; } expression : atom { $return = $item[1]; } atom : /\d+/ { $return = $item[1]; } GRAMMAR my $parser = Parse::RecDescent->new($grammar) or die "Bad grammar!\n"; my $text = "5+6"; my $result = $parser->expression($text) or die "Bad text!\n"; say "$text=$result";
実行結果は以下の様になる。
5+6=11
理解している範囲で、ソースコードの説明をする。
- $grammar
- 文法を記述する。ここで出てくる expression, atom はルール(名)となる。ルールの定義の指定を行い、{}内では、そのルールに当てはまる時の動作を記述する。
- $return
- ルールに当てはまった時の結果が返される。
- @item
- ルールにマッチしたものが入る。
サンプルで使用した、5+6 で考えてみることにする。
expression のルールは、
expression : atom "+" expression { $return = $item[1] + $item[3]; }
と定義されており、atom が 5 に "+"が + に expression が 6 にそれぞれマッチする。
atom のルールは、
atom : /\d+/ { $return = $item[1]; }
と定義されており、5 はつまり atom となる。$item[1]に 5 が入り、expression ルールの atom に戻り、$item[1]に 5 が入る。$item[2]には + が入る。最後の 6 は、
expression : atom { $return = $item[1]; }
もう一つの expression のルールで expression は atom、atom は 0〜9の1回以上の繰り返しとなり、atom の $item[1]は expression の $item[1]となる。これにより 5+6 を得ることができ、最上位にある、expression の $result に 11という結果が入ることになる。
また、このルールは再帰的に適用されるので、5+6+7 でも定義さえ間違えなければ、問題なく動作するはずである。
もう少し深く勉強したいので、次回は足し算以外の演算を実装してみることにしよう。
AnyEvent で echo サーバ作ってみた
最近ちょくちょく勉強している、AnyEvent で簡単な echo サーバを作ってみた。
入力された文字を単純に返すだけだと面白くないので、入力した時の日時を表示するようにしてみた。
#!/usr/bin/env perl use strict; use warnings; use feature qw/say/; use DateTime; use AnyEvent; use AnyEvent::Socket; my $HOST = '127.0.0.1'; my $PORT = '18888'; tcp_server $HOST, $PORT, sub { my ($fh, $host, $port) = @_; say "connected $host:$port"; &add($fh); }; AnyEvent->condvar->recv; sub add { my $fh = shift; my $watcher; $watcher = AnyEvent->io( fh => $fh, poll => 'r', cb => sub { my $length = sysread $fh, my $buf, 256; if (! $length) { undef $watcher; return; } my $dt = DateTime->now(time_zone => 'Asia/Tokyo'); syswrite $fh, '[' . $dt->ymd('/') . ' ' . $dt->hms . "] $buf"; } ); }
echo サーバを起動する。
% perl ae-echo-server.pl
echo サーバに接続して、利用してみる。
% telnet 127.0.0.1 18888 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. amari3 [2010/04/05 00:35:53] amari3 AnyEvent [2010/04/05 00:35:58] AnyEvent hoge [2010/04/05 00:36:01] hoge foo [2010/04/05 00:36:04] foo baz [2010/04/05 00:36:05] baz echo server [2010/04/05 00:36:15] echo server ^]
簡単な echo サーバだったらこんなに短いソースコードで実現できてしまう。もっと勉強してなんか面白いものを作ってみたいですね。
Perl で QRcode を作ってみる
業務の都合上、Perl で QRcode を作る必要があったのでそのやり方をメモしとく。
GD::Barcode::QRcode を使って QRcodeを作成する
おそらくこのやり方が、事実上のデファクトスタンダードだと思う。
#!/usr/bin/perl use strict; use warnings; use GD::Barcode::QRcode; my $url = 'http://www.google.com'; my $qr = GD::Barcode::QRcode->new($url, { Ecc => 'M', Version => 3, ModuleSize => 2, })->plot; open my $fh, '>', "qr.gif" or die; print {$fh} $qr->gif; close $fh;
GD::Barcode::QRcode のオブジェクト生成時に指定するパラメータの説明です。
- Ecc
- エラー訂正レベル L(7%) M(15%) Q(25%) H(30%)
- Version
- QRcode のバージョン(1-40) バージョンが上がると使用可能な文字数等が増える
- ModuleSize
- ドットのサイズ(だと思う)
Imager::QRCode を使って QRcode を作成する
libqrencode というライブラリを入れるだけで動作します。GD を入れる必要はないです。
#!/usr/bin/perl use strict; use warnings; use Imager::QRCode; my $qrcode = Imager::QRCode->new( size => 2, margin => 2, version => 1, level => 'M', casesensitive => 1, lightcolor => Imager::Color->new(255, 255, 255), darkcolor => Imager::Color->new(0, 0, 0), ); my $img = $qrcode->plot("http://www.google.com"); $img->write(file => "qr2.gif");
QRcode の色を Imager::Color を使って自由に設定もできるみたいです。使い道があるかはわかりませんが、ちょっとうれしいかも。
QRcode::Barcode::QRcode と Imager::QRCode を取り上げてみたが、業務では前者を採用した。サーバの環境(ソフトウェアも含めて)が古いこともあったので、業務で導入実績のある、GD 系のモジュールを採用する方がいいと今回は判断した。いわゆる、モダンな環境なら後者でも全然ありだと思う。
AnyEvent でイベント駆動プログラミング
昨年の YAPC で非同期系のセッションが人気があったので、ちょっと遅いけど、手を出してみることにする。
AnyEvent を小2時間ほどいじってみたので、自分なりの解釈で記事を書くことにする。
イベント駆動プログラミングって何?
プログラムが上から下に実行される、いわゆる手続き型プログラミングとは違い、イベントハンドラにコールバック関数を登録しておき、イベントループを回します。イベント発生をトリガとして、イベントハンドラに登録されているコールバック関数を実行するプログラミング手法。
ちなみにイベントとは以下のようなことを指します。
- キーボード押された
- マウスがクリックされた
Windows プログラミングの経験者であれば、すんなり入っていくことができると思います。GetMessage のイベントループとか WindowProc とか例のやつです。僕も、Windows プログラミングの経験者だったので、すんなり入ることができました。
初めての AnyEvent
1秒毎に現在時刻を表示し、標準入力(STDIN)から「quit」が入力されるまで、監視をするプログラムのサンプルです。
#!/usr/bin/env perl use strict; use warnings; use feature qw/say/; use AnyEvent; # 1秒ごとに現在時刻を表示するイベント my $cvt = AnyEvent->condvar; my $t; $t = AnyEvent->timer( after => 0, interval => 1, cb => sub { say AnyEvent->time; $cvt->send; }, ); $cvt->recv; local $| = 1; # 標準入力から入力イベント # quit が入力されるとイベントループを抜ける IO: for (;;) { my $cv = AnyEvent->condvar; my $wait_for_input; $wait_for_input = AnyEvent->io( fh => \*STDIN, poll => 'rl', cb => sub { chomp(my $input = <STDIN>); undef $wait_for_input; $cv->send($input); } ); my $input = $cv->recv; say "[$input]"; last IO if defined $input && $input eq "quit"; } undef $t; say "done.";
ソースコードの前半では、タイマーのイベントを作成しています。interval で指定した秒数毎に登録してあるコールバック関数が呼び出される仕組みになっています。
ソースコードの後半では、標準入力を監視するイベントを作成しています。標準入力から入力があったときに、登録してあるコールバック関数が呼ばれる仕組みになっています。
ソースコード内に度々でてくる、AnyEvent->condvar は、状態変数と呼ばれるもので、メインループに対してイベントの状態を通知するための変数です。
$cv->send で状態変数を変更し、$cv->recv は状態変数が変更されるまで、つまり、$cv->send が呼び出されるまでコードをブロックしています。明快な説明じゃないですね。。ちゃんと説明できるようになったら修正しよう。
ちなみに実行してみるとこんな感じになります。
% perl ae3.pl 1269878022.06494 1269878023.06862 1269878024.0677 amar1269878025.06774 i3 [amari3] 1269878026.06878 1269878027.06878 quit [quit] done.
勉強を始めたばっかりで、説明に誤りがあるかもしれないけど、おもしろいので引き続き勉強していきます。
Crypt::RC4 試してみた
可逆暗号を使う必要があって色々調べてみた。Blowfish やら RC4 やらいっぱいあるけど、とりあえず、RC4 を試してみた。以下ソースコードです。
#!/usr/bin/perl use strict; use warnings; use feature qw/say/; use Crypt::RC4; my $key = 'amari3'; my $plaintext = 'hatenadiary'; my $rc4e = Crypt::RC4->new($key); my $rc4d = Crypt::RC4->new($key); my $encrypted = unpack('H*', $rc4e->RC4($plaintext)); my $decrypted = $rc4d->RC4(pack('H*', $encrypted)); say "encrypted: $encrypted"; say "decrypted: $decrypted";
実行結果はこれ
encrypted: 58b5b02a88ff42f5b2d4db decrypted: hatenadiary
調べてる内に楽しくなってきたので、他の暗号化も勉強しようと思う。
YAPC::Asia 2009 行ってきた
諸事情があって2日目のみの参加となったが、YAPC::Asia 2009(以下 YAPC) に行ってきた。
僕が参加したセッション一覧です。
- DeNA Loves Perl
- endeworks での WebApp の作り方
- OpenGL Programming with Perl
- Asynchronous Database Queries with Perl
- Asynchronous Programming for (A)synchtonous Communication
- (Parameternized) Roles
- Remedie: Building a desktop app using Perl, SQLite and jQuery
- 記憶
今年の YAPC は非同期の回と言っても過言じゃないほど、非同期に関するセッションが多かったと思う。tokuhirom さんの非同期での DB アクセスは涙ものでした。質問できなかったけど、Oracle は対応してるのかしら・・・。あとで調べておこう。
個人的に思ったことなどを箇条書きで。
- Test::FITesque が便利そう
- AnyEvent,Coro の非同期処理面白そう
- mobamail は仕事で早速使えそう
- LTの Emacs で Perl の便利なやつ(メモし忘れた)は絶対使いたい
- 転職活動したいなぁー
1日目から参加できなかったのが、激しくくやしい。懇親会に参加して、Perl ハカーさんと交流しようと目論んでたのに。YAPC 前日の帰る直前に仕事持ってこないでくらさい。
あれもこれもやろうとすると、あせってしまいそうですが、まずは非同期処理に的を絞って勉強しようと思う。
最後に、スタッフの皆様、本当にお疲れ様でした。