ページ

ラベル ホットキー の投稿を表示しています。 すべての投稿を表示
ラベル ホットキー の投稿を表示しています。 すべての投稿を表示

2009年7月27日月曜日

SimpleCap Hotkeyでエラー #3 解決

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

原因はイージーミス。リセット時にホットキーを管理している NSMutableSet に対して removeAllObjects を投げているつもりが、release が投げられていた。

HotkeyRegister.m

- (void)unregistAll
{
for (Hotkey* hotkey in _hotkey_set) {
[self unregistHotkey:hotkey];
}
// [_hotkey_set release];
[_hotkey_set removeAllObjects];

}


これでは吹っ飛ぶわけだ。リセットを1回でも実行すればオブジェクトがリリースされて2回目以降のリセットでクラッシュする。

合わせてサンプルの修正版も上げておく。
サンプル:Hotkey-8.zip


なぜ書き間違えたか覚えていないが Xcodeの補完機能で出てきたメソッドを確認なしで確定してしまったのかもしれない。

2009年7月21日火曜日

ホットキー変更対応(30) - SimpleCapへの組み込み

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

ホットキー変更機能を SimpleCap へ組み込もう。

まずこれまでに作成したソースコードを SimpleCap のプロジェクトへ組み込む。


これら3つのクラスは汎用的に作成してあるのでそのまま利用できる。


これらのクラスを利用するのはコントローラクラスである、AppController と PreferenceController の2つ。前者はアプリ動作時の処理(ホットキーが押さされた時の処理など)、後者はプリファレンスの管理(表示や変更など)の責務をそれぞれ負っている。コードは省略するが、基本的には前回の AppController クラスで行っていた処理をそれぞれのクラスに割り振って追加しただけ。


次は InterfaceBuilder で環境設定のウィンドウへホットキー変更UIを追加する。


変更に加えて有効/無効のチェックボックスも追加した。


さて実行してみよう。

プリファレンスを開く。


変更する。


変更後のホットキーでメニューが開いた。


リセットも有効/無効も大丈夫なようだ。


なお気になる現象としてコンパイル時に下記の Warningが出ていた。

サンプルアプリの時には出なかったのだが。コンパイルオプションに違いがあるのだろうか。

- - - -
ようやくホットキー変更機能の実装ができた。大した機能ではないが随分時間がかかってしまった。しばらく動作確認を行い問題がなければ1度リリースを出そう(1.0.1ぐらい)。

2009年7月19日日曜日

ホットキー変更対応(29) - リセットの実装

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

さてホットキー変更対応もいよいよ(というかやっと)大詰め。最後の機能であるリセットを実装する。いろいろいじくり回し最終的に下記のようになった。

まずはインターフェイスから。ウィンドウへ「Reset」ボタンを追加し、- [AppController reset:] を呼ぶように設定する。



次に Hotkeyのイニシャライザに targetを加える。これによって NSUserDefaults から取得した値を使い、1回で初期化ができる。
Hotkey.h

- (id)initWithSavekey:(NSString*)savekey number:(NSNumber*)number target:(id)target;



コントローラは初期化とリセット両方で使う共通処理をくくり出してメソッドにしておく。
AppController.m
- (void)initializeHotkeysFromDefaults
{
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
NSNumber* number;
Hotkey *hotkey;

number = [defaults objectForKey:@"HOTKEY1"];
hotkey = [[[Hotkey alloc] initWithSavekey:@"HOTKEY1"
number:number
target:self] autorelease];
[_hotkey_register registHotkey:hotkey];
[defaults setObject:[hotkey numberValue] forKey:[hotkey savekey]];

_text1.hotkey = hotkey;
_text1.target = self;

number = [defaults objectForKey:@"HOTKEY2"];
hotkey = [[[Hotkey alloc] initWithSavekey:@"HOTKEY2"
number:number
target:self] autorelease];
[_hotkey_register registHotkey:hotkey];
[defaults setObject:[hotkey numberValue] forKey:[hotkey savekey]];

_text2.hotkey = hotkey;
_text2.target = self;
}


このメソッドでは NSUserDefaultsから取得した値を無条件に使って Hotkeyを作成&登録する。起動時の初期化とリセット処理からこのメソッドを呼出す。


まずはリセット処理から。
- (IBAction)reset:(id)sender
{
[_hotkey_register unregistAll];
[self resetHotkeyDefaults];
[self initializeHotkeysFromDefaults];
}

リセッットボタンが押されたら、一旦すべてのホットキーの登録を解除(unregistAll)する。

続いて resetHotkeyDetails を呼出し、NSUserDefaults の値を初期化する。
- (void)resetHotkeyDefaults
{
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
NSNumber* number;

number = [Hotkey numberValueWithKeycode:0x23 modifier:(cmdKey | optionKey)];
[defaults setObject:number forKey:@"HOTKEY1"];

number = [Hotkey numberValueWithKeycode:0x25 modifier:(cmdKey | optionKey)];
[defaults setObject:number forKey:@"HOTKEY2"];

}

そして最後に initializeHotkeysFromDefaults を使い、ホットキーの再登録を行う。

まとめると、リセット処理は:
 1. ホットキー全登録解除
 2. NSUserDefaults初期化
 3. NSUserDefaultsの値を使いホットキーを登録する
となる。


