gtk2-perl #1

LinuxGNOME 環境用の GUIPerl で書きたいと突然思い立って勉強を始めますた。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 でツールをいっぱい作ってたので、ある程度は対応できると思うけど、WindowsLinux だと作法も違うと思うしはまる箇所もいっぱい出てくると思う。

会社の仕事も落ち着いて?きたので、勉強日記の更新頻度もがんばって上げていきます。

Parse::RecDescent 使ってみた

最近、字句解析やら構文解析に興味があるので、perl の強力なパーサ Parse::RecDescent を使ってみた。まずは簡単に足し算の文法を考えてみる。

バッカス・ナウア記法を使って書いていますが、勉強を始めたばかりなので誤りがあるかもしれません。

expression ::= atom + expression | atom
atom ::= [0-9]+
expression の定義
atom + expression もしくは atom
atom の定義
0〜9の1回以上の繰り返し


これを、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 は atomatom は 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の EmacsPerl の便利なやつ(メモし忘れた)は絶対使いたい
  • 転職活動したいなぁー


1日目から参加できなかったのが、激しくくやしい。懇親会に参加して、Perl ハカーさんと交流しようと目論んでたのに。YAPC 前日の帰る直前に仕事持ってこないでくらさい。

あれもこれもやろうとすると、あせってしまいそうですが、まずは非同期処理に的を絞って勉強しようと思う。

最後に、スタッフの皆様、本当にお疲れ様でした。