feed

2013年10月02日, 編集履歴

Google Adwardsのプロモーションコードに関するトラブル(2013年10月21日追記)

 「Google Adwordsが一ヶ月分無料」というようなプロモーションコードがGoogleから郵送されてくることがある。無視しても忘れた頃にまた何度も郵送してきたりする。同じようなダイレクトメールを受け取ったことがあるひともいるだろう。

 AdwordsはGoogleの検索結果等に広告を掲載してくれるサーヴィスである。広告を出稿するには当然料金が掛かるわけだが、それを「一ヶ月分無料でお試しください」と謳っているのが前述のプロモーションコードである。

 無料なら、ということでプロモーションコードを入力し、一ヶ月間広告の出稿を行なっていた。
 ところが、ふとクレジットカードの明細を見るとGoogle Adwordsの料金の請求が掛かっている。プロモーションコードの入力は正常に行えており、期間も料金も上限を超えていない。どういうことかとサポートに連絡したところ、以下のような返答をもらった。

 プロモーションコードで「利用金額」分が付与されるので、当然Adwordsを「利用」しなければならない。ところが付与された金額分で「利用」した分を相殺できないというのである。
 ダイレクトメールの文言には「一ヶ月分無料でお試しください」などとあるが、料金は必ず掛かり、その後も利用を継続しなければプロモーションコードの恩恵を受けられないのであれば、それは「無料でお試し」とは言えないのではないか?
 サポートでは請求の取り消しは難しいということである。サポート曰く、同様の連絡がこれまでにもあったということで、過去に比べればダイレクトメールの文面も改善されているということであるが、私はこのように実際に請求されて困っているわけである。

 諦めるしかないのだろうか。

追記(2013年10月21日)

 その後、Google Adwordsの「料金」ページを見ていると「処理中の払い戻し」なる項目があり、払わされた金額分と同額が表示されているのに気がついた。
 これは何だろう、結局返金してくれるのかだろうか。サポートに問い合わせをしたところ、この表示は間違いである、不具合である、払い戻しはない、という返答。
 なるほどなるほど。仮に今回のトラブルの原因がこちらの勝手な誤解、間違いにあったとして、こちらの間違いには容赦なく料金請求をする。しかしGoogleが「払い戻し」の表示をしておいて、払い戻しはない、不具合であるとして自分の間違いからは逃げるわけか。
 ご立派。

2013年08月20日, 編集履歴

佐野昌一(海野十三)の「虫喰い算大会」電子書籍版を公開

 佐野昌一(海野十三)の「虫喰い算大会」を横組みにし、問題部分をSVG画像化した電子書籍をGitHubで公開しました。

 「虫喰い算大会」は、探偵小説家の海野十三が本名の佐野昌一名義で出版した虫喰い算が多数掲載された本で、青空文庫で公開されています。

 AmazonのKindleストア等、青空文庫の作品をラインナップしている電子書籍プラットフォームからでもこの本を入手することは可能ですが、ひとつ問題があります。

 たとえばKindleストアの「虫喰い算大会」は、他の一般的な小説等と同じように縦組みで組版されています。これの何が問題かというと、青空文庫の「虫喰い算大会」の問題部分はプレインテキストでベタ組みされており、そのまま縦組みにすると問題の筆算が90度回転したかたちになってしまいます。さらに筆算の途中でページを跨いでしまうという問題もあり、読みにくいものになっています。
 Kindleストアに並んでいる青空文庫の本は自動変換されたものでしょうし、虫喰い算を扱った本書が一般の小説とは異なる特殊なものであるということで、これは仕方のない事です。

 そこで、「虫喰い算大会」を横組みにし、問題部分をSVG画像化して読みやすくした電子書籍を作成しました。
 問題部分を画像化することで美しく、かつページ跨ぎをさせないように読みやすくしました。底本である元々の「虫喰い算大会」は縦組みっぽいのですが、序文の解き方解説等で数字を多く扱うので、横組みの方が読みやすいだろうということで、横組みにしました。