一方、起動時の初期化処理はこんな感じ。
- (void)awakeFromNib
{
_hotkey_register = [HotkeyRegister sharedRegister];
if (![[NSUserDefaults standardUserDefaults] objectForKey:@"HOTKEY1"]) {
[self resetHotkeyDefaults];
}
[self initializeHotkeysFromDefaults];
}


if 文は、アプリの起動が初めてかどうかの判断を意味している。今回は安易に HOTKEY1 の値の存在だけで判断している。アプリケーションへ組み込む場合は、そのアプリに合った判断を書くべきだろう。もし初めてであればリセット処理と同じ動きとなる。そうでなければ NSUserDefaultsの値を使いホットキーを登録する。

初期化処理をまとめると:
 1. もし初めてなら NSUserDefaults を初期化する
 2. NSUserDefaultsの値を使いホットキーを登録する
となる。


なおNSUserDefaultsへ保存する NSNumber値の計算メソッッドをHotkeyのクラスメソッドとして公開しておく。以前のコードもそれに合わせて書き直す。
Hotkey.m
+ (NSNumber*)numberValueWithKeycode:(UInt32)code modifier:(UInt32)modifier
{
return [NSNumber numberWithUnsignedInt:(modifier<<16|code)];
}

- (NSNumber*)numberValue
{
return [Hotkey numberValueWithKeycode:self.code modifier:self.modifier];
}





サンプル:Hotkey-7.zip

- - - -
ようやく一通りの実装ができた。
いよいよ SimpleCap への組み込みに入る。

2009年7月18日土曜日

ホットキー変更対応(28) - NSUserDefaultsへ保存 #2

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

モデル側(Hotkey)の用意ができたのでコントローラに処理を入れる。

まず初期化コード。
AppController.m

- (void)awakeFromNib
{
_hotkey_register = [HotkeyRegister sharedRegister];

NSNumber* number;
Hotkey *hotkey;
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];

number = [defaults objectForKey:@"HOTKEY1"];
if (number) {
hotkey = [[[Hotkey alloc] initWithSavekey:@"HOTKEY1" number:number] autorelease];
} else {
hotkey = [[[Hotkey alloc] init] autorelease];
hotkey.savekey = @"HOTKEY1";
hotkey.code = 0x23; // 'S'
hotkey.modifier = cmdKey | optionKey;
hotkey.target = self;
}
[_hotkey_register registHotkey:hotkey];

_text1.hotkey = hotkey;
_text1.target = self;
:


NSUserDefaults から値を取り出しそれを使って - [Hotkey initWithSavekey:number:]でインスタンス化する。もし値がなければ(すなわち初回)Command + Option + S のキー設定を行う。


ホットキーが変更された時には逆に NSUserDefaults へ保存する。
- (BOOL)hotkeyShouldChange:(Hotkey*)hotkey
{
NSLog(@"hotkeyShouldChange was called: %@", hotkey);

[[NSUserDefaults standardUserDefaults] setObject:[hotkey numberValue] forKey:[hotkey savekey]];
[_hotkey_register registHotkey:hotkey];
return YES;
}


これでアプリケーションを終了しても変更内容が次回起動時に反映される。

サンプル:Hotkey-6.zip

2009年7月17日金曜日

ホットキー変更対応(27) - NSUserDefaultsへ保存 #1

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

ホットキーの値を NSUserDefaults へ保存する。今回はその準備で Hotkey へいくつかメソッドを追加しよう。

Hotkey.h

@interface Hotkey : NSObject {
:
NSString* _savekey;
}
:
@property (retain) NSString* savekey;
:
- (NSNumber*)numberValue;
- (id)initWithSavekey:(NSString*)savekey number:(NSNumber*)number;


メンバ変数に _savekey を加え、プロパティを宣言する。ここには NSUserDefaults で保存する時の key(文字列)を格納しておく。numberValue は保存時に modifier と code を NSNumber型に変換して返すメソッド。

実装はこんな感じ。
Hotkey.m
- (NSNumber*)numberValue
{
return [NSNumber numberWithUnsignedInt:(self.modifier<<16|self.code)];
}

- (id)initWithSavekey:(NSString*)savekey number:(NSNumber*)number
{
self = [super init];
if (self) {
UInt32 value = [number unsignedIntValue];
self.savekey = savekey;
self.modifier = value >> 16;
self.code = value & 0xffff;
}
return self;
}


-[numberValue]では上位16ビットを modifier、下位16ビットをキー値とする UInt32値を作り、これを NSNumberに入れて返す。modifier, code 共に UInt32なのだが、実際は 16ビットしか使っていないので(安易だが)これでいい。-[initWithSavekey:number:]はその逆の動きで、NSNumberで渡された値を分解して modifier と code へ戻す。

- - - -
動作確認は次回以降。

2009年7月14日火曜日

ホットキー変更対応(26) - 修正2/キー変更時の一貫性

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

これまでのコードだと、ホットキー変更時の主導権が HotkeyTextView にあり、なんらかの理由でコントローラ側がキャンセルしようとしてもできない。実際のアプリケーションでは、コントローラが UserDefaults などに変更内容を保存(永続化)する。何かの理由でこの時に保存に失敗した場合は表示と保存値が食い違い、一貫性が保てなくなってしまう。

前回までのコードはこんな感じ。
HotkeyTextView.m

