2007-07-17
目次
りんご味Ruby 第7回 藤本 尚邦
先月(6月9-10日)、東京で「日本 Ruby 会議 2007」が開催されました。Sunの支援により開発されているJRuby(Java VM上で動くRuby処理系)、Dave Thomasさんによる印象深い基調講演、他にもネタに走った内容の発表など幅広い内容で、運営も含めたいへん素晴らしい会議でした。
そんな中、私もRubyCocoa作者として発表させていただきました。発表の冒頭で、RubyによるCocoaライブプログラミングをデモしました。Ruby会議用に準備したインタラクティブプログラミングのためのRubyCocoaアプリケーションCocoaReplを用いて、WindowやViewをインタラクティブに構成していく様子を示す簡単なデモです。QuartzComposerを使ったのが効いたのか、まずまず好評だったような気がしています。
ということで、今回は、そのときのライブプログラミングで書いたRubyCocoaプログラムついて解説します。
-----------------------------------------------
include OSX
require_framework "QuartzComposer"
mw = NSApp.mainWindow
w = NSWindow.alloc.
objc_send(:initWithContentRect, [0,0,800,600],
:styleMask, NSTitledWindowMask,
:backing, NSBackingStoreBuffered,
:defer, false)
w.makeKeyAndOrderFront(self)
v = w.contentView = QCView.alloc.init
qtz = "/Developer/Examples/Quartz Composer/" +
"Motion Graphics Compositions/Cube Replicator.qtz"
v.objc_methods.grep /^load/i
v.loadCompositionFromFile(qtz)
v.startRendering
w.zoom(self)
w.title = "RubyCocoaでインタラクティブにWindowとViewを構成した様子"
mw.alphaValue = 0.7
-----------------------------------------------
新しく生成したウィンドウにQCViewを埋め込んで、そこにQuartzComposerファイルをロードし、アニメーションのレンダリングを開始する、というのがおおよその流れです。
・QuartzComposerフレームワークをロード (2行目)
・CocoaReplのウィンドウにmwと命名 (3行目)
・ウィンドウを新規生成してwと命名 (4-8行目)
・QCViewを生成してw.contentViewにセットし、さらにvと命名 (10行目)
・vにQuartzComposerファイル(Appleによるサンプル)をロード (14行目)
・qtzアニメーションのレンダリング開始 (15行目)
・wを最大化 (16行目)
・wにタイトルをセット (16行目)
CocoaReplには、Rubyプログラムを評価するコマンドが3つあります。それぞれ、行・選択範囲・テキストエリア全体をRubyプログラムとして評価します。上のプログラムを1行ずつ順に入力して行を評価していくことにより、最終的には、ウィンドウの中でQuartzComposerにあるアニメーションのレンダリングが始まります。新規ウィンドウの生成と、qtzにファイルパスをセットしている部分だけは、メールの折り返しの都合により1行に書くことができませんでした。この2カ所のみ、複数行を選択して評価する必要がある点に注意してください。
■ まとめ
第3回で、動的な開発環境やREPLへの思い入れについて書きました。私が、動的な開発環境の魅力に目覚めたきっかけはRubyとEmacs(最近の海上さんの連載でも取り上げられていますね)です。このデモで使っているCocoaReplはまだまだおもちゃの域のものですが、RubyとMac OS Xを結びつけて、EmacsとMacの良いとこ取りをしたおもしろいアプリケーション環境を作れないかなぁと考えたりしています。
■ [補足] URL
CocoaRepl(Ruby会議バージョン)は、以下のURLから入手可能です。
http://www.fobj.com/hisa/d/20070611.html#p01
CocoaRepl
http://rubycocoa.sourceforge.net/rubykaigi2007/CocoaRepl-20070624.zip
今回紹介したRubyCocoaプログラム
http://rubycocoa.sourceforge.net/rubykaigi2007/opening.rb
藤本裕之のプログラミング夜話 #118
お待たせ。前回の続き,別の nibファイルから読み込んだビューの上にあるボタンなどのコントロールをどうやってプリンシパルクラスがアクションを受け取るか,という話である。
ちょっと考えてみてもやり方はいくつかある。最もCarbon的(?)なのはビュー上のコントロール一個一個について setTarget: と setAction: をやっていくことだが,普通そんなかったるいことはしないだろ。とりあえずは独立した nibファイルのなかで,それらのコントロールからのアクションをFile’s Owner である MyTabViewController(前回定義したNSWindowControllerのサブクラス)で受けるように設定してしまおう。ビュー
に普通のボタンが3つあるとして,それぞれのアクションメソッドとして
- (IBAction)button1:(id)sender;
- (IBAction)button2:(id)sender;
- (IBAction)button3:(id)sender;
を作り,ボタンをこれらに結びつける。この状態で Interface Builderにスケルトンを作らせると,そのヘッダーファイルはこんな風になる。
/* MyTabViewController */
#import
さてこっからだ。本物の…というか実際にこれらのメッセージを受けたいのはこのオブジェクトを nibファイルから読み出して初期化する方なので,そいつの情報を受けとるメソッドとその情報を格納しておくインスタンス変数を作らなければならない。すなわち,
id _target;
というインスタンス変数と,
- (void)setTaget:(id)target ;
というメソッドになるわけだが,ここでちょっと考えよう。この,_target に対してどんなメッセージを送る? もちろんその意味するところは「ボタン1が押された」「ボタン2が押された」「ボタン3が押された」なんだけどさ。
オレ思うに,最も間違いが少ないのはそこにこのオブジェクトと同じ3つのアクションメソッドがあることである。そこで上の MyTabViewController の定義の上に,これらのアクションメソッドを持つプロトコルの定義を加えてしまう。
@protocol MyTabViewOwner
- (void)button1:(id)sender;
- (void)button2:(id)sender;
- (void)button3:(id)sender;
@end
プロトコルって何だ? という人はなんか Objective-C の入門書を読んで欲しいが,簡単に(そして乱暴に)言えば,ある機能を実現するためにいくつかのメソッドを定義して,それらのメソッドを持つオブジェクトを(クラスに関わらず)同じように扱うための仕組みである。よく例に挙げられるのは「ミュージックプレイヤー」という概念がこれに似ている。使うメディアはメモリだったりハードディスクだったりCDだったりMDだったりいろいろだが,少なくとも「プレイ」「ストップ」という2つの機能は持っている(「ポーズ」もかな?)。で,それらの機能を「ミュージックプレイヤー」というプロトコルでくくってしまうと,いろんな機械を「ミュージックプレイヤー」として抽象化して扱えるわけ。この話自体が抽象化されててわかんない? 使えばすぐ分かるようになるよ。
プロトコルの定義をしたら,_target と setTarget: の定義も以下のように変える。
id
この意味は,_target はオブジェクト(id)だけど,このオブジェクトはMyTabViewOwner というプロトコルに準拠してます。setTarget: の引数もオブジェクト(id) だけど,このオブジェクトはMyTabViewOwner というプロトコルに準拠してます,ということ。具体的に言うと,こいつらは button1: ,button2:,button3: というメソッドを持っていないといけないのだ(こう書いておけばコンパイルラがワーニングを出してくれる)。
では次回こそこの話題の最終回(ホントは今回終わる予定だったんだがな)。今回定義したメソッドを具体的にコーディングしておしまいである。
(2007_07_11)
高橋真人の「プログラミング指南」第116回
〜XcodeによるPowerPlant X入門(7)〜
こんにちは、高橋真人です。1回間が空きましたが、続けます。
続いて、MyApplication.cpの方を見てみます。DoCommandProcess()で、
HICommandのcommandIDを見て分岐処理をしていますが、これはこの関数が呼び出されるのは必ずHICommandのイベントがアプリケーションに到達した場合に限っているからです。また、この関数が呼び出された時点でinCommandの中には、イベントから取り出されたHICommandの値が入っているのです。
そういうわけで、この関数の中では、単にcommandIDの値によってどのコマンドであるのかを判断して必要な関数を呼び出せばよいということになります。今回は、渡ってくるcommandIDは、kHICommandNewとkHICommandCloseの2つのみですので、このような形でswitch文で分岐をしていますが、実際のCarbonイベントモデルのプログラミングでは、このイベントによる処理の分岐を状況に応じてきめ細かく切り替えることが重要になってくることもあるので、PPxではこのようにswitch文を使ったコマンドの分岐処理を余り行いません。
その代わりにどのようなやり方をするのかということについては、次に行うプログラム例の時に解説します。
さて、残りの関数を簡単に説明しましょう。
HandleNew()では、Windowを作成して表示しています。CreateWindowFromNib
という関数でNibファイルからWindowを読み込んできています。
NibDecoderというのは、一見クラスのように見えますが、これは実はただのnamespce(名前空間)です。つまり、CreateWindowFromNib()という関数は、いずれのクラスにも属さない関数です。
Cにおける普通の関数をC++では「グローバル名前空間にある関数」などと呼んだりします。しかし、プログラムのどこからでも見えてしまうために大規模なプログラム、特に複数の人間で一つのプログラムを作る際には、「出来る限り使わない」ことが望ましいとされています。
全く別の関数が、たまたま同じ名前になっていたりすることでトラブルになることもあり得るからです。よってこの種の関数は、通常はむき出しにせず、何らかの名前空間に入れることが推奨されるわけです。
では、何でわざわざPPxとNibDecoderという二つの名前空間を二重にかぶせるのか言えば、それは機能のグループ分けをしているわけです。
Javaでは、すべてのメソッド(C++でいう関数)は必ずいずれかのクラスに属していなければなりませんが、C++ではどちらでも可能です。
Java的な書き方をするなら、一つのクラスを定義してそこに属する関数をすべてstaticにしてしまうことで可能ですが、そうすると、作る必要がないにも関わらず、そのクラスのオブジェクトを作ることもできてしまいます。
あくまで機能をグループ分けしたいだけであれば、そのためだけにクラスを作る必要は全くないので、C++では名前空間を使うわけです。(もちろん、クラスを作っても間違いではありません)
ちなみに、クラスを作り、staticな関数にしても、クラスの代わりに新たな名前空間を使っても、呼び出す側の見た目は全く変わりません。
話を戻します。
さて、CreateWindowFromNibはテンプレート付きの関数になっています。これは、Windowクラスのサブクラスを作る場合にも簡単にできるためにこのようなやり方になっているのですが、理屈を説明するよりも、実際にWindowのカスタムクラスを作る手順をお見せした方がずっと理解しやすいので、ここでは説明はしません。
とりあえず、使い方のパターンとして、PPx::WindowかそれのサブクラスをWINDOWと表すと、
WINDOW *win = PPx::NibDecoder::CreateWindowFromNib
のような使い方をします。引数の、「nibの名前」、「windowsの名前」はCFStringで与えます。
::RepositionWindow(theWindow->GetSysWindow(), nil, kWindowCascadeOnMainScreen);
という文ですが、まず、この関数のように先頭に::が付いているものは、先ほど触れたグローバル名前空間に属することを明示的に表す時に使います。
C++においては、Cの関数はすべてグローバル名前空間に属するわけですが、PowerPlantからの習慣として、Toolboxというか、Carbonなどのシステムが提供するAPI呼び出しは::を付けることで区別をしやすくしています。
次に、HandleClose()です。
PPx::Window* win = PPx::Window::GetWindowObject(theWindow);
GetFrontWindowOfClass()によりアクティブなウインドウが得られますが、(純粋なCarbonの呼び出しについては、PPx固有のものではないので解説はしませんから、適宜Appleサイトのリファレンスなどで確認してください)これはPPxによって作ったWindowクラスのオブジェクトです。(HandleNew()の中で作ったもの)
なので、WindowRefからPPx::Windowに変換をします。変換に成功すれば、
win->Close();
と、閉じるだけです。
ちなみに、PPx::Windowとして作っていない「タダのウインドウ(WindowRef)をPPx::Windowに変換しようとすると、もちろん失敗します。
PPx::Windowが表示しているWindowはWindowRefのウインドウそのものですが、プログラムから見た時には、それにいくつかの付加情報が加えられたPPx::Windowクラスのオブジェクトになりますので、念のため。
ところで、この関数が呼ばれると、
::CFShow(CFSTR("HandleClose called."));
という文が実行されてHandleClose called.とXcodeの実行ログに表示されるように見えるのですが、何故か表示されません。というか、ウインドウが一つもない時にメニューから「閉じる」を選ぶと、表示されるのです。
もちろん、これは「おかしい」のですが、説明のためにわざとやっています。
・なぜ、Windowを閉じる時には、このメッセージが表示されないのか
・Windowのない時に、メニューから「閉じる」が選べてしまうのを防ぐには
どうしたらいいのか
については次のプログラムで解説します。
さて、最後に残ったClassName()という関数ですが、これは、クラス構造をファイルなどに格納する場合に必要な情報を提供された仕組みだと私は思うのですが、今のところこれが有効に使われているという気がしないのも事実です。
書籍紹介 Programming with Quartz
解説担当:高橋政明
Programming with Quartz 2D and PDF Graphics in Mac OS X
David Gelphman、Bunny Laden共著
ISBN: 978-0-12-369473-7
AppleのCocoa Drawing Guideに参考文献として載っている本(洋書)です。
Quartzを使ってどのように描画するか詳しい解説とサンプルプログラムが載っていてMac OS X 10.4対応です。著者のDavid Gelphman氏はアップルのグラフィックスイメージングチームのソフトウェアエンジニアで二十年以上にわたりQuartzの心髄であるPostScriptとPDF関連の開発に従事したそうです。
洋書もインターネットを使い費用も納期的にも手軽に発注できるようになりましたが、本の中身がわからなければ購入の決断ができないと思いご紹介しようと思いました。でも中身も立ち読み的な参照は可能なのですね。
http://www.amazon.co.jp/gp/reader/0123694736/ref=sib_rdr_fc/249-6059979-7374735?ie=UTF8&p=S001&j=0
上記amazonのサイトで目次と索引に加えて最初の章を少し読む事ができます。
OSのバージョンに依存する機能の解説部分は10.2 10.3 10.4などのアイコンがつきタイトルの(Jaguar)(Panther)(Tiger)でも明示されています。
Mac OS Xのグラフィック能力を完全に利用し理解したいと思っている、PDFをサポートしたい、あるいはアプリケーションのグラフィック能力を高めたいCocoaとCarbonプログラマ向けの本だそうです。
情報量はとても多く読破するのは大変ですが、サンプルプログラムと結果の画像が対になっているものが多く画像から必要な処理を探すような使い方もできます。
2005年暮れに出版されたため、Intel Mac(Universal Binaries)についての解説もあります。私もぱらぱらと眺めただけですが基礎から、イメージやマスク、色、文字テキスト、パフォーマンスやデバグまで網羅されています。
▼出版社のweb
http://www.elsevier.com/wps/find/bookdescription.cws_home/706409/description
◇MOSAからのお知らせと編集後記は割愛します◇