Mac用EPUBリーダMurasakiでEPUB版を表示

Kindle PreviewerでMOBI版を表示

iPhoneのKindleでAZK版を表示

iPhoneのiBooksでEPUB版を表示

2013年01月04日, 編集履歴

OS Xステータスバーアプリケーションの作り方

ステータスバーアプリケーションについて

 ステータスバーとは、OS Xの画面上部に表示されるメニューバーの、右側に位置する部分である。メニューバーの右端には通知やSpotlightのアイコン、時計表示、システムによって制御される機能(音量やBluetooth等)のアイコン(メニューエクストラ)が並び、その次に様々なアプリケーションのアイコンが並んでいる。

 上記画像で言えば、Google NotifierやDropbox、Adium、ClamXavのアイコンがある赤く塗った領域がステータスバーである。アプリケーション自身が非アクティヴであるときやウィンドウ等を表示していないときも、状態を示したり何らかの機能を呼び出したりする場合にステータスバーにアイコン等の項目(ステータス項目)を配置することができる。

 ここで言う「ステータスバーアプリケーション」とはいわゆる「常駐アプリケーション」と呼ばれるものの一種である。アプリケーションを起動してもウィンドウやDockのアイコンが表示されず、代わりにステータスバーにテキストやアイコンを表示し、そこから呼び出されるメニューやウィンドウを用いる。基本的にはバックグラウンドで常に起動しておき、タイマや何らかのイヴェントをフックして動作するアプリケーションを想定する。
 ステータスバーアプリケーションの特徴は、

 ここでは上記のような特徴を持つアプリケーションを作成する。今回作成したプロジェクトはGenjiApp/StatusBarApp - GitHubで公開するので参照のこと。なおXcodeのヴァージョンは4.5.2を用いた。

Xcodeプロジェクトの作成

 Xcodeを起動し、New Project…からOS X ApplicationのCocoa Applicationを選択する。ここではProject NameをStatusBarAppとし、Create Document-Based Applicationはオフに、Use Automatic Reference Countingをオンにしてプロジェクトを作成した。

常駐アプリケーションとして設定する

 常駐アプリケーションとして設定するには以下の手順を行う。

  1. Xcodeウィンドウ左のProject Navigatorからプロジェクトを選択し、Project Editorを表示させる
  2. Project EditorのTARGETSからアプリケーションのターゲットを選択する
  3. Project EditorのInfoペインを表示させる
  4. Custom OS X Application Target PropertiesからApplication is agent (UIElement) Keyを追加し、TypeをBoolean、ValueをYESに設定する。

 この設定により、アプリケーションは常駐アプリケーションとして起動され、Dockにアイコンが表示されなくなる。同時に、ふつうのアプリケーションであればアクティヴ時にメニューバーに各種メニュー項目が表示されるが、それも表示されなくなり、Command + Option + Escapeで呼び出される「アプリケーションの強制終了」ダイアログにもアプリケーションが表示されなくなる。したがって、後述するメニュー等でアプリケーションを終了する手段を提供しておかないと、アクティビティモニタを使用しないとアプリケーションを終了するできなくなってしまう。

メニューの追加

 MainMenu.xibにステータスバー項目をクリックしたときに表示されるメニューを追加する。

ウィンドウオブジェクトの削除

 MainMenu.xibを開く。XIBファイルにはウィンドウオブジェクトがトップレヴェルに存在しており、アプリケーション起動時にそのウィンドウが自動的に開かれてしまう。今回はウィンドウは必要ないのでこのオブジェクトは削除しておく。ウィンドウオブジェクトを削除したらAppDelegate.hを開き、

@property (assign) IBOutlet NSWindow *window;

