vrubyを使ってみました。
require 'vr/vruby' require 'vr/vrcontrol' require 'vr/vrhandler' class MyPanel < VRPanel def construct @i = -1 @colors = [RGB(0, 0, 0), nil] end include VRDrawable def self_paint 2.times do |i| drawLine(20*(i+2),20, 20*(i+2), 60) drawLine(20, 20*(i+2), 60, 20*(i+2)) end self.opaque = false font = @screen.factory.newfont("Terminal", 20) setFont(font) drawText("1", 20+5, 40 + 1, 40, 79) if @colors[@i] setBrush(@colors[@i]) else setBrush(RGB(0,0,0), SWin::BRUSH::NULL) end fillRect(40, 40, 60, 60) end include VRMouseFeasible def self_lbuttonup(shift, x, y) if 40 < x && x < 60 && 40 < y && y < 60 @i += 1 @i %= @colors.size refresh end end end class MyForm < VRForm def construct self.caption = "using vr" addControl(VRStatic, "msg", "message", 10, 10, 60, 20) addControl(MyPanel, "panel", "") end include VRResizeSensitive def self_resize(w, h) @panel.move(0, 0+30, w, h - 30) end end VRLocalScreen.start(MyForm, 200, 200, 250, 200)
swin だけ使っていたときよりも、簡単になってます。
まず、SWin::Window#addEvent メソッドを使う必要が無くなりました。 例えば、WM_PAINT イベントで処理していた部分は、
include VRDrawable def self_paint ... end
となりました。 WM_PAINT を処理したい場合は、"include VRDrawable" として "self_paint" メソッドを定義しておけば、いいのです。
WM_LBUTTONUP の処理で使った何だかよくわからない名前の(私には馴染みの名前ですが) lParam メソッドも消えてなくなり
include VRMouseFeasible def self_lbuttonup(shift, x, y) ... end
と素直に、x座標、y座標を取り出せるようになってます。 もうこれは、vruby側を使っていくしかありません。
と言いたいところなのですが、MyPanel#self_lbuttonup の中の refresh メソッドが気になります。
refreshメソッドを使うと、WM_PAINT イベントが発生して、画面全体が描画されます。 マウスでクリックした一つの四角の中だけ描画したいだけなのに画面全体が描画されてしまうと処理が遅くなりそうです。 画面がちらつきそうだし。 調べてみましたが、refresh_area というような画面の一部だけ再描画するメソッドは無いみたいです。
SWin::Canvas オブジェクトを生成して、stretchを使う方法を調べてみたいと思います。
そう言えば、今回みたいに、下のコントロールは親ウィンドウのサイズに合わせて目一杯大きくして 上のコントロールは大きさ固定というのは、VRVertLayoutManagerなど使ってもできるのでしょうか。 結局、よくわからなくて、WM_SIZEイベントを自前で処理するようにしちゃいました。
SWin::Window#setFont メソッドを使って指定します。 引数で指定するSWin::Fontオブジェクトは、SWin::LWFactory#newfont メソッドで生成します。
require 'swin' WM_PAINT=0x000F RFactory=SWin::LWFactory.new SWin::Application.hInstance $font1 = RFactory.newfont("Terminal", 10) $font2 = RFactory.newfont("Terminal", 20) $mw=RFactory.newwindow(nil) $mw.caption="label2" $mw.move(300,200,200,150) $mw.create.show $mw.addEvent WM_PAINT def $mw.msghandler(msg) $mw.setFont($font1) $mw.drawText("Terminal 10", 10, 10, 200, 30) $mw.setFont($font2) $mw.drawText("Terminal 20", 10, 50, 200, 80) end $mw.refresh SWin::Application.messageloop
線を引くには、SWin::Window#drawLineメソッドを使います。 引数は、始点のX座標、Y座標、終点の X座標、Y座標の4つを指定します。
require 'swin' WM_PAINT=0x000F RFactory=SWin::LWFactory.new SWin::Application.hInstance $mw=RFactory.newwindow(nil) $mw.caption="Line" $mw.move(300,200,200,200) $mw.create.show $mw.addEvent WM_PAINT def $mw.msghandler(msg) 4.times do |i| drawLine(20*(i+1),20, 20*(i+1), 80) drawLine(20, 20*(i+1), 80, 20*(i+1)) end end $mw.refresh SWin::Application.messageloop
SWin::Window#fillRectメソッドを使います。 色をつけるには、SWin::Window#setBrush で色を指定します。 四角の枠の色は、SWin::Window#setPen で指定します。 枠だけ色をつけるには、SWin::Window#setBrush の第二引数に SWin::BRUSH::NULLを指定します。
require 'swin' def RGB(r,g,b) return r+(g<<8)+(b<<16) end $red = RGB(0xFF, 0, 0) WM_PAINT=0x000F BS_NULL = SWin::BRUSH::NULL RFactory=SWin::LWFactory.new SWin::Application.hInstance $mw=RFactory.newwindow(nil) $mw.caption="rect" $mw.move(300,200,250,200) $mw.create.show $mw.addEvent WM_PAINT def $mw.msghandler(msg) setBrush($red) setPen($red) fillRect(20, 20, 40, 40) setBrush($red, SWin::BRUSH::NULL) fillRect(40, 20, 60, 40) end $mw.refresh SWin::Application.messageloop
マウスの左ボタンを押したイベントを拾うことにします。 押したというのは、マウスを押して放した瞬間です。 押し続けている間は、このイベントは発生しません。
514(WM_LBUTTONUP) を引数にして、SWin::Window#addEvent を呼び出します。 別に必要はないのですが、SWin::Window#msghandlerが呼ばれたときに WM_LBUTTONUPイベントが発生したのかどうかをチェックすることにします。 チェックは、SWin::MSG#msgメソッドの戻り値を調べることで確認できます。
ボタンが押された位置は、SWin::MSG#lParam を調べればわかります。 SWin::MSG#msg はわかるとしても、SWin::MSG#lParam って何やねん? という感じがするかも知れません。 この辺は、Cで書くWindowsプログラミングからの影響がもろに出ています。 WindowsプログラミングをCで書いたことのある人はピンと来るでしょう。 ここでは使いませんが、SWin::MSG#wParam というのもあります。
require 'swin' def LOWORD(lParam) return (lParam & 0xffff) end def HIWORD(lParam) return ( (lParam>>16) & 0xffff) end WM_LBUTTONUP=514 RFactory=SWin::LWFactory.new SWin::Application.hInstance $mw=RFactory.newwindow(nil) $mw.caption="mouse" $mw.move(300,200,250,200) $mw.create.show $mw.addEvent WM_LBUTTONUP def $mw.msghandler(msg) p msg.class if msg.msg == WM_LBUTTONUP puts("[x = #{LOWORD(msg.lParam)}, y = #{HIWORD(msg.lParam)}]") end end $mw.refresh SWin::Application.messageloop
と言いたいところだが、それは、もうちょっと後にすることにした。
swinしか使ってないのが気になっているから。 VisualuRubyには、Rubyスクリプトで実装されたswinのラッパーのライブラリが豊富にある。
今回の目的には、ボタンだのリストボックスだのを使う必要がないので、swinだけでも十分な気がしている。 その一方で、vr以下のライブラリを利用する方がいいんじゃないかと囁く声が聞こえてくる。 空耳かも知れないけど。
急ぐ必要がないので、囁き声に従って、vr以下のライブラリを少し触ってみることにした。
文字の色はSWin::Window#textColor= メソッドで指定し、 背景色は、SWin::Window#drawBkColor= メソッドで指定します。
require 'swin' def RGB(r,g,b) return r+(g<<8)+(b<<16) end $red = RGB(0xff, 0, 0) $blue = RGB(0, 0, 0xff) WM_PAINT=0x000F RFactory=SWin::LWFactory.new SWin::Application.hInstance $mw=RFactory.newwindow(nil) $mw.caption="label1" $mw.move(300,200,200,200) $mw.create.show $mw.addEvent WM_PAINT def $mw.msghandler(msg) $mw.textColor=$red $mw.drawBkColor=$blue drawText("Hello World",10,10,130,40) end $mw.refresh SWin::Application.messageloop
背景色を元と同じ色にするには、SWin::Window#opaque= メソッドにfalseを設定します。
def $mw.msghandler(msg) $mw.textColor=$red $mw.opaque = false drawText("Hello World",10,10,130,40) end
Makefile は、cygwin環境で作ってあんなことやこんなことやそんなことまでやって、 さらに、systemメソッドがうまく動かないので、have_library あたりは常にアウトで 拡張ライブラリなんかもボロボロなのだが、 無理矢理なことをして、手でごにょごにょすれば、win32ole.soもできるし ruby.exe もできた。
できるとこまでやっただけで、動かしてません。
あんなことや、こんなことや、そんなことや、手でごにょごにょは、あまりにも無茶苦茶なので、ここでは書きません。 書くの面倒くさいし。
最初は、できるかな、ぐらいの軽い気持ちだったのが、最後の方はかなり意地になってました。 やっている本人は結構楽しんでました。
どうやら、Windows上でRubyをコンパイルしたときに、いろいろコンパイルエラーが出たり、 こけたりする度に手でごにょごにょしていた昔を思い出し、ノスタルジーな気分に浸っていたみたいです。
そう言えば、あの頃も、こんなことやってたよなあ。みたいな。
私は、GTK+について、ずぶの素人なのだが、Windowsプログラミングに関しては、少々経験がある。 だから、こちらの方がすんなり使っていけるかも。
文字を出力するのは、drawTextを使います。
WM_PAINTイベントを自分で処理するよと宣言するために SWin::Window#addEventを呼び出します。 そうしておけば、SWin::Window#msghandler メソッドが勝手に呼び出されるようになるので、 その中で、drawTextで文字を表示してやります。
require 'swin' WM_PAINT=0x000F RFactory=SWin::LWFactory.new SWin::Application.hInstance $mw=RFactory.newwindow(nil) $mw.caption="label0" $mw.move(300,200,400,400) $mw.create.show $mw.addEvent WM_PAINT def $mw.msghandler(msg) drawText("Hello World",10,10,130,40) end $mw.refresh SWin::Application.messageloop
ちなみに、上のスクリプトは
VMWare上のWindows2000のCygwinでコンパイルしたmingw32版Ruby 1.9.0 (2005-02-17)
VMWare上のWindows2000のCygwinでmingw32版Rubyを使って extconf.rb を実行してコンパイルしたswin(05/01/17)
を使って、Vine Linux の Wine-20050211 上で動くことを確認しました。
えーっと。画面の最大化はできても、最小化ができないのは、確認した環境に問題があると思ってます。
多分、素直に、Windows 環境で、mswin32版 Ruby 1.8.2 を使って実行すれば、 ちゃんと最小化できるんじゃないかなと思います。
インストールしました。
tar zxvf Wine-20050211.tar.gz cd wine-20050211 ./tools/wineinstall
次に日本語フォントファイルをコピーしておきます。 ここでは、さざなみフォントを利用しました。
tar jxvf sazanami-20040629.tar.bz2 cp sazanami-20040629/*.ttf ~/.fonts
でもって、Sidenet wine configuration utilityを 使って setup します。
tar zxvf wine-config-sidenet-1.8.1.tgz cd wine-config-sidenet ./setup
途中でエラーが出ましたが、IEの起動には成功しました。 ただ、滅茶苦茶遅いので使い物になるかどうか疑問です。 Windows Media Player もインストールされたみたいですが、まだ試してません。
Wine上でRubyを動かしてみることにしました。
VMWare上のWindows2000上のCygwin環境でコンパイルしたmingw32版 Ruby を Wine 上にまるごとコピーしてみました。
wcmd
でコマンドプロンプトを立ち上げます。wcmd上で、
>set PATH=c:\usr\local\bin;%PATH% >ruby -v ruby 1.9.0 (2005-02-17) [i386-mingw32]
おお。バージョンは表示してくれるみたいです。
VisualuRubyをインストールするのを忘れてたので、 VMWare上のWindows2000上のCygwin環境に戻って swin.so を作ってから、 swin.so ファイルを Wine内の c:\usr\local\lib\ruby\site_ruby\1.9\i386-msvcrt にコピーしました。
vrのファイルは、いきなりWine上でもインストールできるかもと思い 試してみました。
>ruby vrinstall.rb
おおっ。画面全体が赤くなって、例のインストール画面がちゃんと立ち上がりました。 すんなりインストールできたみたい。
Simple OLE Browser soleb も動くかと試してみました。
>ruby soleb.rb
一応画面は出ます。ただ、リストボックスで項目を選択すると Wineがエラーメッセージを出して落ちてしまいます。
あとは、コンパイラなのですが、cygwinはインストールできませんでした。 setup.exeを動かすと画面はでるけど、ボタンが一切出ません。
cygwin版Rubyは動くかと思って試してみました。 Wine 上の c:\cygwin に cygwin1.dll と cygcrypt-0.dll と ruby.exe と cygruby19.dll を コピーします。 PATHを
>set PATH=c:\cygwin;%PATH%
としておいて
>ruby -v
とすると
err:comm:GetCommState tcgetattr or ioctl error 'Invalid argument' fixme:ntdll:NtQueryObject Unsupported information class 1 fixme:ntdll:NtQueryObject Unsupported information class 1 fixme:ntdll:NtQueryObject Unsupported information class 1 ruby 1.9.0 (2005-02-17) [i386-cygwin] fixme:ntdll:NtQueryInformationProcess (0x3c,0x00000003,0x425deb4c,0x0000002c,(nil)),stub!
エラーも出るけど、rubyも動かない訳ではないようなあるような...。
試しに optparse と erb を必要としないように手を加えた nono を動かすと 前後が同じエラーで挟まれるものの一応ののぐらむを解いて結果は正しく表示しました。
Mingw はインストールできるんだけど、Msys はインストールの最後で止まる。 どうも、sh.exe とか rxvt.exe は動かない。
Mingw の gcc は hello world と表示するプログラムぐらいはコンパイルできた。
Visual C++ Toolkit 2003はインストーラが動かなかった。
BCC 5.5.1 はインストールはできるものの、make の動きがおかしいのと batファイルがうまく動かないっぽくて、 Rubyをコンパイルできない。
Mingwを使って、VMWare上のWindows 2000の Cygwin 上で作成した mingw32版用のconfig.h や GNUMakefile Makefile などを流用すれば miniruby.exe は、何とかできた。 拡張ライブラリは、駄目。どうやら system メソッドが Wine上だと動かないっぽい。
うーん。あと、二、三歩足りない感じ。
準備してきたものを組み合わせてみました。 nono 0.0.6をリリースします。
Gtk::Window#set_events で Gdk::Event::BUTTON_PRESS_MASK を指定する。 Gtk::Window#signal_connectの "button_press_event" にてマウスのイベントを拾うことができる。 クリックされた位置は、イテレータブロックに渡される2番目の引数の x, y メソッドで取得できる。
Gtk.init win = Gtk::Window.new win.set_app_paintable(true) win.signal_connect("destroy") do Gtk.main_quit end win.set_events(Gdk::Event::BUTTON_PRESS_MASK) win.signal_connect("button_press_event") do |win, evt| puts "clicked #{evt.x} #{evt.y}" end win.realize win.show_all Gtk.main
Gtk::Window#set_events は、Gtk::Window#realizeを呼び出す前に呼び出さないといけない。
これで、好きな位置に好きな文字を表示し、線を描き、好きな色で四角を描き、マウスのイベントを拾う方法がわかった。 他にもこまごまとしたことは、いろいろあるが、主なネタはそろった。 後はこれを組み合わせるだけ。
Drawable#draw_rectangle を使う。 四角を塗りつぶす場合は、第2引数にtrueを、塗りつぶさない場合は、falseを指定する。 四角の色は、Gdk::GC#set_foregroundで指定しておく。
require 'gtk2' Gtk.init win = Gtk::Window.new win.set_app_paintable(true) win.realize draw = win.window gc = Gdk::GC.new(draw) win.signal_connect("destroy") do Gtk.main_quit end red = Gdk::Color.parse("#FF0000") colormap = Gdk::Colormap.system colormap.alloc_color(red, false, true) win.signal_connect("expose_event") do |win, evt| gc.set_foreground(red) draw.draw_rectangle(gc, true, 10, 40, 20, 20) end win.show_all Gtk.main
Pango::Layout#font_description= メソッドで Pango::FontDescriptionオブジェクトを指定すればよい。 Pango::FontDescriptionオブジェクトは、Pango::FontDescription.new で生成できる。
require 'gtk2' Gtk.init win = Gtk::Window.new win.set_app_paintable(true) win.realize draw = win.window gc = Gdk::GC.new(draw) l = win.create_pango_layout l.set_text("Hello World") win.signal_connect("destroy") do Gtk.main_quit end win.signal_connect("expose_event") do |win, evt| l.font_description=Pango::FontDescription.new("Sans 12") draw.draw_layout(gc, 10, 10, l) l.font_description=Pango::FontDescription.new("Courier 16") draw.draw_layout(gc, 10, 40, l) end win.show_all Gtk.main
Drawable#draw_lineを使う。
require 'gtk2' Gtk.init win = Gtk::Window.new win.set_app_paintable(true) win.realize draw = win.window gc = Gdk::GC.new(draw) win.signal_connect("destroy") do Gtk.main_quit end win.signal_connect("expose_event") do |win, evt| 4.times do |i| draw.draw_line(gc, 20*(i+1),20, 20*(i+1), 80) draw.draw_line(gc, 20, 20*(i+1), 80, 20*(i+1)) end end win.show_all Gtk.main
Gdk::Color オブジェクトを生成して、Drawable#draw_layoutの第5引数と第6引数で指定する。 第5引数が文字の色、第6引数が背景の色。文字の色だけ指定することはできない。
Gdk::Colorオブジェクトを生成するには、Gdk::Color.new メソッドを使う方法と Gdk::Color.parseを使う方法がある。
require 'gtk2' Gtk.init win = Gtk::Window.new win.set_app_paintable(true) win.realize draw = win.window gc = Gdk::GC.new(draw) l = win.create_pango_layout l.set_text("Hello World") red = Gdk::Color.new(65535, 0, 0) blue = Gdk::Color.parse("#0000FF") colormap = Gdk::Colormap.system colormap.alloc_color(red, false, true) colormap.alloc_color(blue, false, true) win.signal_connect("destroy") do Gtk.main_quit end win.signal_connect("expose_event") do |win, evt| draw.draw_layout(gc, 10, 10, l, red, blue) end win.show_all Gtk.main
背景色を元のウィンドウの色と同じにするには、Gtk::Style#bgメソッドを使う(みたい)。
win.signal_connect("expose_event") do |win, evt| draw.draw_layout(gc, 10, 10, l, red, win.style.bg(win.state)) end
Window の好きな位置に直接文字を表示するには、Drawable#draw_layout を使う。 draw_text が無くて悩んでしまった。
require 'gtk2' Gtk.init win = Gtk::Window.new win.set_app_paintable(true) win.realize draw = win.window gc = Gdk::GC.new(draw) l = win.create_pango_layout l.set_text("Hello World") win.signal_connect("destroy") do Gtk.main_quit end win.signal_connect("expose_event") do |win, evt| draw.draw_layout(gc, 10, 10, l) end win.show_all Gtk.main
Gtk::Labelを使っても同じことできるのかな?
840円の赤字プロジェクトである nono の開発であるが、顧客満足度という点で見た場合はどうか。
やはり、駄目プロジェクトと言うしかない。 大体、nonoを使うユーザーは何をするかというと、nonoに解かせるための問題を作って、 nonoに解かせるだけなのである。 ユーザーが自分でののぐらむを解いて遊ぶ訳ではない。 ののぐらむの問題を作る人の手助けぐらいにはなるかも知れないが、 喜んでnonoを使う人は、ごく少数であろう。
こう書くと何の取り得もないみたいだが、作っている本人だけは、結構、楽しんでいる。 nonoが解けない問題を解けるようにするにはどうするか考えたり、 解くのに時間がかかっていた問題を、どうすればすんなり解けるようになるか考えたり、試行錯誤したりするのが、 本人にとっては、パズル感覚で面白いところなのである。
ちなみに、『イラストロジック』にある55×45の超難問ロジックを解かせてみたところ、 最終的には、50分ぐらいで答えを出すようになった。 答え合せが、まだできないので、正しいのかどうかは、わからない。 困ったことに人間と比較した場合、これが人間より速いのか 遅いのかさっぱりわからない。 まあ、私より、速いのは間違いない。 といっても、私は、生まれてから一度も、まともにイラストロジックを解いたことが無いので、参考にはならない。
レベルを下げて、55×45と升目の数は同じだけど、難しさは中程度のものを、nonoに解かせると、30秒程度で答えを出した。
難しい問題だと時間がかかり、簡単な問題は、それなりに速く解けるようになっている。
一部の問題を速く解くようにしました。 一部の問題では、逆にちょっとだけ時間がかかるようになってしまったかも知れません。
カラーロジックを解いたり、解かなかったりするようになりました。 また、キャラクタベースで見ても何の絵だかさっぱりわからないので、 結果をHTMLファイルに出力するようにしました。
nonoの開発をプロジェクトとして外から見た場合、失敗プロジェクトである。
テスト・ファーストで開発してないとか、ソースが汚ないとか、ドキュメントが無いとか、そんなことはさておき、 赤字プロジェクトなのである。
別に儲けようとは思っていない。でもマイナスになるのは痛い。
作り始めたときには、当然、nonoは間違った答えを出したり、解けなかったりすることが多かった。 それを解けるようにプログラムを修正していった。 インターネット上のいろんなサイトでののぐらむの問題は公開されている。 nonoのテストの題材に困ることはない。
手前味噌ではあるが、最近は、間違った答えを出すことも少くなってきた。 こうなってくると力試しをしてみたくなってくる。
インターネット上で公開されている問題は、数多くある。 だが、世の中には、ののぐらむを掲載しているパズル雑誌というものもある。 果たして雑誌の問題にnonoは通用するだろうかという興味があった。 金を出さないと手に入らない雑誌の問題は、 インターネット上の問題とは一味違うんじゃないかという勝手な思い込みもあった。
そこで、『イラストロジック 3月号』を買うことにした。 こうして、420円の赤字プロジェクトになってしまったのである。
気づいたのであるが、『イラストロジック 3月号』の答は、『イラストロジック 4月号』にしか載ってこない。 答え合わせのために、今月の末には840円の赤字プロジェクトになる予定だ。
実際のところ、通用したかどうかというと、これが、なかなか進まない。
問題を nono 用に写すのが面倒なのである。 25×25のからふるロジック(カラーロジック)の問題を写すだけでも、1時間ぐらいかかる。
nonoに問題を解かせたあとも面倒くさい。 一見、nono の答えが間違っているように見えても、実は、問題を写し間違えただけだったりする。
nonoが間違っているのか、問題が間違っているかを判断するのが面倒くさい。 最初の頃は、nonoの方が間違っていることが多かったのだが、最近は、写し間違いの方が多くなってきた。 どこを写し間違えたかを探す間違い探しのパズルとして楽しむことができる。 というのは、詭弁であり、負け惜しみでしかない。
-s オプション付きで実行したときに、例外が発生するバグを修正しました。
余計なとこまで空白にしてしまうバグがあったのを修正しました。
だらだらと作っていたnonoを公開します。 ののぐらむを解くソフトです。 ののぐらむをそこそこ解くようになったように思えるので公開に踏み切りました。
午前中、病院に行ったあと、 参加しようかやめようか迷っていたのだけど、午後、ちょっと参加することにした。 目的は平鍋さんの話である。 裏でやる arton さんの話も聞きたかったんだけど仕方ない。
通路の狭さで人混みに酔う。 汗はかくし、ちょっとつらい。 平鍋さんの講演まで気力がもつかなと思いながら、前の講演を聞く。
結局、そのまま、平鍋さんの講演を聞くことにした。
XPというかアジャイルの世界もここまで来たのかと改めて思う。 「アジャイル開発とは何か」という初心者向けの紹介でもなく、 「アジャイル開発の実例の紹介」というのともちょっと違う。 「アジャイル開発」を効果的に実践していくための独自のツールの紹介という感じであった。
もはや、「アジャイル開発とは何かを知る」から「アジャイル開発を適用」するという段階も超え、 自分達に合うように「アジャイル開発をカスタマイズ」するという3番目の段階なのだと感じた。
今回は、誰にも会わないでとっとと帰ろうと思っていたら、 通路で、懸田さんと接近遭遇したので、挨拶だけした。 その直後、しっかり、まつもとさんに発見されてしまう。 ずるずると惰性でついていってしまい、artonさんとも会って話をした。
LL Weekendのときと違い、隣りにいらっしゃっったかずひこさんにも挨拶できた。
からさわぎのときと同様、今回も話はできなかったけど、角谷さんと会釈だけした。
挨拶しようと思って行くと挨拶できず、誰にも見つからないようにさっさと帰ろうとすると なぜか、名刺交換までできてしまう。 世の中、こういうもんである。