MOSA Developer News[MOSADeN=モサ伝]第118号
2004-06-22
目次
- Cocoaでいこう! Macらしく 最終回 Yoshiki(DreamField)
- 小池邦人の「Carbon API 徒然草
- 「Behind the WebObjects」 第22回 田畑 英和
Cocoaでいこう! Macらしく 最終回 Yoshiki(DreamField)
ついに今回で終わりです。1年と2ヶ月の間、ご愛読ありがとうございました。
座標の原点を左上にしよう(再び)
今回は、第38回で行ったimageViewをflipさせるのとは違う方法で、画像表示の原点を左上にします。
NSScrollViewの挙動が左上原点に変わるのは、そのsubviewがflipしているからです。そこで、NSScrollViewとimageViewの間にもう一つNSViewクラスのインスタンスを挟み、これをflipさせてしまいます。こうすれば、imageViewはそのままで、NSScrollViewの挙動を変えることができます。では、そのクラスを実装しましょう。名前はFlippedviewにします。新規ファイルを作る時の雛形は、Objective-C NSView subclassを選んで下さい。ファイルを作ったら、雛形で用意されているメソッドはそのままほっておいて、次のメソッドを実装してください。
- (BOOL)isFlipped {
return YES;
}
あまりにシンプルですが、このクラスは実際に描画を行うわけでは無いので、これだけです。では、このクラスのインスタンスをnibファイルに加えましょう。MyDocument.nibを開いてください。XcodeからFlippedView.hを、nibファイルウィンドウにドラッグ&ドロップします。この辺りの操作は、もうお馴染みですね。MyImageViewを選んでinfoパネルを開いてください。選びにくいようでしたら、nibファイルウィンドウを一覧表示にして選ぶという方法もあります(fig.01)。確実に選ばれているかどうかは、infoパネルのタイトルバーを見れば分かります(fig.02)。選んだら、現在はCustom ClassがMyImageViewになっているはずですので、これを先ほど読み込んだFlippedViewに変更します(fig.03)。次に、新しくCustomViewをFlippedViewの中に加えます(fig.04)。加えたら、左下の座標が(0,0)になるように、ぴったり合わせてください(fig.05)。サイズはプログラムの中から変更しますので、適当でかまいません。位置を合わせたら、このCustomViewのCustom ClassをMyImageViewに変更しましょう(きちんと選ばれていることを確認してから変更してください)。これでNSScrollViewの中にFlippedView、そしてさらにその中にMyImageViewという構造になりました(fig.06)。構造的にはこれで良いのですが、今までMyImageViewだったオブジェクトをFlippedviewにしてしまったので、Outletの接続先も変える必要があります。File’s Ownerを選んでinfoパネルをConnectionsに切り替えてください。imageViewの接続先がFlippedViewになってしまっているので、これを外します(fig.07)。そして、改めてMyImageViewに接続し直してください(fig.08)。以上でnibファイルの加工は終わりですので、保存してXcodeに戻りましょう。
[fig.01] 一覧表示からなら確実にオブジェクトを選択できる
http://www.remus.dti.ne.jp/~yoshiki/cocoa/ed1/55/images/fig01.gif
[fig.02] infoパネルのタイトルバーには選ばれているオブジェクトが表示される
http://www.remus.dti.ne.jp/~yoshiki/cocoa/ed1/55/images/fig02.gif
[fig.03] MyImageViewからFlippedViewに変更する
http://www.remus.dti.ne.jp/~yoshiki/cocoa/ed1/55/images/fig03.gif
[fig.04] FlippedViewの中にCustomViewをドラッグ&ドロップ
http://www.remus.dti.ne.jp/~yoshiki/cocoa/ed1/55/images/fig04.jpg
[fig.05] 左下に合わせる
http://www.remus.dti.ne.jp/~yoshiki/cocoa/ed1/55/images/fig05.jpg
[fig.06] 間にFlippedViewを挟むことができた
http://www.remus.dti.ne.jp/~yoshiki/cocoa/ed1/55/images/fig06.gif
[fig.07] imageViewの接続先がFlippedViewになっているので外す
http://www.remus.dti.ne.jp/~yoshiki/cocoa/ed1/55/images/fig07.gif
[fig.08] imageViewをMyImageViewに接続する
http://www.remus.dti.ne.jp/~yoshiki/cocoa/ed1/55/images/fig08.gif
あと一つ実装しなければならないことが残っています。MyImageViewは必要に応じて自分自身のサイズを調節していますが、FlippedViewは何もしていません。MyImageViewはFlippedViewの中に表示されるのですから、これでは困ります。二つは常に同じサイズでなければなりません。そこで、MyImageViewが自分自身のサイズを調節する時に、いっしょにFlippedViewのサイズも調節することにします。MyImageViewが自分自身のサイズを調節している箇所は二つです。これに下記の様に、FlippedViewの調節を加えます。
- (void)setImage:(NSImage *)newImage{
[ newImage retain];
[ image release];
image = newImage;
[ self setFrameSize: [ image size]];
[ [ self superview] setFrameSize: [ image size]];
}
- (void)setZoomValue:(float)newZoomValue {
NSRect theRect;
zoomValue = newZoomValue;
theRect.size = [ image size];
theRect.size.width *= zoomValue;
theRect.size.height *= zoomValue;
theRect.origin = NSZeroPoint;
[ self setFrame:theRect];
[ [ self superview] setFrame:theRect];
[ self setNeedsDisplay:YES];
}
それぞれ、[ self superview]にメッセージを投げている所が、加えた部分です。FlippedViewは、imageViewのsuperviewですから、これでメッセージが届きます。それでは、ビルドして実行してみてください。ウィンドウのサイズを変更すると、表示の起点が左上になっていることが分かります(fig.09)。これでまた一つMacらしくなりましたね。もっとも、拡大/縮小後のウィンドウの位置は、まだまだMacらしくありませんが、これの調節まで書いていると終わらないので、これは各自で工夫してみて下さい。
[fig.09] 左上が起点なので、ウィンドウのリサイズは表示範囲の変更に見える
http://www.remus.dti.ne.jp/~yoshiki/cocoa/ed1/55/images/fig09.jpg
(サンプル画像は、ゆきみだいふくさんのご好意により、使用させていただいています。)
メニューにチェックを付けよう
実は、もう一つ気になっていることがあります。現在のViewメニューは、一目でどのモードが選ばれているのか分かりません。通常のMacのアプリでしたら、今選ばれているモードのメニューにチェックを付ける等して、明示するはずです。では、これをどこで行えば良いでしょうか。まっ先に思いつくのは、メニューでモードを選んだ時です。この時、チェックしなければならないメニューは変化します。では、他には無いでしょうか。TinyViewは、同時に複数のウィンドウを開けます。ウィンドウ毎にモードを切り替えますから、それぞれのウィンドウでモードが違うこともあるはずです。従いまして、メインウィンドウが切り替わった時に、チェックを付けるメニューもそれに合わせて変更する必要があります。そして全てのウィンドウを閉じた時は、全てのチェックを外す必要もあるでしょう。以上のことを実装します。まずは現在のモードを覚えておく必要がありますので、MyDocumentクラスに下記のインスタンス変数を加えてください。
int zoomMode;
この値をメニューを切り替えた時にセットします。selectZoomMenu:の頭の方を次の様に書き換えて下さい。
- (IBAction)selectZoomMenu:(id)sender{
float theZoomValue;
zoomMode = [ sender tag];
switch( zoomMode){
・
・
これだけでは、一度もメニューを選んで無い時の値が0になってしまいます。これを1にするために、initに次の一行を加えます。
zoomMode = 1;
これでモードを覚えておく事ができるようになりましたので、このモードに合わせてメニューを切り替えるメソッドを実装しましょう。
MyDocumentControllerに次のメソッドを実装して下さい。
- (void)updateViewMenuWithMode:(int)newMode{
NSMenu *theViewMenu;
int i;
theViewMenu = [ [ [ NSApp mainMenu] itemWithTitle:NSLocalizedString(@"View", nil)]
submenu];
for( i = 0; i <= 2; i++){
[ [ theViewMenu itemWithTag:i] setState:(i == newMode)?( NSOnState):
( NSOffState)];
}
}
ここでは、指定されたモードと同じメニューだけstateをOnにし、それ以外をOffにしています。何でMyDocumentControllerの方に実装するかと言うと、今回の場合はMyDocumentクラスのインスタンスが一つも無くなった時にも呼び出されるからです。その処理は同じMyDocumentControllerにありますから、先に実装してしまいましょう。removeDocument:メソッドでドキュメントが一つも無くなった時の処理の最後に、次の一行を加えて下さい。
[ self updateViewMenuWithMode:-1];
-1なんてモードはありませんから、これでチェックが全部外れます。次に、MyDocumentの方では、zoomModeをパラメタとしてメッセージを投げるメソッドを実装します。
- (void)updateViewMenu{
[ [ NSDocumentController sharedDocumentController] updateViewMenuWithMode:zoomMode];
}
これを必要な所で呼び出せば良いわけです。selectZoomMenu:と、windowDidBecomeMain:の両方の最後に、次の行を加えてください。
[ self updateViewMenu];
以上です。このままですとワーニングが出ますので、適宜ヘッダーの宣言を追加し、新たに必要になったヘッダーはimportするようにしてください。ワーニングが出たままでも実行はできますが、なるべく潰した方がミスを発見しやすくなります。それでは実行してみましょう。複数の画像を開いて拡大/縮小を行い、メニューがどう変化するかを見てください。期待通りの動作になったと思います(fig.10)。
[fig.10] メニューにチェックが入るようになった
http://www.remus.dti.ne.jp/~yoshiki/cocoa/ed1/55/images/fig10.gif
予定した内容が収まりきらず、最後は駆け足になってしまいました。これでも普段の倍の量なのですが。申し訳ない。
最後に
ここまでお付き合いくださいまして、ありがとうございます。なるべく市販の本に無いことを書きたいと思うあまり、構成的には少し歪んでしまったと、ちょっと反省もしています。ですが、本だけではなかなか理解しにくい所や、本では説明してくれない所を、私なりの考えを交えて説明してきたつもりです。まだまだ説明していないことは一杯ありますが、これ以上はご自分で本を読まれたり、ドキュメントを調べたりすれば、十分に解決できるでしょう。
Cocoaはとても強力なフレームワークであり、Objective-Cはとても素晴らしいオブジェクト指向言語です。本連載を通して、少しでもCocoaやObjective-Cを面白いと思っていただけたら、幸いです。長い間、ありがとうございました。
小池邦人の「Carbon API 徒然草」(2004/06/18)
Main Event Loopへ入る前準備-その1
今回と次回は、サンプルアプリケーションに実装されているCarbon Event TimerとCarbon Event Handlerの話をします。具体的には、初期化ルーチンのstartApplication()から呼ばれている、setupEventLoopTimer()とsetupApplicationEvent()についての解説となります。
Carbon Event Timerとは、「アプリケーション起動中に定期的に実行しなければいけない処理」を受け持つルーチンのことです。アプリケーションの種類によっては、状況に応じて、こうした処理が複数存在したりします。
setupEventLoopTimer()は、サンプルアプリケーションに、このCarbon Event Timerルーチンを実装しています。WaitNextEvent()を呼ぶことで、自分自身でMain Event Loopを管理していた時代には、こうしたルーチンはループ内から直接呼べばOKでした。Thread ManagerのAPIを使うという手もありましたが、こちらの方が簡単な実装方法です。まずは、Mac OS 9時代のアプリケーションで用いていた、古典的なMain Event Loopルーチンを見てみましょう。
void mainEventLoop(void)
{
EventRecord event; // どんなイベントが発生したかを保持する
short flag;
FlushEvents( everyEvent,0L ); // まずは待ちイベントをすべてクリアする
while( 1 )
{
myEventLoopTimer(); // 定期的に実行しなければいけない処理
if( WaitNextEvent( everyEvent,&event,10L,0L ) ) //イベントを受け取る
{
switch( event.what )
{
case mouseDown: // マウスが押された
doMouseDown( &event );
break;
case keyDown: // キー入力がなされた
case autoKey:
doKeyDown( &event );
break;
case updateEvt: // ウィンドウの書き換えが必要になった
doUpdate( (WindowRef)event.message );
break;
case activateEvt: // ウィンドウのレイアが切り替わった
flag=event.modifiers&activeFlag;
doActivate( (WindowRef)event.message,flag );
break;
}
}
}
}
mainEventLoop()ルーチンから呼ばれているmyEventLoopTimer()には、定期的に実行する処理が記述されています。どんな状況(あるウィンドウがフロントの時だけ...)において、どんなタイミング(間隔)で呼び出すかについては、すべて自分自身で管理する必要があります。また、Mac OS 9の仕組み上、ユーザがメニューやウィンドウをドラッグしている最中などは、mainEventLoop()へと制御が渡って来ませんので、myEventLoopTimer()も実行されなくなるという大きな制限が存在していました。
それとは異なり、モダンなCarbonアプリケーションでMain Event Loopへ入るためには、RunApplicationEventLoop()を実行します(抜けるにはQuitApplicationEventLoop()を実行)。サンプルでは、startApplication()の最後でRunApplicationEventLoop()を呼んでおり、この時点でMain Event Loopが開始されるわけです。CarbonアプリケーションのMain Event Loopはシステム側で管理されており、そこで受けたCarbon Eventは種類により振り分けられ、適切なクラスのCarbon Event Handlerルーチンへと渡される仕組みになっています。
つまり、Carbonアプリケーションでは、mainEventLoop()を記述する必要がない代わりに、myEventLoopTimer()を呼び出す場所もなくなってしまったわけです。そこで、Carbon Eventの登場と同時に、Carbon Event Timerの仕組みが導入されました。この仕組みは、簡単にCooperative(協調的)な別スレッドを作る仕組みだと考えれば分かりやすいと思います。協調的スレッドですが、Mac OS 9の時とは異なり、メニューやウィンドウのドラッグ中にもちゃんと処理が継続されます(逆にそのために注意すべき点もある)。
こうしたCarbon Event Timerルーチンは、InstallEventLoopTimer()によってアプリケーションに実装します。以下がCrbon Event TimerのmyEventLoopTimer()ルーチンを実装しているsetupEventLoopTimer()です。
void setupEventLoopTimer(void)
{
InstallEventLoopTimer( GetCurrentEventLoop(),0.0,0.1,myEventLoopTimer,
NULL,NULL );
}
InstallEventLoopTimer()の最初の引数はEventLoopRefで、どのEvent Loopに対応しているのかを指示しています。GetCurrentEventLoop()により得たEventLoopRefを渡すことで、現在働いているEvent Loopに実装するよう指示しています。次の引数は開始時間、その次は呼び出し間隔で、それぞれ秒で指定します。4番目の引数にはCarbon Event Timerルーチン本体を渡します。これにより、myEventLoopTimer()は、Main Event Loopが始まると同時に有効となり、アプリケーションが起動している間、0.1秒間隔で呼び出されるわけです。
pascal void myEventLoopTimer( EventLoopTimerRef timeref,void *userData )
{
WindowRef wptr;
CGrafPtr cptr;
Rect brt;
short chk;
Point pt;
wptr=FrontNonFloatingWindow(); // フロントウィンドウのWindowRefを得る
if( wptr && menu_track==0 ) // ウィンドウなしとメニュー表示中は避ける
{
chk=QDSwapPort( GetWindowPort( wptr ),&cptr ); // カレントポート待避
GetMouse( &pt ); // マウス位置を得る
GetWindowPortBounds( wptr,&brt ); // ウィンドウの矩形領域を得る
switch( GetWRefCon( wptr ) ) // ウィンドウの種類をチェック
{
case 'VIEW': // これは「画像ウィンドウ」
brt.right-=15; // スクロールバー部分を外す
brt.bottom-=15;
if( PtInRect( pt,&brt ) ) // ウィンドウ領域内か判断
myCursor( 128 ); // ハンドカーソル表示
else
myCursor( 0 ); // 矢印カーソル表示
break;
default: // その他のウィンドウ
myCursor( 0 ); // 矢印カーソル表示
break;
}
if( chk )
QDSwapPort( cptr,NULL ); // カレントポート復帰
}
else
myCursor( 0 ); // 矢印カーソル表示
}
ここでの処理は、表示されているウィンドウの種類に応じマウスカーソルの形状を変えるという簡単なものです。しかし、アプリケーションの種類によっては、このルーチン内で特別な処理を実行する必要が出てきます。例えば、QuickTimeのMovie Controller(Movieを操作するためのコントロール)を利用している場合には、処理時間を配分するためにMCIdle()を定期的に呼ぶ必要があります。同様に、QuickTimeのSequence Grabberでビデオ映像を表示している時には、定期的にSGIdle()を呼ぶことになります。こうした「定期的に実行しなければいけない約束事」は、Event Loop Timer内で実行してやるのが一番効率的です。Mac OS Xになってから、こうした手間のかかる「約束事」は随分と少なくなりましたが、まだいくつか存在していますので注意が必要です(化石のようなもの)。
RunApplicationEventLoop()やQuitApplicationEventLoop()が定義されているヘッダファイルはCarbonEvents.hです。InstallEventLoopTimer()の方はCarbonEventsCore.hに定義されていますので参照してみてください。次回は今回の続きで、アプリケーション自身に実装するCarbon Event Handlerの話です。
つづく
「Behind the WebObjects」 第22回 田畑 英和
前回はProject WONDER(1.0.1)のCommonディレクトリの構成について解説しましたが、今回はそれ以外のディレクトリについて説明します。
Adaptors
もともとソースコードが公開されているWebサーバアダプターの改良版です。アップルからバイナリーが提供されていないプラットフォーム向けの、ビルド済みアダプターの配布がおこなわれています。具体的には以下のOS向けに各種アダプターが提供されています。
HP-UX : Apache
Red Hat Linux : CGI, Apache
FreeBSD : CGI, Apache
OpenBSD : CGI, FastCGI, Apache
・アダプターダウンロードページ
http://wonder.sourceforge.net/WOAdaptor.html
またビルド済みアダプターの提供だけではなく、以下のようなコードの修正や機能的な改良もおこなわれています。
・アプリケーション数の上限値の変更(16 -> 64)
・Apacheアダプターでのタイムアウトの修正
・ビルドスクリプトのLinux対応
・Apacheアダプターでのリクエストヘッダーの追加
DynaReporting
グルーピングしたレコードのレポートを作成するフレームワークです。このフレームワークはDRGroupingとWRReportingの2つから構成されており、サンプルプロジェクトも付属しています。
Experimental
以下の実験的なプロジェクトが収められています。
・DevStudio
Webベースの開発環境です。Direct To WebのルールやEOFのモデルファイルをサポートしています。
・DAPDB_PlugIns
SAP DBのためのデータベースプラグインです。Project WONDERでは基本的にはBSDスタイルのライセンスを採用していますが、このプラグインはLGPLを採用しています。またこのプラグインを使用するにはSAP DBのためのJDBCドライバーが別途必要になります。
なおSAP DBは現在MaxDBへと名称を変更しており、MySQLの開発をおこなっているMySQL ABへと引き継がれています。
「SAP DB」
http://www.sapdb.org/
・WOWebLog
ブックマーク可能なステートレスのWebLogアプリケーションです。このアプリケーションは様々なフレームワークから構成されており、Seppukuという名前のフレームワークも含まれています。
このフレームワークは、以前はWOHarakiriと呼ばれていたものの改良版であり、反応のなくなったアプリケーションを自動的に終了してくれます。
WOHarakiriではアプリケーション側でのコード追加による対応が必要でしたが、改良版であるSeppukuではNotificationを利用することにより、アプリケーション側での対応が必要なくなり、フレームワークを追加するだけで利用できるようになりました。
PayPal
WebObjectsアプリケーションからPayPalの送金サービスを利用できるようにするフレームワークです。実際に利用するにはPayPalの口座が必要になります。
・PayPal
http://www.paypal.com/
SVGObjects
動的にSVGフォーマットのデータを作成します。複数のサンプルも付属しており、SVGの統合についてはRavi Mendis著「WebObjects Developer's Guide」で詳しく解説されています。
「WebObjects Developer's Guide」
http://www.amazon.co.jp/exec/obidos/ASIN/0672323265/
Utilities
ユーティリティが収められており、Project Builderのメニューをアップルスクリプト対応にするプラグイン「BXAppleScriptMenuPlugin」や、参照しているフレームワークをアプリケーション内(.woa内)にコピーするPerlのスクリプト「woaFrameworkMerger」や、Webセーフカラーに対応したMac OS X用カラーパレット「HTMLColorPalette」などが含まれています。
Validity
EOFのためのルールベースのValidation(データの正当性検証)システムです。Validationのルールを設定するアプリケーション(ValidityModeler)も付属しており、アトリビュートあるいはリレーションごとにValidationルールを設定することができます。
以上のようにProject WONDERには様々なフレームワーク/アプリケーションなどが収められています。これらはオープンソースとして開発が進められており、最新版のVer2.0では様々な改良、機能追加がおこなわれています。そこで次回は最新版のVer2.0について紹介する予定です。
MOSAからのお知らせと編集後記は割愛します
Apple、Mac OSは米国アップルコンピュータ社の登録商標です。またそのほかの各製品名等はそれぞれ各社の商標ならびに登録商標です。
このメールの再配信、および掲載された記事の無断転載を禁じます。
http://www.mosa.gr.jp/
Copyright (C)2004-2006 MOSA. All rights reserved.