の行を削除する。

 後々、環境設定ウィンドウ等を作成するのであれば、このウィンドウオブジェクトをとっておいて流用してもよい。その場合は起動時にウィンドウが開かないように、

  1. ウィンドウオブジェクトを選択する
  2. UtilitiesエリアのAttributes Inspectorペインを選択する
  3. BehaviorのVisible At Launchチェックボックスをオフにする

としておく。

メニューの追加とアクション接続

 UtilitiesエリアのObject libraryペインからMenuを選択し、トップレヴェルに追加する。

 追加したメニューは三つのメニュー項目(Item1、Item2、Item3)を持っているので、そのうちのふたつを削除する。残ったひとつのタイトルをQuit StatusBarAppに変更する。Quit StatusBarAppメニュー項目からInterface Builder DockのApplicationまでControlキィを押しながらドラッグを行い、terminate:アクションと接続する。これでQuit StatusBarAppメニュー項目を選択したときにアプリケーションを終了できるようになる。

メニューのアウトレット接続

 追加したメニューをコードから参照できるようアウトレット接続を行う。

 前準備としてAppDelegateにクラスエクステンションを追加する。AppDelegate.mファイルを開き、@implementation AppDelegate行の前に以下の記述を追加する。

@interface AppDelegate ()

@end

 次に、XIBファイルを開いた状態でAssistant editorを表示し、そこにAppDelegate.mを開く。Interface Builder Dockから追加したメニューオブジェクトを選択し(XIBにはじめからあるMain Menuと間違えないように注意)、Controlキィを押しながらAppDelegate.mのクラスエクステンション内までドラッグする。そうするとダイアログがポップアップ表示されるので、

とし、Connectボタンをクリックする。

 アウトレット接続をヘッダファイルで行う場合は実装ファイルにクラスエクステンションを追加する必要はないが、ここではメニューを参照する変数は外部に公開する必要はないので、実装ファイルへ退避させた。

ステータス項目を表示する

 AppDelegate.mを開く。まずステータス項目を保持するインスタンス変数を宣言する。

@implementation AppDelegate
{
  NSStatusItem *_statusItem;
}

 次に以下のようなメソッドsetupStatusItemを定義する。

- (void)setupStatusItem
{
  NSStatusBar *systemStatusBar = [NSStatusBar systemStatusBar];
  _statusItem = [systemStatusBar statusItemWithLength:NSVariableStatusItemLength];
  [_statusItem setHighlightMode:YES];
  [_statusItem setTitle:@"StatusBarApp"];
  [_statusItem setImage:[NSImage imageNamed:@"StatusBarIconTemplate"]];
  [_statusItem setMenu:self.statusMenu];
}

 ここで行っていることを行ごとに説明すると、

  1. NSStatusBarsystemStatusBarクラスメソッドでステータスバーを取得する。

  2. NSStatusBarstatusItemWithLength:インスタンスメソッドでステータス項目を新しく生成し、_statusItemインスタンス変数に保持する。このメソッドの引数にCGFloatの数値を渡すと固定幅のステータス項目が生成されるが、たいていは項目の内容によって幅が可変になるNSVariableStatusItemLength定数を用いればよい。
    なお、このメソッドの返り値はretainされていないオブジェクトであり、オブジェクトが解放されると同時にステータスバーから項目も消えてしまう。したがって、ARC環境では強参照なインスタンス変数やプロパティで、非ARC環境ではretainしてからインスタンス変数に入れるかretainを指定したプロパティで保持しておく必要がある。

  3. setHighlightMode:YESを渡すことで、ステータス項目が選択されたときに、選択された状態が視覚化される(背景が青くなる)。通常はYESを渡しておく。

  4. setTitle:でステータス項目にタイトル文字列を指定する。タイトルが必要なければ不要だが、後述のアイコン等の内容物がないとステータス項目が見えない。

  5. setImage:でステータス項目にアイコン画像を指定する。画像ファイルはあらかじめプロジェクトに追加しておく。アイコンが必要なければ不要だが、前述のタイトル等の内容物がないとステータス項目が見えない。
    なお、上記のコードで画像のファイル名がTemplateで終わった形式になっているのは、その形式の画像ファイルからNSImageを生成すると自動的にテンプレート画像として扱ってくれるからである。テンプレートとして指定された画像をボタンやセグメンテッドコントールに指定すると、それらを選択したとき等にシステムが自動でイイ感じに画像を扱ってくれる(色の反転や非アクティヴ時のグレーアウト等。ただしテンプレート画像は黒と透明のみ)。今回のステータス項目のアイコン画像もテンプレート画像を指定しておくことで選択時にアイコンの色を反転してくれる。
    ファイル名がTemplateで終わっていない画像でも、NSImagesetTemplate:メソッドを使うことでテンプレート画像として扱うことができる。

  6. setMenu:で前項でアウトレット接続したメニューを指定する。これでステータス項目を選択したときにメニューが表示されるようになる。

 最後にapplicationDidFinishLaunching:内でいま実装したsetupStatusItemメソッドを呼び出す。

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
  [self setupStatusItem];
}

 applicationDidFinishLaunching:はアプリケーションの起動が終了した時点で呼ばれるので、そのタイミングでステータスバーにステータス項目が表示されるようになる。

