2011年12月20日火曜日

Mac アプリケーションの環境設定ウィンドウを作る

環境設定ウィンドウについて

 Mac アプリケーションの動作等を設定するときに用いる環境設定ウィンドウを作る。

 環境設定ウィンドウの特徴は、
  • クラスは NSPanel (非 NSWindow
    これはエスケープキィを押したときにウィンドウが閉じられることから
  • ツールバーを持ち、そのツールバーアイテムをクリックすることでウィンドウのヴュウを切り替える
  • ヴュウを切り替える際にウィンドウの大きさがアニメーションを伴って変化する
 ここでは、上記のような環境設定ウィンドウの、ウィンドウとしてのふるまいを実装していく。実際にアプリケーションの設定をする方法は別論(NSUserDefaults 等を用いる)。

 今回作成したプロジェクトは GenjiApp/PrefWindowApp - GitHub で公開するので参照のこと。

Xcode プロジェクトの作成

 Xcode を起動し、New Project... から Mac OS X Application の Cocoa Application を選択する。ここでは Project Name を PrefWindowApp とし、適当な場所にプロジェクトを作成する。

ウィンドウコントローラの追加

 プロジェクトに環境設定ウィンドウを管理する新しいファイルを追加する。New File... から Objective-C class を選択し、NSWindowController のサブクラスを作成する。名前は PreferencesWindowController とした。
 今回の環境設定ウィンドウはふたつのヴュウを切り替えて操作するものとする。
 それぞれのヴュウを判別するための tag に用いる定数を列挙型で宣言しておく。
typedef enum {
  PreferencesViewGeneral = 100,
  PreferencesViewAdvanced,
} PreferencesViewType;
 さらに、それぞれのヴュウと接続するメンバ変数として、ふたつの NSView クラスの変数を宣言する。
@interface PreferencesWindowController : NSWindowController
{
  IBOutlet NSView *generalView_;
  IBOutlet NSView *advancedView_;
}
 最後に、ヴュウを切り替えるアクションメソッドを宣言する。
- (IBAction)switchView:(id)sender;
 実装部は後回しにして、次に xib ファイルを編集する。

環境設定ウィンドウの xib ファイルを編集する

 MainMenu.xib ファイルを開く。
 Object Libaray から Object(NSObject)をドラッグしてトップレヴェルに追加する。オブジェクトの Identity Inspector で Class を先ほど作成した PreferencesWindowController に設定する。MainMenu の Preferences... メニューアイテムから PreferencesWindowController へ Control + ドラッグして showWindow: と接続する。

 Object Library から Panel(NSPanel)をキャンバスにドラッグして追加する。パネルの Attribute Inspector で以下の設定を行う。
  • Style を Regular Panel に変更
  • Controls の Resize と Minimize からチェックを外す
  • Behavior の Hide on Deactivate と Visible At Launch からチェックを外す
 次に PreferencesWindowController からパネルへ Control + ドラッグして window と接続する。

 Object Library からToolbar(NSToolbar)をパネルにドラッグして追加する。ツールバーの Attribute Inspector で Customizable からチェックを外す。
 ツールバーの Allowed Toolbar Items からすべてのツールバーアイテムを削除し、代わりに Object Library から Image Toolbar Item(NSToolbarItem)をふたつドラッグして追加する。追加したふたつのツールバーアイテムをそれぞれ Default Toolbar Items 欄にドラッグする。ツールバーアイテムの Attribute Inspector より以下のように設定する。
  • Image Name をそれぞれ NSPreferencesGeneralNSAdvanced に設定する
  • Label をそれぞれ General、Advanced に設定する
  • Palette Label をそれぞれ General、Advanced に設定する
  • Tag をそれぞれ 100101 に設定する
    この 100101PreferencesWindowController.h で宣言した列挙型の値に合わせたもの
  • Behavior の Selectable にチェックを入れる
 それぞれのツールバーアイテムからトップレヴェルオブジェクトに追加した PreferencesWindowController へ Control + ドラッグして switchView: アクションと接続する。これでツールバーアイテムをクリックしたときに switchView: アクションが呼ばれるようになる。 

 次に Object Library から Custom View(NSView)をふたつ、キャンバスにドラッグしトップレヴェルに追加する(Panel への追加ではない)。このヴュウのサイズは適当に違う大きさにしておくと、あとで実装するヴュウ切り替えによるウィンドウサイズの変更が解りやすくなる。とりあえず、解りやすいように Label(NSTextField)でも追加して適当な文字列を入力しておく。
トップレヴェルオブジェクトの PreferencesWindowController からふたつのヴュウへ Control + ドラッグして、それぞれ generalView_advancedView_ へ接続する。これで、PreferencesWindowController.m からふたつのヴュウを参照できるようになる。

環境設定ウィンドウのふるまいを実装する

 PreferencesWindowController.m を開き、以下のような switchView: メソッドを実装する。これは前述のツールバーアイテムをクリックしたときに呼ばれるアクションメソッドである。
- (IBAction)switchView:(id)sender
{
  NSWindow *window = [self window];
  NSView *contentView = [window contentView];

  NSArray *subviews = [contentView subviews];
  if([subviews count]) {
    NSView *currentView = [subviews objectAtIndex:0];
    [currentView removeFromSuperview];
  }

  NSToolbarItem *item = (NSToolbarItem *)sender;
  NSString *title = [item label];
  [window setTitle:title];

  NSView *newView = nil;
  switch([item tag]) {
    case PreferencesViewGeneral:
      newView = generalView_;
      break;
    case PreferencesViewAdvanced:
      newView = advancedView_;
      break;
    default:
      return;
  }

  NSRect windowFrame = [window frame];
  NSRect newWindowFrame = [window frameRectForContentRect:[newView frame]];
  newWindowFrame.origin.x = windowFrame.origin.x;
  newWindowFrame.origin.y = windowFrame.origin.y + windowFrame.size.height - newWindowFrame.size.height;
  [window setFrame:newWindowFrame display:YES animate:YES];

  [contentView addSubview:newView];
}
 ここで行なっていることは、
  1. ウィンドウの contentView がサブヴュウを持っている場合はそれを取り除く
  2. sender、つまりツールバーアイテムの label からウィンドウのタイトルを設定する
  3. sender、つまりツールバーアイテムの tag から切り替え先となる新しいヴュウを判別する
  4. ウィンドウと新しいヴュウの frame から、切り替え後のウィンドウの frame を計算する
  5. 計算したウィンドウの新しい frame をウィンドウにアニメーション付きで適用する
  6. ウィンドウの contentView に新しいヴュウを追加する
である。
 ウィンドウの新しい frame について、Mac OS X の座標系は左下原点なので、contentView のサイズを変更しただけでは、ウィンドウの上端が動いてしまうことになる。したがって、ヴュウ切り替え前のウィンドウの上端座標を計算し、そこから切り替え後のウィンドウの高さを引くことで、ウィンドウの新しい原点座標を得る。

 最後に、以下のような awakeFromNib メソッドを追加する。
- (void)awakeFromNib
{
  NSWindow *window = [self window];
  NSToolbar *toolbar = [window toolbar];
  NSArray *toolbarItems = [toolbar items];
  NSToolbarItem *item = [toolbarItems objectAtIndex:0];

  [toolbar setSelectedItemIdentifier:[item itemIdentifier]];
  [self switchView:item];
  [window center];
}
 awakeFromNib メソッドは xib ファイルが読み込まれ、そこに登録されたオブジェクトにアウトレット接続がなされたあとで呼ばれる。ここでツールバーアイテムの初期選択状態や、最初に表示されるヴュウの設定を行う。今回はツールバーアイテムの一番最初のものを初期状態に設定するようにしたが、選択状態を適当な方法で保存しておき、次回起動時にその状態を復元しても良いかもしれない。

ビルドして実行

 以上により、環境設定ウィンドウが完成した。アプリケーションを実行し、アプリケーションメニューから Preferences... を選択して環境設定ウィンドウを表示してみる。ツールバーアイテムのクリックでヴュウが切り替わり、同時にウィンドウサイズの変更がアニメーションを伴って変更されることを確認する。

 今回作成したプロジェクトは GenjiApp/PrefWindowApp - GitHub で公開するので参照のこと。

0 comments:

コメントを投稿