validateUserInterfaceItem(1)
メニューとツールバーの有効/無効を制御するプロトコルとして”NSUserInterfaceValidations”がある。
これをNSButtonに対して拡張する手法がある。2006年〜2007年頃に書いたコードだ。
@interface MTLButton : NSButton
{
}
- (void) updateEnableState;
@end
@implementation MTLButton
- (id)initWithFrame:(NSRect)frameRect
{
self = [self initWithFrame:frameRect];
if(self)
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateEnableState)
name:NSWindowDidUpdateNotification
object:nil];
}
return self;
}
- (void) dealloc
{
[self subviews];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (id) initWithCoder: (NSCoder*)aDecoder
{
self = [super initWithCoder: aDecoder];
if(self != nil)
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateEnableState)
name:NSWindowDidUpdateNotification
object:nil];
}
return self;
}
- (void) updateEnableState
{
// actionが設定されているかをチェック。(target == nil)の場合はfirstResponderだから有効なのでtargetのnilチェックはしない。
if( [self action] != nil && [self window] != nil && [self isHiddenOrHasHiddenAncestor] == NO)
{
id theValidator;
// レスポンダーチェーンをたどって実際にactionを処理するオブジェクトを探す。そのオブジェクトがメニューやボタンのenable/disableの値を決定出来るはず
theValidator = [NSApp targetForAction:[self action]
to:[self target]
from:self];
if([self infoForBinding:@"enabled"])
{
// enabledにCocoaBindingが設定されている場合は
// CocoaBindingの設定を優先させるために何もしない。
}
else if ((theValidator == nil) || ![theValidator respondsToSelector:[self action]])
{
// actionを実行できるレスポンダーが無い場合は、ボタンは押せない
[self setEnabled:NO];
}
else if ([theValidator respondsToSelector:@selector(validateToolbarItem:)])
{
// actionを実行できるレスポンダーが"validateToolbarItem:"を実装しているならば従う
[self setEnabled:
[theValidator validateToolbarItem:
(NSToolbarItem*)self]];
}
else if ([theValidator respondsToSelector:@selector(validateUserInterfaceItem:)])
{
// actionを実行できるレスポンダーが"validateUserInterfaceItem:"を実装しているならば従う
[self setEnabled:
[(id<NSUserInterfaceValidations>)theValidator validateUserInterfaceItem:
(id<NSValidatedUserInterfaceItem>)self]];
}
else
{
// レスポンダーがあるけど、"validateXXXX"系のメソッドが無い場合は、ボタンは押せる。
[self setEnabled:YES];
}
}
}
@end
最近、仕事でiOSでなくCocoaのコードを書く事があり気がついた。 このコードのNotificationを登録する箇所と値が間違っている。
- 登録する場所は、 “[MTLButton viewDidMoveToWindow]”で行われるべき。
- 登録解除する場所は、”[MTLButton viewWillMoveToWindow:newWindow]”で行われるべき。
- Notification登録時にButtonが乗っているWindowでフィルタリングするべき。
そんな訳で、
- (id)initWithFrame:(NSRect)frameRect;
- (void) dealloc;
- (id) initWithCoder: (NSCoder*)aDecoder;
は削除され、以下のコードで置換えるべきだと思うのだが、まだ実装してません。
- (void)viewDidMoveToWindow
{
[super viewDidMoveToWindow];
NSWindow* theWindow = [self window];
if( theWindow != nil )
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateEnableState)
name:NSWindowDidUpdateNotification
object:theWindow];
}
}
- (void)viewWillMoveToWindow:(NSWindow *)newWindow
{
[super viewWillMoveToWindow:newWindow];
NSWindow* theWindow = [self window];
if( theWindow != nil )
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSWindowDidUpdateNotification
object:theWindow];
}
}
ひょっとしたら、windowが放棄される時に”viewWillMoveToWindow:newWindow”が呼ばれないかもしれない。 が、もう寝る。
NSWindowControllerとESCキー
眠れぬ夜のために : OS X 用 Cocoa アプリケーションにおける環境設定ウィンドウの作り方( http://forthesleeplessnight.blogspot.jp/2012/10/os-x-cocoa.html)では、環境設定ウィンドウをESCキーでクローズする動作を、NSWindowのサブクラス化で実現している。
@interface PreferencesWindow : NSWindow
@end
@implementation PreferencesWindow
- (void) cancelOperation:(id)sender
{
[self close];
}
@end
個人的にはNSWindowではなく、NSWindowControllerで以下のように実装するのが好みだ。
@interface PreferencesWindowController : NSWindowController
@end
@implementation PreferencesWindowController
- (IBAction) cancel:(id)sender
{
[self close];
}
@end
delegateで拡張できるならば、subclass化よりもdelegateで拡張する方がCocoaらしいと思っています。
ディレクトリレイアウトのメモ
プロジェクトを始めるにあたって、毎回悩むディレクトリレイアウトです。 一部ではファイル構成管理と言うようですが、もっと狭い意味での構成管理です。
ファイル構成管理と言うと、svnやgitのようなバージョン管理を思い浮かべるかもしれないですが、ここではファイルとディレクトリをどのように配置するかを書いています。
私はSubversion実践入門で紹介されていたレイアウトを使っています。
BUILDING <--- ビルドする方法を書いたメモ GLOSSARY <--- プロジェクト特有の用語集 README <--- このプロジェクトが何なのか説明したメモ bugs/ <--- ditzのissueを入れるディレクトリ data/ <--- 本番用/テスト用のデータファイル等 doc/ <--- プロジェクトに関するドキュメント src/ <--- ソースコード util/ <--- プロジェクト特有の各種ツールやscript vendor/ <--- サードパーティ製のライブラリ等 vendorsrc/ <--- サードパーティ製のライブラリのソースコード
- BUILDING
ビルドする方法を書いたメモを書くとの事です。 内容は、ビルドに必要な環境とビルド方法を書いたメモです。
必要条件: Xcode 4.5.2 MacOSX 10.8.2 ビルドの方法: cd ./src/ make 詳細情報: ./doc/building.htmlしかし、このメモが必要になった時には、あまり役には立ちませんでした。 XCodeのバージョンが合わなかったり、OSのバージョンアップで各種ツールの位置が変わったりしていたため、結局XCodeのプロジェクトやmakeファイルの中身を書き換える事になりました。
- GLOSSARY
プロジェクト特有の用語をまとめて書くと良いと言われてますが、私は一度も書いた事はありません。 多分、複雑で大規模なコードを書く人たちには重要なのかもしれません。
- README
このプロジェクトが何なのか説明したメモ。 GitHubにあるプロジェクトのREADMEを参考にすると良いだろうと思ってますが、未だに書いた事がありません。
- bugs/
ditzのissueを入れるディレクトリ。ditz initで作成してます。今回初めて作りました。
- data/
本番用/テスト用のデータファイル等を入れるらしい。が、これも使った事無いです。
- doc/
プロジェクトに関するドキュメントを入れる。議事録、設計メモ、メールでのやり取り、運用マニュアル等を入れておきます。 読んでおくべき参考文献等もPDFやWebArchiveフォーマットで入れておくと、探す手間が省けて便利でした。
- src/
プログラムのソースコー本体です。
- util/
プロジェクト特有の各種ツールやscriptを入れておきます。意外に使います。 私の場合は、
- deleteDotSVN.sh
出荷用パッケージに.svnデイレクトリが残らないようにするためのスクリプト
- openUp
diskImageの”first-open-window”属性をONにするプログラム。Apple製
- make_svn_version_h.sh
カレントディレクトリ上にsvnversionの値をc文法で定義するファイルのsvn_version.hを生成する。ビルド番号を生成を補助するツール等を入れたり入れなかったりしてます。
- vendor/
サードパーティ製のライブラリを入れておきます。例えば、YAML.framework等を入れたりします。
- vendorsrc/
サードパーティ製のライブラリのソースコードを入れておきます。例えば、YAML.frameworkのソースを入れたりします。毎回ビルドする物ではないので、ビルドした成果物は、vendorに入れてバージョン管理下に加えておきます。