ビルドして実行

 プロジェクトをビルド、アプリケーションを実行すると、ステータスバーにアイコンとタイトルテキストが配置され、それを選択するとメニューが表示されることを確認する。ウィンドウやDockアイコンは表示されないこと、ステータス項目が選択されたときにタイトルやアイコンの色が反転されること、Quit StatusBarAppメニュー項目を選択するとアプリケーションが終了することも確認する。

 以上により、ステータスバーアプリケーションの基本的な実装が完了した。実際に何かの仕事をさせる場合は、タイマ等を使って定期的に処理をしたり、何らかのイヴェントをフックしたりする必要がある。

2012年10月25日, 編集履歴

OS Xアプリケーションにおける環境設定ウィンドウの作り方

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

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

 環境設定ウィンドウの特徴は、

 ここでは、上記のような特徴を持つ環境設定ウィンドウの、ウィンドウとしての振る舞いを実装する。実際にアプリケーションの設定をする方法は別論(NSUserDefaults等を用いる)。

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

 なお、Xcodeはヴァージョン4.5.1を用いた。

Xcodeプロジェクトの作成

 Xcodeを起動し、New Project…からOS X ApplicationのCocoa Applicationを選択する。ここではProject NameをPrefWindowAppとし、Create Document-Based Applicationはオフに、Use Automatic Reference Countingをオンにして、適当な場所にプロジェクトを作成した。

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

 プロジェクトに環境設定ウィンドウを管理するクラスを実装する新しいファイルを追加する。New File…からOS XのCocoaカテゴリィのObjective-C Classを選択肢、ClassをPreferencesWindowController、Subclass ofをNSWindowControllerにして、With XIB for user interfaceチェックボックスをオンにしてファイルを作成する(PreferencesWindowController.h/m/xibのみっつのファイルが作成される)。

 今回の環境設定ウィンドウはふたつのヴューを切り替えて操作するものとする。それぞれのヴューを判別するための定数を列挙型で適当な場所に宣言しておく。ここではPreferencesWindowController.mファイルに宣言した。

enum PreferencesViewType {
  kPreferencesViewTypeGeneral = 100,
  kPreferencesViewTypeAdvanced,
};
typedef NSInteger PreferencesViewType;

 ふたつのヴューが判別できれば値は何でも構わない。

NSWindow のサブクラスを作成する

環境設定ウィンドウの振る舞い

 一般的な環境設定ウィンドウはEscape キィ(あるいはCommand-.)で閉じることができる。環境設定ウィンドウを単なるNSWindowで実装するとこの振る舞いを実現できない。その一方でNSPanelというクラスではEscapeキィでウィンドウを閉じることができる。しかし、単なるNSPanelではメインウィンドウにはなれない。環境設定ウィンドウはアクティヴ時にはメインウィンドウになっていなければならない。さらにメニューバーのWindowメニューにはアプリケーションで開かれているウィンドウの一覧が表示され、環境設定ウィンドウも表示されるが、NSPanelはWindowメニューに表示されない。