- (void)keyDown:(NSEvent *)theEvent
{
:
_hotkey.modifier = modifier;
_hotkey.code = keycode;

if (self.target && [self.target respondsToSelector:@selector(hotkeyShouldChange:)]) {

[self.target performSelector:@selector(changedHotkey:) withObject:self.hotkey];

// ↑この時点でコントローラがキャンセルしようとしてできない。
// コントローラが処理に失敗した場合でも、そのまま処理は続行されて
 // 表示は更新されたように見える。
}
:
}



コントローラのメソッド(上記では changeHotkey:)を一方的に呼び出すだけではなく、その戻り値を見て変更を続行するかどうか判断できるように修正する。今回は -[NSWindow windowShouldClose:] をお手本にして実装してみる。メソッド名も hotkeyShouldChange: としてみた。

UInt32 old_modifier = _hotkey.modifier;
UInt32 old_code = _hotkey.code;

if (self.target && [self.target respondsToSelector:@selector(hotkeyShouldChange:)]) {

_hotkey.modifier = modifier;
_hotkey.code = keycode;

NSNumber* result =[self.target performSelector:@selector(hotkeyShouldChange:) withObject:self.hotkey];

if ([result boolValue]) {
[self redraw];
[self endEdit];
} else {
_hotkey.modifier = old_modifier;
_hotkey.code = old_code;
}
}


変更前の値をとっておき(old_modifier, old_code)、hotkeyShouldChange: の結果を元に処理を行う。YESが帰ったらそのまま画面表示を更新して入力を終了する。一方 NO の場合は、変更前の値に戻して入力受付を続行する(見た目には何もおこっていないように見える)。

呼び出されるコントローラ側のメソッドでは結果に NSNumber のインスタンスを返す。
AppController.m
- (NSNumber*)hotkeyShouldChange2:(Hotkey*)hotkey
{
[_hotkey_register registHotkey:hotkey];
return [NSNumber numberWithBool:YES];
}

※上記はサンプルアプリなので UserDefaultsへの保存はまだ実装されていない。

2009年7月13日月曜日

ホットキー変更対応(25) - リファクタリング / プロパティを減らす

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

ホットキーが押された時に Hotkey.target の Hotkey.action が呼び出される。同様に ホットキーが変更された場合、HotkeyTextView.target の HotkeyTextView.action が呼び出される。

汎用的に各 action を設定できるようにしたが、これらのメソッドは別に決め打ちでかまわないので、各プロパティを無くして、決まったメソッドを呼び出す様に修正した。

Hotkeyは、hotkeyDown:(Hotkey*) を呼び出す。
HotkeyTextViewは、changedHotkey:(Hotkey*) を呼び出す。

hotkeyDown: を呼び出す箇所のコードはこんな感じ。
HotkeyRegister.m

if (hotkey.target && [hotkey.target respondsToSelector:@selector(hotkeyDown:)]) {
[hotkey.target performSelector:@selector(hotkeyDown:) withObject:hotkey];
} else {
NSLog(@"WARNING: Hotkey.target is null, or Hotkey.target instance does not implement hotkeyDown:(Htokey*), you should write the method on Hotkey.target");
NSLog(@"Hotkey: %@", hotkey);
NSLog(@"Hotkey.target: %@", hotkey.target);
}


hotkey.target と hotkeyDown: の存在チェックを行った後、hotkey.targetへメッセージを投げる。もしどちらにも該当しない場合は WARNINGをログへ書き出す。メソッド実装が必要であることはリファレンスマニュアルを書けばいいのだが、個人開発ではこんな安易な方法でもいいだろう。

2009年7月11日土曜日

ホットキー変更対応(24) - キー設定の変更をシステムへ反映する

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

前々回まででホットキー設定 UIのひな形ができたが、変更してもシステムへは反映していなかった。今回はその実装を行う。

まず HotkeyTextView で変更があったことをコントローラへ通知する必要がある。今回は自前のターゲット/アクションを用意しよう。

まず HotkeyTextViewへ target と action プロパティを用意する。

HotkeyTextView.h

@interface HotkeyTextView : NSTextView {
:
id _target;
SEL _action;
}
:
@property (retain) id target;
@property (retain) SEL action;

@end



次にホットキーが変更された場合に target に対して action メソッドを呼び出す処理を用意する。これは keyDown: の中がいいだろう。

HotkeyTextView.m
- (void)keyDown:(NSEvent *)theEvent
{
:
// 確定処理
if (_hotkey.modifier != modifier || _hotkey.code == keycode) {
_hotkey.modifier = modifier;
_hotkey.code = keycode;

if (self.target && [self.target respondsToSelector:self.action]) {
[self.target performSelector:self.action withObject:self.hotkey];
}
}

[self redraw];
[self endEdit];
}

前回は同じキーが押された場合の処理が抜けていたのでついでに加えておいた(実際にはホットキーが起動するため、イベントはとれないのだが)。
必要な情報を Hotkey へ格納した後、target / action の存在チェックを行い、メッセージを投げる。引数として Hotkeyを渡しておく。