メインウィンドウとキィウィンドウ

 OS Xアプリケーションのウィンドウには、メインウィンドウとキィウィンドウ、および非アクティヴなウィンドウというみっつの状態が存在する。

 非アクティヴなウィンドウとはフォーカスが当たっていないウィンドウのことで、他のウィンドウの下にあったり、他のアプリケーションがアクティヴな状態のとき、つまりユーザの現在の操作対象にはなっていないウィンドウのことである。非アクティヴなウィンドウ自身や、そのUI部品は色がグレーアウトし、ウィンドウのドロップシャドウが小さくなる。

 キィウィンドウとはフォーカスが当たっており、ユーザの現在の操作対象になっているウィンドウのことである。

 メインウィンドウとはユーザの現在の操作対象になっているウィンドウのことである。メインウィンドウがキィウィンドウとなっていることが多いが、他のウィンドウ(パネル)がキィウィンドウになっていることもある。メインウィンドウとは別にキィウィンドウが存在する場合、メインウィンドウのタイトルバーの閉じるボタン等はグレーアウトするが、ウィンドウ自身やそのUI部品はグレーアウトせず、ウィンドウのドロップシャドウも大きいままである。

 マウスやキィボードの入力は始めにキィウィンドウに対して送られ、キィウィンドウがそれに応えられない場合はメインウィンドウに伝搬される。いま、メインウィンドウとキィウィンドウが別々にあったとする。キィウィンドウにはテキストフィールドが置いてあり、そこにフォーカスが当たっているとする。テキストエディタアプリケーションだったとして、メインウィンドウがエディタ本体のウィンドウで、キィウィンドウとして検索パネルのような物が表示されている状態である。ユーザの通常のキィ入力はキィウィンドウ(検索パネル)のテキストフィールドで処理される(テキストフィールドに文字が入力される)。Command-Vのようなキィボードショートカットもテキストフィールドで処理される(ペースト)。これらはキィウィンドウが応答できる処理だからである。キィウィンドウが応答できない処理の場合、たとえばCommand-Sの保存はメインウィンドウに伝搬され、エディタアプリケーションの保存ダイアログが開く。

 環境設定ウィンドウを開くと、それまでのメインウィンドウは非アクティヴになり、環境設定ウィンドウがメインウィンドウとなる。これは、それまでのメインウィンドウやそのUI部品がグレーアウトし、そこで処理可能であったメニューコマンドが使用不能に変わることから見て取れる。

NSWindow のサブクラス化

 つまり、環境設定ウィンドウを実装するには、次のどちらかの手法をとる必要がある。

 ここではかんたんな前者の手法をとることにする。

 NSWindowのサブクラスを作成するにあたり、新たにファイルを作成してもよいが、必要なコードは微少なのでPreferencesControllerに同居させる。

 PreferencesWindowController.hを開き、NSWindowのサブクラスを作成する。名前はPreferencesWindowとした。

@interface PreferencesWindow : NSWindow

@end

 次にPreferencesWindowController.mを開き、PreferencesWindowを実装する。NSWindowをEscapeキィで閉じられるようにするには、cancelOperation:を実装してその中でウィンドウを閉じるようにすればよい。また、環境設定ウィンドウのツールバーを隠せてしまってはまずいので、ツールバーの表示、非表示をトグルするメニュー項目を無効化しておく。

@implementation PreferencesWindow

- (void)cancelOperation:(id)sender
{
  [self close];
}

- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)anItem
{
  SEL action = [anItem action];
  if(action == @selector(toggleToolbarShown:)) return NO;
  return [super validateUserInterfaceItem:anItem];
}

@end

 作成したPreferencesWindowは次項で用いる。

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

ウィンドウの設定を行う

 PreferencesWindowController.xibファイルを開く。XIBファイルにはあらかじめウィンドウオブジェクトが用意されているので、そのウィンドウオブジェクトを選択し、UtilitiesエリアのIdentity InspectorペインのCustom Classで前項で作成した PreferencesWindowクラスを指定する。これでウィンドウはEscapeキィで閉じることができるようになる。

 ウィンドウオブジェクトを選択したままでAttributes Inspectorペインにて各種設定を行う。

 次にウィンドウにツールバーを追加する。UtilitiesエリアのObject libraryペインからツールバーを選択し、ウィンドウにドラッグ・アンド・ドロップする。ウィンドウ上部にツールバーが追加されるので、そのツールバーをクリックして選択し、UtilitiesエリアのAttributes InspectorペインのCustomizableチェックボックスをオフにする。

 追加したツールバーを選択した状態でもう一度ツールバーをクリックすると、ツールバーに表示する項目を編集できるようになる。ディフォルトでいくつかの項目が用意されているが、これらをすべて削除し、Object libraryペインからImage Toolbar Itemをふたつドラッグ・アンド・ドロップして追加する。追加したImage Toolbar ItemのAttributes Inspectorペインで、

 追加した Image Toolbar ItemがAllowed Toolbar Itemsにあるので、設定が終わったら、それをDefault Toolbar Items欄にドラッグ・アンド・ドロップしておく。

 Image Name、Label、Palette Labelの値は任意であるがここでは上記のようにした。Tagの値は前項で宣言した列挙型定数に対応している。

ヴューを追加し、アウトレット接続を行う

 UtilitiesエリアのObject LibraryペインからCustom Viewをトップレヴェルにふたつ追加する(ウィンドウ内への追加ではない)。このヴューの上に環境設定ウィンドウのUI部品が乗ることになる。とりあえず、Labelでも追加して適当な文字列を入力しておく。ヴューのサイズは適当に違う大きさにしておくと、あとで実装するヴュー切り替えによるウィンドウのリサイズが解りやすくなる。

 XIBファイルを開いた状態でAssistant editorを表示し、そこにPreferencesWindowController.mを開く。それぞれのヴューからControlキィを押しながらドラッグを行い、Assistant editorで開いたPreferencesWindowController.mのクラスエクステンション部分までドラッグする。そうするとダイアログがポップアップ表示されるので、

とし、Connectボタンをクリックする。これでコードからそれぞれのヴューを参照できるようになる。

アクションを接続する

 続いて、ウィンドウのツールバーに追加したImage Toolbar ItemのひとつからControlキィを押しながらドラッグを行い、Assistant editorに開いたPreferencesWindowController.mのクラスエクステンション部分までドラッグする。そうするとダイアログがポップアップ表示されるので、

とし、Connectボタンをクリックする。これでImage Toolbar Itemをクリックしたときに呼ばれるアクションメソッドとの接続ができた。残ったもうひとつのImage Toolbar ItemからもControl-ドラッグを行い、いま作成したswitchView:アクションメソッドの宣言文でドロップして接続を行い、同じメソッド呼び出しができるようにしておく。

PreferencesWindowControllerの実装

ヴュー切り替えアクションの実装

 PreferencesWindowController.mを開き、以下のようなswitchView:メソッドを実装する。前項でアクション接続した際にスケルトンが作成されているので、中身を埋めていく。