一方、使う側はこんな感じ。
AppController.m
- (void)awakeFromNib
{
_hotkey_register = [HotkeyRegister sharedRegister];

Hotkey *hotkey;

hotkey = [[[Hotkey alloc] init] autorelease];
:
_text1.hotkey = hotkey;
_text1.target = self; <--追加
_text1.action = @selector(changeHotkey:); <--追加
:


変更時に呼び出される changeHotkey: でシステムへの反映を行う。

- (void)changeHotkey:(id)sender
{
NSLog(@"changeHotkey was called: %@", sender);
[_hotkey_register registHotkey:sender];
}


- [HotkeyRegister registHotkey] は直前のホットキーを取り消した(unregist)後に、新しいキーを登録するように実装してある。

さて実行してみよう。デバッグコンソールで動作を追って見る。
「⌥⌘P」を「⌃⌥⌘⇧Space」へ変更してみよう。

[Session started at 2009-07-11 06:10:45:24 +0900.]
:
2009-07-11 06:10:34.376 Hotkey[1062:10b] changeHotkey was called: <Hotkey: 0x1559a0>
↑ホットキーを変更すると changeHotkey: が呼び出された。
2009-07-11 06:10:34.416 Hotkey[1062:10b] unregistHotkey: <Hotkey: 0x1559a0>
 ↑直前のキーが取り消され
2009-07-11 06:10:34.434 Hotkey[1062:10b] registHotkey: keyid=2, modifier=1b00, code=31, ref=1aefe0,
target=<AppController: 0x151730>, action=keyDown:
 ↑新しいキーが登録された
2009-07-11 06:10:34.464 Hotkey[1062:10b] {(
<Hotkey: 0x155760>,
<Hotkey: 0x1559a0>
)}
2009-07-11 06:10:38.954 Hotkey[1062:10b] keyDown: keycode=31 [⌃⌥⌘⇧Space]
 ↑試しに新しいホットキーを押すとちゃんと反応した。
2009-07-11 06:10:44.979 Hotkey[1062:10b] keyDown: keycode=25 [⌥⌘L]




サンプル:HotKey-4.zip

2009年7月9日木曜日

ホットキー変更対応(23) - キー設定UIの実装

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

さて次はホットキー設定のユーザインターフェイスを実装する。以前の検証で作った NSTextViewのサブクラスを持ってきて修正する。クラス名は HotkeyTextView。

HotkeyTextView.h

@class Hotkey;
@interface HotkeyTextView : NSTextView {

BOOL _is_editing;
Hotkey* _hotkey;
}

- (Hotkey*)hotkey;
- (void)setHotkey:(Hotkey*)hotkey;

@end

setHotkey: は再描画処理を書きたいのでプロパティを使わず自前で実装する。

以下、実装。以前紹介した時とほぼ同じ。大きく違うのは Hotkey クラスを使うところ。
Hotkey.m

まずセッターとゲッター
- (Hotkey*)hotkey
{
return _hotkey;
}

- (void)setHotkey:(Hotkey*)hotkey
{
[hotkey retain];
[_hotkey release];
_hotkey = hotkey;
[self redraw];
}


再描画は - [Hotkey string] で得た文字列を使う。
- (void)redraw
{
[self setString:[_hotkey string]];
}


イベント系:ダブルクリック検出
- (void)mouseDown:(id)theEvent
{
if ([theEvent clickCount] >= 2) {
[self startEdit];
}
}

startEdit でキー入力モードへ移行する。

- (void)startEdit
{
[[self window] makeFirstResponder:self];
[self setSelectable:YES];
_is_editing = YES;
NSRange range = NSMakeRange(0, [[self string] length]);
[self setSelectedRange:range];
}

- (void)endEdit
{
[self setSelectable:NO];
_is_editing = NO;
}


_is_editing フラグで入力モードを管理している。キー入力を受け付けるモードになったら、現在のキー文字列を選択状態(反転表示)にする。


イベント処理:キー入力
- (void)keyDown:(NSEvent *)theEvent
{
if (!_is_editing) {
return;
}

UInt32 modifier = 0;
UInt32 keycode = [theEvent keyCode];

NSUInteger modifier_flags = [theEvent modifierFlags];
if (modifier_flags & NSShiftKeyMask) {
modifier |= shiftKey;
}
if (modifier_flags & NSCommandKeyMask) {
modifier |= cmdKey;
}
if (modifier_flags & NSAlternateKeyMask) {
modifier |= optionKey;
}
if (modifier_flags & NSControlKeyMask) {
modifier |= controlKey;
}

if (!([Hotkey isHotKeyForModifier:modifier])) {
if (keycode == kVK_Escape) {
[self redraw];
[self endEdit];
}
// abort
return;
}

if (![Hotkey isHotKeyForKeyCode:keycode]) {
// abort
return;
}

_hotkey.modifier = modifier;
_hotkey.code = keycode;

[self redraw];
[self endEdit];
}

最初に修飾キーを判定し、その後ホットキーとして受け付けるかどうかのチェックを入れる。それらのチェックが済んだら Hotkeyインスタンスへmodifierとcodeを設定し再描画させる。最後に endEdit を呼んで入力モードを終了する。

ホットキーとして登録できるのはCommand/Option/Controlerのいづれかの修飾キーが押されている場合のみで、そのルールは Hotkey クラスに用意しておく。
Hotkey.m
+ (BOOL)isHotKeyForModifier:(UInt32)modifier
{
if (modifier & (cmdKey | optionKey | controlKey)) {
return YES;
} else {
return NO;
}
}




HotkeyTextViewクラスが実装できたので、これを試すサンプル画面を作ってみる。
InterfaceBuilderで HotkeyTextView を2つ貼付けてみた。


AppControllerでアウトレットを設定し、これらへ Hotkeyを指定する。
AppController.m
- (void)awakeFromNib
{
_hotkey_register = [HotkeyRegister sharedRegister];

Hotkey *hotkey;

hotkey = [[[Hotkey alloc] init] autorelease];
hotkey.code = 0x23; // 'S'
hotkey.modifier = cmdKey | optionKey;
hotkey.target = self;
hotkey.action = @selector(keyDown:);
[_hotkey_register registHotkey:hotkey];
[_text1 setHotkey:hotkey];

hotkey = [[[Hotkey alloc] init] autorelease];
hotkey.code = 0x25; // 'L'
hotkey.modifier = cmdKey | optionKey;
hotkey.target = self;
hotkey.action = @selector(keyDown:);
[_hotkey_register registHotkey:hotkey];
[_text2 setHotkey:hotkey];

}


これでできあがり。動かしてみよう。
まず初期表示。


よし、問題なし。


次にダブルクリックして変更してみる。


大丈夫だ。

とりあえずいいようだ。

サンプル:HotKey-3.zip

- - - -
今回はホットキーを変更しても表示が変わるだけでシステムへの再登録は行っていない。
この辺りは次回以降。

2009年7月8日水曜日

ホットキー変更対応(22) - ふたたびキー表示

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

ホットキー登録ができるようになったので今度は登録キーの表示を行う。以前の検証を思い出しつつ Hotkeyクラスを拡張して表示ロジックを組み込む。動作確認の為、前回のサンプルにキーを表示させてみよう。

まずキーの文字表現から。以前の設計では HotkeyCharConverter なんて大げさな名前のクラスを用意するつもりだったが、ホットキー用のモデルクラス Hotkey を用意することになったので、ここに文字表現の責務を持たせることにする。

Hotkey を次のように拡張する。

Hotkey.h

@class HotkeyRegister;
@interface Hotkey : NSObject {
:
}

- (NSString*)string;
- (BOOL)isHotkey;

@end


- [Hotkey string] で修飾キーを含めた文字表現を取得できる。
例えば Command+Option+P なら @"⌥⌘P" を返す。


実装はこんな感じ。まずマッピング用の構造体配列を定義する。
Hotkey.m
static const struct {
UInt32 keycode;
NSString* description;
NSString* string;
BOOL is_hotkey;
}
keymap[] = {
{ kVK_ANSI_A, @"A" , @"A" , YES },
{ kVK_ANSI_S, @"S" , @"S" , YES },
:


続いて追加メソッドの実装。
- (NSString*)string
{
NSString* key_desc = @"";

if (self.modifier & controlKey) {
key_desc = [key_desc stringByAppendingFormat:@"%C", kControlUnicode];
}
if (self.modifier & optionKey) {
key_desc = [key_desc stringByAppendingFormat:@"%C", kOptionUnicode];
}
if (self.modifier & cmdKey) {
key_desc = [key_desc stringByAppendingFormat:@"%C", kCommandUnicode];
}
if (self.modifier & shiftKey) {
key_desc = [key_desc stringByAppendingFormat:@"%C", kShiftUnicode];
}
key_desc = [key_desc stringByAppendingFormat:@"%@", _keymap[[self indexOfKeymap]].string];

return key_desc;
}


- (BOOL)isHotkey
{
return _keymap[[self indexOfKeymap]].is_hotkey;
}


それとキーマップのインデックスを取得するメソッドを用意。
- (UInt32)indexOfKeymap
{
UInt32 index;
UInt32 max = sizeof(_keymap)/sizeof(_keymap[0]);
for (index=0; index < max; index++) {
if (_keymap[index].keycode == self.code) {
break;
}
}
if (index == max) {
index = 0; // DUMMY
}
return index;
}



さて実行してみよう。今回は押したキーの文字表現をデバッガコンソールへ表示するようにしてみた。
@implementation AppController

- (void)keyDown:(Hotkey*)hotkey
{
NSLog(@"keyDown: keycode=%x [%@]", hotkey.code, [hotkey string]);
}
:



結果はこう。


おお出た。


サンプル:HotKey-2.zip

2009年7月7日火曜日

ホットキー変更対応(21) - 実装サンプル

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

サンプルアプリを作り昨日まで作成したクラスの動作確認を行う。
サンプル:HotKey-1.zip

いつものごとく AppController を用意し、InterfaceBuilderでインスタンス化しておく。そして実行時 Nibファイル(Xib)読み込み後に呼び出される awakeFromNib でホットキーの登録を行う。

AppController.m

- (void)awakeFromNib
{
_hotkey_register = [HotkeyRegister sharedRegister];

Hotkey *hotkey;

hotkey = [[[Hotkey alloc] init] autorelease];
hotkey.code = 0x23; // 'P'
hotkey.modifier = cmdKey | optionKey;
hotkey.target = self;
hotkey.action = @selector(keyDown:);
[_hotkey_register registHotkey:hotkey];

hotkey = [[[Hotkey alloc] init] autorelease];
hotkey.code = 0x25; // 'L'
hotkey.modifier = cmdKey | optionKey;
hotkey.target = self;
hotkey.action = @selector(keyDown:);
[_hotkey_register registHotkey:hotkey];

}


試しに Command + Option + P or L の2つのキーを登録してみた。これらのキーが呼び出されると - [AppController keyDown:] が呼び出される。

- (void)keyDown:(Hotkey*)hotkey
{
NSLog(@"keyDown: keycode=%x", hotkey.code);
}



後始末として NSApplication のデリゲートメソッド - [NSApplication applicationWillTerminate:] で - [HotkeyRegister unregistAll] を呼び出す。

- (void)applicationWillTerminate:(NSNotification *)aNotification
{
[_hotkey_register unregistAll];
}



さて実行してみよう。

アプリケーションが立ち上がったら Command + Option + P または L を押す。

デバッガコンソール
[Session started at 2009-07-07 22:42:07 +0900.]
2009-07-07 22:42:08.584 Hotkey[22009:10b] registHotkey: keyid=0, modifier=900, code=23, ref=13c020,
target=, action=keyDown:
2009-07-07 22:42:08.589 Hotkey[22009:10b] {(

)}
2009-07-07 22:42:08.592 Hotkey[22009:10b] registHotkey: keyid=1, modifier=900, code=25, ref=12cbc0,
target=, action=keyDown:
2009-07-07 22:42:08.663 Hotkey[22009:10b] {(
,

)}
2009-07-07 22:42:11.813 Hotkey[22009:10b] keyDown: keycode=25
2009-07-07 22:42:12.845 Hotkey[22009:10b] keyDown: keycode=23
2009-07-07 22:42:14.238 Hotkey[22009:10b] unregistHotkey:
2009-07-07 22:42:14.243 Hotkey[22009:10b] unregistHotkey:
2009-07-07 22:42:14.245 Hotkey[22009:10b] finished

The Debugger has exited with status 0.


ちゃんと - [AppController keyDown:] が呼び出されているようだ。
アプリケーション終了時の - [HotkeyRegister ungregistHotkey:] も最後に呼び出されている。

2009年7月6日月曜日

ホットキー変更対応(20) - HotkeyRegisterの実装

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

ホットキーをシステムへ登録するクラスの実装に入る。

ヘッダはこんな感じ。
HotkeyRegister.h

@class Hotkey;
@interface HotkeyRegister : NSObject {

NSMutableSet* _hotkey_set;
}

@property (retain) NSMutableSet* hotkey_set;

+ (HotkeyRegister*)sharedRegister;
- (void)unregistAll;

- (BOOL)registHotkey:(Hotkey*)hotkey;
- (BOOL)unregistHotkey:(Hotkey*)hotkey;

@end


NSMutableSet の _hotkey_set に Hotkeyのインスタンスを追加して管理する。ここで - [Hotkey isEqual:] が生きてくる(今回は keycodeと modifier両方で同値性をチェックしている)。

またインスタンスは1つあれば十分なので、Singletonパターンを使い、インスタンスは sharedRegister で取得させる。


次に実装。

ホットキー登録部分は以前紹介したように Carbon APIを使う。
Cocoaの日々 - ホットキー (2008/04)


こんな感じ。
HotkeyRegister.m
#define SC_HOTKEY_SIGNATURE 'schk'

OSStatus hotKeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData);

static HotkeyRegister* _hotkey_register = nil;
static UInt32 _hotkey_id = 0;

@implementation HotkeyRegister
@synthesize hotkey_set = _hotkey_set;

- (void)unregistAll
{
for (Hotkey* hotkey in _hotkey_set) {
[self unregistHotkey:hotkey];
}
[_hotkey_set release];

NSLog(@"finished");
}


+ (HotkeyRegister*)sharedRegister
{
if (!_hotkey_register) {

_hotkey_register = [[HotkeyRegister alloc] init];
_hotkey_register.hotkey_set = [[NSMutableSet alloc] init];
EventTypeSpec eventTypeSpecList[] ={
{ kEventClassKeyboard, kEventHotKeyPressed }
};

InstallApplicationEventHandler(
&hotKeyHandler, GetEventTypeCount(eventTypeSpecList),
eventTypeSpecList, self, NULL);
}

return _hotkey_register;
}


// Hot key handler
OSStatus hotKeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData)
{
EventHotKeyID hotKeyID;
GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL,
sizeof(hotKeyID), NULL, &hotKeyID);