- (IBAction)switchView:(id)sender
{
  NSToolbarItem *item = (NSToolbarItem *)sender;
  PreferencesViewType viewType = [item tag];
  NSView *newView = nil;
  switch(viewType) {
    case kPreferencesViewTypeGeneral: newView = self.generalView; break;
    case kPreferencesViewTypeAdvanced: newView = self.advancedView; break;
    default: return;
  }

  NSWindow *window = [self window];
  NSView *contentView = [window contentView];
  NSArray *subviews = [contentView subviews];
  for(NSView *subview in subviews) [subview removeFromSuperview];

  [window setTitle:[item label]];

  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. sendertagから切り替え先となる新しいヴューを判別する
  2. ウィンドウのcontentViewがサブヴューを持っている場合はそれを取り除く
  3. senderlabelを用いてウィンドウのタイトルを設定する
  4. ウィンドウと切り替え先ヴューのframeから、切り替え後のウィンドウのframeを計算する
  5. 計算した新しいウィンドウのframeをアニメーション付きで適用する
  6. ウィンドウのcontentViewに切り替え後のヴューを追加する

 このswitchView:メソッドは環境設定ウィンドウのツールバー項目をクリックしたときに呼ばれる。つまり、引数のsenderはそのクリックしたImage Toolbar Item(NSToolbarItem)への参照である。Image Toolbar Itemには前項でTag欄に値を設定している。この値はふたつあるヴューのどちらかに対応しているので、その値を取り出してどちらのImage Toolbar Itemがクリックされたか、言い換えればどちらのヴューへの切り替えなのかを判別する。それぞれのヴューはアウトレット接続されたプロパティとしてコードから参照できる。

 ウィンドウの新しいframeについて、OS Xの座標系は左下原点なので、単にcontentViewのサイズを変更しただけではウィンドウの上端が動いてしまうことになる。したがって、ヴュー切り替え前のウィンドウの上端座標から切り替え後のウィンドウの高さを引くことで、新しいウィンドウの原点座標を得ることになる。

初期選択状態の設定

 次にwindowDidLoadメソッドを以下のように書き換える。

- (void)windowDidLoad
{
  [super windowDidLoad];

  NSWindow *window = [self window];
  NSToolbar *toolbar = [window toolbar];
  NSArray *toolbarItems = [toolbar items];
  NSToolbarItem *leftmostToolbarItem = [toolbarItems objectAtIndex:0];
  [toolbar setSelectedItemIdentifier:[leftmostToolbarItem itemIdentifier]];
  [self switchView:leftmostToolbarItem];
  [window center];
}

 windowDidLoadメソッドで、ツールバー項目の初期選択状態や、最初に表示されるヴューの設定を行う。ここではツールバー項目の一番左のものが最初に選択された状態であるとし、それに対応するヴューが最初に表示されるようにした。適当な方法で選択状態を保存しておき、次回起動時にその状態を復元してもいいかもしれない。

半シングルトン化

 環境設定ウィンドウは通常そのアプリケーション全体を通してひとつしか存在しない。たとえばテキストエディタアプリケーションは複数のエディタウィンドウを開くことができるだろうが、環境設定ウィンドウをどこからいつ呼び出しても同じものが使い回されて表示される。

 これをかんたんに行うには、MainMenu.xibのトップレヴェルにPreferencesWindowControllerを追加し、アウトレット接続しておけばよい。そうするとアプリケーション起動と共にPreferencesWindowControllerがインスタンス化され、アウトレット接続された変数を用いる限りアプリケーション起動中はずっと同じインスタンスが使われる。

 しかし環境設定ウィンドウは使うときは使うが、使わないときはまったく使わない。使わないのにアプリケーション起動中ずっとメモリィを消費してしまうのは無駄なので、ここでは必要なときにコードからインスタンスを生成する手法をとる。また、同じインスタンスを使い回すために、シングルトンデザインパターンを用いる。

 シングルトンとは、かんたんに言えば、クラスが自身のインスタンスをただひとつのみ生成し、それを使い回す手法である。通常はインスタンスを生成、取得するクラスメソッドを用意する(Cocoa の場合このクラスメソッドにはsharedあるいはdefault等の接頭辞を付けるのが習わしであるようだ)。

 PreferencesWindowController.hを開き、インターフェイス部を以下のように編集する。インスタンスを生成、取得するクラスメソッドの名前はsharedPreferencesWindowControllerとした。

@interface PreferencesWindowController : NSWindowController

+ (PreferencesWindowController *)sharedPreferencesWindowController;

@end

 次にPreferencesWindowController.hを開き、メソッドを実装する。

+ (PreferencesWindowController *)sharedPreferencesWindowController
{
  static PreferencesWindowController *sharedController = nil;
  if(sharedController == nil) {
    sharedController = [[PreferencesWindowController alloc] init];
  }
  return sharedController;
}

 このクラスメソッドの初回呼び出し時にはstatic変数sharedControllernilであり、したがってif文に入ってインスタンスを生成し、それを返す。次回以降の呼び出しではsharedControllerにはインスタンスが入っているのでif文をスルーしてそのまま返すのみである。

 クラスメソッドから呼ばれるinitメソッドもオーヴァライドする。

- (id)init
{
  self = [super initWithWindowNibName:@"PreferencesWindowController"];
  if(self) {
    // Initialize
  }
  return self;
}

 コードからインスタンスを生成し、同時にウィンドウのXIBファイルの読み込みも済ませるために、init内でinitWithWindowNibName:メソッドを用いる。こうしておけば、インスタンス生成側はallocinitとするだけでウィンドウコントローラのインスタンス生成と対応するXIBファイルの読み込みを行うことができるようになる。

 これにより、sharedPreferencesWindowControllerクラスメソッドを通してインスタンスを得る場合に限り、同じインスタンスが返されることになり、PreferencesWindowControllerのシングルトン化ができた。完璧にシングルトンとするにはメモリィ管理系のメソッドをオーヴァライドする必要があるが、自分がシングルトンデザインパターンを用いるクラスを実装して、自分がそのクラスを使用するということを前提において、ここでは省略する。

環境設定ウィンドウを表示するアクションメソッドを実装する

 前項でPreferencesWindowControllerの実装が終わったので、今度はそれを呼び出す側を実装する。環境設定ウィンドウはアプリケーション起動中はいつでも呼び出せる必要がある。MainMenu.xibはアプリケーション起動時に読み込まれ、そこに登録されたオブジェクトはアプリケーション起動中ずっとインスタンス化されたままなので、今回の用途に適合する。MainMenu.xibには初めからAppDelegateがトップレヴェルに登録されており、ここに環境設定ウィンドウを表示するアクションメソッドを実装するのがかんたんである。

 MainMenu.xibファイルを開き、Assistant editorにAppDelegate.hを開く。MainMenu.xibのMain Menuを選択し、そこにあるアプリケーションメニュー(アプリケーションの名前が付いたメニュー項目)内のPreferences…メニュー項目を選択する。Preferences…メニュー項目からControl-ドラッグしてAssistant editorに開いたAppDelegate.h@interface内にドロップしてアクションを接続する。表示されるポップアップではConnectionでActionを選択、NameでshowPreferencesWindowとする。

 AppDelegate.mを開いて、PreferencesWindowControllerクラスを使えるようにするため、ファイル先頭に#importディレクティヴを追加する。

#import "PreferencesWindowController.h"

 showPreferencesWindow:メソッドはアクション接続時にスケルトンが作成されているので、以下のように編集する。

- (IBAction)showPreferencesWindow:(id)sender
{
  PreferencesWindowController *sharedController = [PreferencesWindowController sharedPreferencesWindowController];
  [sharedController showWindow:sender];
}

 前項で作成したPreferencesWindowControllersharedPreferencesWindowControllerクラスメソッドを用いて、シングルトンインスタンスを取得し、showWindow:インスタンスメソッドで環境設定ウィンドウを表示する。

ビルドして実行

 以上により、環境設定ウィンドウを持つアプリケーションが完成した。プロジェクトをビルド、アプリケーションを実行し、アプリケーションメニューからPreferences…を選択して、以下のことを確認する。