if (hotKeyID.signature == SC_HOTKEY_SIGNATURE) {

for (Hotkey* hotkey in _hotkey_register.hotkey_set) {
if (hotKeyID.id == hotkey.keyid) {
[hotkey.target performSelector:hotkey.action withObject:hotkey];
}
}
}
return noErr;
}

- (BOOL)unregistHotkey:(Hotkey*)hotkey
{
OSStatus status = UnregisterEventHotKey(hotkey.ref);

if (status != noErr) {
NSLog(@"UnregisterEventHotKey() was failed : %d", status);
return NO;
}
NSLog(@"unregistHotkey: %@", hotkey);
return YES;
}

- (BOOL)registHotkey:(Hotkey*)hotkey
{
// replace or overwrite ??
if ([_hotkey_set containsObject:hotkey]) {
// same hotkey exists, then replace it
if (![self unregistHotkey:hotkey]) {
// error
return NO;
}
}

EventHotKeyID hotKeyID;
hotKeyID.id = _hotkey_id++;
hotKeyID.signature = SC_HOTKEY_SIGNATURE;
OSStatus status;
EventHotKeyRef hotkeyRef;

status = RegisterEventHotKey(hotkey.code, hotkey.modifier, hotKeyID,
GetApplicationEventTarget(), 0, &hotkeyRef);
hotkey.ref = hotkeyRef;
hotkey.keyid = hotKeyID.id;

if (status != noErr) {
NSLog(@"RegsiterEventHotKey() was failed : %d", status);
return NO;
}

[_hotkey_set addObject:hotkey];

NSLog(@"registHotkey: %@", [hotkey dump]);
NSLog(@"%@", _hotkey_set);


return YES;

}

@end


ホットキー毎にとっておく必要のある情報(idやEventHotKeyRef)は、あらかじめ用意しておいた Hotkeyのプロパティへ保存しておく。id は登録毎に新規に発番するようにしている。
登録後、システムからのコールバックを受けて hotKeyHandler( ) が呼び出される。この処理で _hotkey_set の中に登録されている Hotkey 情報を調べ、id が一致するものがあれば、Hotkey.target へ Hotkey.action のメッセージを投げる( - [NSObject performSelector:withObject: ] を使う)。

その他、後始末用の unregistAll などを用意しておく。

- - - -
ここまで作ったクラスの動作確認を行うため
次回は一度サンプルアプリに組み込んで動作させてみる。

2009年7月4日土曜日

ホットキー変更対応(19) - 実装 利用側

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

HotkeyRegister へ行く前に利用する側のコードをイメージしてみる。その結果によって必要なインターフェイスが決まる。

サンプルを作り、利用側のイメージを固めてみる。
例えばこんな感じ。

AppController.m

- (void)awakeFromNib
{
Hotkey* hotkey = [[[Hotkey alloc] init] autorelease];
hotkey.code = 0x29;
hotkey.modifier = cmdKey | optionKey;
hotkey.target = self;
hotkey.action = @selector(keyDown:);

HotkeyRegister* hotkey_register = [Hotkeyregister sharedRegister];
[hotkey_register registHotkey:hotkey];
}


Hotkey のインスタンスを作り、そこへキーコードや修飾キー(modifier)、そしてキーが押された時に呼び出されるインスタンス(target)とメソッド(action)を設定する。

HotkeyRegister は registHotkey: を受け、この情報をもとにホットキーをシステムへ登録する。ホットキーが押された時にシステムからHotkeyRegisterの特定のメソッドがコールバックされるので、その中で [target action] を呼び出す。

実装は続く。。

- - - - -
こんな本が出ていた。


こんな記事も出ている。
有料ゲーム危機の時代 iPhoneアプリは「ゼロ化」の法則に立ち向かえるか

2009年7月3日金曜日

ホットキー変更対応(18) - 実装 Hotkeyクラス#2

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

昨日の続き。

Hotkeyの同値チェックは id ではなく、キーコードで行うべきだな。昨日の isEqual: は次のようになる。

- (BOOL)isEqual:(id)anObject
{
if (anObject == self) {
return YES;
}
if (!anObject || ![anObject isKindOfClass:[self class]]) {
return NO;
}
if (self.code == [anObject code] && self.modifier == [anObject modifier] ) {
return YES;
}
return NO;
}


キーコードと装飾キー(modifier)の組み合わせで判断する。

NSArray, NSSet の containsObject: を呼ぶと、コレクション内のオブジェクトに対して isEqual: を投げて同値チェックを行う。containsObject: は後ほど HotkeyRegister で使う。

2009年7月2日木曜日

ホットキー変更対応(17) - 実装 Hotkeyクラス

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

ホットキーも長くなった。今実装に入っている。先日の設計には無かったがキー情報を保持するクラスがあると便利なので用意した。

こんな感じ。
Hotkey.h

@interface Hotkey : NSObject {

// Hotkey attrs
UInt32 _keyid; //TODO: id
UInt32 _modifier;
UInt32 _code;
EventHotKeyRef _ref;

// Handler attrs
id _target;
SEL _action;

// keydown:(Hotkey*)hotkey;
}

@property UInt32 keyid;
@property UInt32 modifier;
@property UInt32 code;
@property EventHotKeyRef ref;
@property (retain) id target;
@property SEL action;


Hotkey.m
@implementation Hotkey

@synthesize keyid = _keyid;
@synthesize modifier = _modifier;
@synthesize code = _code;
@synthesize ref = _ref;
@synthesize target = _target;
@synthesize action = _action;

- (BOOL)isEqual:(id)anObject
{
if (anObject == self) {
return YES;
}
if (!anObject || ![anObject isKindOfClass:[self class]]) {
return NO;
}
if (self.keyid != self.keyid) {
return NO;
}
return YES;
}


※未完成でまだテストしていない


今のところ実装はオブジェクトの同値性をチェックする isEqualのみ。これは [NSArray containsObject] の中で使われる。

- - - -
キー値と表示文字用に HotkeyCharMapper なるクラスを用意するつもりだったが、このクラスの責務を考えると統合しても良さそう。

2009年6月29日月曜日

ホットキー変更対応(16) - 設計

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

検証が大体できてきたので SimpleCapへの組み込みを始めよう。

組み込みにあたり、実際の動作パターンを整理してクラスを設計してみる。使い方のパターンは4つが考えられる。

(1) ホットキーの初期登録


アプリケーション起動時にUserDefaultsの値を使い、ホットキーをシステムへ登録する。


(2) ホットキーの表示


ユーザがプリファレンスを開いた時にUserDefaultsの値を文字列に変換して表示する。


(3) ホットキー入力


ユーザが入力したホットキーをシステムおよび UserDefaultsへ登録する。最後に文字列に変換して表示する。


(4) デフォルトに戻す。


ボタンが押されたら UserDefaultsをデフォルトに戻し、システムへホットキーを登録する。最後に文字列に変換して表示する。

こんなところか。

- - - -
CocoaBindings が使えるともう少し楽なのだが InterfaceBuilderが使えないので今回は自前でデータと表示を結びつける。ホットキー入力UIの部分に NSTextField で keyDown: が使えればいいのだが、その後も調べたが良い方法は見つからなかった。最終的には今まで見てきた様に NSTextView を使う。

2009年6月27日土曜日

ホットキー変更対応(15) - 入力ルールを調整

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

キーの入力ルールを調整する。

システム環境設定では大体次のようになっていた。

・Shift + キーは受け付けない
・Command + キーは OK
・Option + キーは OK
・Control + キーは OK
・Shiftは他の Command, Option, Control との組み合わせなら OK
・Escapeキーは他の Command, Option, Control との組み合わせなら OK
・Returnキーは他の Command, Option, Control との組み合わせなら OK
・入力中に Escapeキーを押すとキャンセル

その他、Deleteは不可など。

独自にルールを決める必要も無いので、上記に従う様にした。

Deleteなどキー毎に使えるかどうかの判断は以前定義した構造体のパラメータで判断する。

static const struct {
UInt32 keycode;
NSString* description;
NSString* string;
BOOL is_hotkey;
}
@"Unkown" , NO },
{ kVK_ANSI_A , @"A" , @"A" , YES },
{ kVK_ANSI_S , @"S" , @"S" , YES },
:


最後の is_hotkeyがそれ。


それ以外のルールはロジックを組み込む。

TextView.m
-(void)keyDown:(NSEvent *)theEvent
:
if (!(modifier & (cmdKey | optionKey | controlKey))) {
if (key == kVK_Escape) {
[self redraw];
[self endEdit];
}
// abort
return;
}

if (![KeyCharMapper isHotKeyForKeyCode:key]) {
// abort
return;
}
:

2009年6月25日木曜日

ホットキー変更対応(14) - ダブルクリックで設定モードへ

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

キー入力の基本ができたので、次は実際の設定インターフェイスを作り込んでいく。

動作は次のようにする。
(1) NSTextViewへホットキー初期設定を表示する。
  この時点ではキー入力を受け付けない。

(2) NSTextViewをダブルクリックすると
  キー入力を受け付けるようになる。
  この時、表示されている文字を変更可能であることが
  わかるように選択状態にする。

(3) キーを入力するとその値を表示し、
  再びキーを受け付けないようになる。


キーを受け付けるモードを管理する為にメンバ変数を追加する。
TextView.h

@class HotkeyFormatter;
@interface TextView : NSTextView {
IBOutlet HotkeyFormatter* _hotkey_formatter;
NSNumber* _hotkey;

BOOL _is_editing; // キー受付モード
}



キー受付開始と終了時の処理を受け持つメソッドをそれぞれ追加する。
TextView.m
- (void)startEdit
{
[[self window] makeFirstResponder:self];
[self setSelectable:YES];
_is_editing = YES;
NSRange range = NSMakeRange(0, [[self string] length]);
[self setSelectedRange:range];
}


- (void)endEdit
{
[self setSelectable:NO];
_is_editing = NO;
}


startEdit では TextViewをファーストレスポンダにしてキー入力を受け取れるようにする。また現在の設定値(文字列)を選択状態にする。

endEdit では入力モードをオフにする。


ダブルクリックされたら startEdit を呼び出す。
- (void)mouseDown:(id)theEvent
{
if ([theEvent clickCount] >= 2) {
[self startEdit];
}
}


後は前回のコードとほぼ同じ。
入力モードがオフの場合はキー入力を受け付けない処理だけ書いておく。
- (void)keyDown:(NSEvent *)theEvent
{
NSLog(@"%@", theEvent);

if (!_is_editing) {
return;
}
:


この辺りの動作は以前ファイル名の変更の時にも書いた。詳細は下記を参照のこと。
ファイル名変更(その3)テキストボックス CommentsAdd Star



さて動かしてみよう。

まず実行直後は初期設定が表示される。


ダブルクリックすると入力モードがオンになる。


そこでキーを入力すると、入力キーが表示され、入力モードがオフとなる。



サンプル:HotKeyUI-3.zip

- - - -
だんだん形になってきた。

2009年6月24日水曜日

ホットキー変更対応(13) - キー入力を表示

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

さて下準備ができたのでいよいよ入力キーを表示させてみる。

まず今までの検証コードを少し削除して必要なものだけ残す。インターフェイスは NSTextViewだけ残す。


コードはサンプルを参照のこと。

サンプル:HotkeyUI-2.zip

基本的には今まで紹介した内容を組み合わせただけで特別なことはやっていない。



さて実行してみよう。

まずは Commandキーを押しながら D (※深い意味は無い)


出た。

次は修飾キー全部押し。Control + Option(Alt) + Command + Shift + 'H'


おお出た。ちょっとうれしい。

特別なキーもこの通り。

Deleteキー


矢印


Returnキー



なお '@'(すなわち Shift + '2')は次のように表示される。


表示文字ではなくキーを判定するのでこれで良い(と思う)。

2009年6月23日火曜日

ホットキー変更対応(12) - InterfaceBuilderでNSTextViewを配置する

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

InterfaceBuilderを使い NSTextViewを配置してみる。パレットに NSTextViewは無いのだが Custom View を使えばできることがわかった。

まず Interface Builder で Custom View を配置する。


このクラスに前回用意した TextView を指定する。


実行してみよう。


出た。
こうするとわざわざプログラムコードで生成や面倒な位置決めを行う必要がなくなる。この方法は NSViewのサブクラスなら何でも使えそうだ。