ローカライズの情報を集めていたらこんなツールを見つけた。
iLingual
http://homepage.mac.com/nsekine/SYW/SYWSoft/softOSX.html#iLingual
PISCES での紹介記事
http://pisces-319.seesaa.net/article/71705307.html
面白い。フリーウェアのローカライズは無料ということなので試してみよう。
2009年3月31日火曜日
ローカライズツール iLingual
2009年3月30日月曜日
β版リファクタリング - 座標変換メソッドを切り出す(2)
Windowキャプチャの動作がおかしい。調べると全ウィンドウの範囲を計算するメソッドが原因だった。
+ (NSRect)unionNSRectWithWindowList:(NSArray*)list
{
NSRect all_rect = NSZeroRect;
NSRect rect;
for(Window* window in list) {
rect = [window rect];
if (NSEqualRects(all_rect, NSZeroRect)) {
all_rect = rect;
} else {
all_rect = NSUnionRect(all_rect, rect);
}
}
all_rect.origin = [CoordinateConverter convertFromLocalToCGWindowPoint:all_rect.origin];
return all_rect;
}
前回 Windowクラスの座標系はすべてローカル座標系に変えてしまった。一方、このメソッドの結果は CGWindow系で使われている。
調査したところ、このメソッドは CGWindowでしか利用していなかったので今回は安易だがメソッド内で変換してしまう。
all_rect.origin = [CoordinateConverter
convertFromLocalToCGWindowPoint:all_rect.origin]; // 追加
return all_rect;
これでOK。
- - - -
マルチスクリーンに関連するバグフィックスはこれで(多分)全部片づいた。最初から異なる座標系を意識していればこんなに苦労しなかった。次?は是非最初から座標系の変換を実装に盛り込みたい。
2009年3月29日日曜日
β版リファクタリング - 座標変換メソッドを切り出す
座標変換のメソッドは各所で使う事になるので専用のクラスを用意してここにまとめることにする。
@interface CoordinateConverter : NSObject {
}
+ (NSPoint)convertFromLocalToCGWindowPoint:(NSPoint)from_p;
+ (NSPoint)convertFromCGWindowPointToLocal:(NSPoint)from_p;
@endとりあえずインスタンスは必要なさそうなのでクラスメソッドとして定義した。
内容は以前定義したものと同じ。
これで座標変換メソッドが利用しやすくなった。これを Windowモデルで使い、内部管理の座標系を CGWindowからローカル座標系へ切り替える。
Windowモデル(クラス)は CGWindow系関数で取得したウィンドウの情報を保持するクラス。定義はこんな感じ。
@interface Window : NSObject {
int _order;
CGWindowID _window_id;
int _owner_pid;
NSString* _window_name;
NSString* _owner_name;
int _layer;
NSRect _rect;
NSImage* _image;
}このイニシャライザ #initWithWindowDictionaryRef: の中で _rectへ格納する時に座標変換を行っておく。
- (id)initWithWindowDictionaryRef:(CFDictionaryRef)window
{
self = [super init];
if (self) {
CGRect cgrect;
CFNumberGetValue(CFDictionaryGetValue(window, kCGWindowNumber),
kCGWindowIDCFNumberType, &_window_id);
CFNumberGetValue(CFDictionaryGetValue(window, kCGWindowOwnerPID),
kCFNumberIntType, &_owner_pid);
CFNumberGetValue(CFDictionaryGetValue(window, kCGWindowLayer),
kCFNumberIntType, &_layer);
CGRectMakeWithDictionaryRepresentation(CFDictionaryGetValue(window, kCGWindowBounds), &cgrect);
_window_name = (NSString*)CFDictionaryGetValue(window, kCGWindowName);
_owner_name = (NSString*)CFDictionaryGetValue(window, kCGWindowOwnerName);
[_window_name retain];
[_owner_name retain];
_rect = NSRectFromCGRect(cgrect);
_rect.origin = [CoordinateConverter convertFromCGWindowPointToLocal:_rect.origin];
}
return self;
}
動作確認は明日マルチスクリーン環境で。
2009年3月28日土曜日
2009年3月27日金曜日
2009年3月26日木曜日
ローカライズ(その9)
今回マージ作業をやってみて、実際の運用では気を付ける点がいくつかあることがわかった。
(1) 前回ローカライズした時点のNibファイルをすべて取っておく
ibtool によるマージは旧バージョンと新バージョンの Nibの差分情報を使う。この為、前回ローカライズを行った時全言語の Nibファイルを取っておく必要がある。これを忘れるとマージができない。
(2) 英語Nibファイルに追加された文字列を用意する
英語Nibファイルに変更を入れた後、翻訳用に文字列(例:MainMenu.strings)を生成する。この中で追加があったものだけを抽出してから翻訳を行う。前回の検証ではこの差分抽出は手作業で行った。
ibtool のマージ(--localize-incrementalオプション)のルールは ADCのドキュメントに書かれている。これに目を通しておくと引数で渡すファイルの役割がよく理解できる。
Interface Builder User Guide > Localization
Performing Incremental Localization Updates
ルールは次の通り(英文内のFrenchは「日本語」と置きえた)。
1. 新英語NIBファイルをコピーして新日本語NIBファイルとする。
2. 新英語NIBファイルと、旧英語NIBファイルを比較する。
a) 新旧変更が無いものについては、旧日本語の文字列を、新NIBファイルへ適用する。
b) もし新英語NIBファイルに存在して、旧英語NIBファイルに存在しない場合は、何もしない。
(つまり、1.でコピーした時の英語の文字列が新日本語NIBに残る)
c) もし旧英語NIBファイルに存在して、新英語NIBファイルに存在しない場合は、何もしない。
(項目が削除された場合に起こる。この項目は新NIBには含まれないので問題ない)
3. 日本語文字列(*.strings)を新日本語NIBファイルへ適用する。
(ここで 2-b) の新規文字列を置換する)
図で書くとこんな感じ。





多少は手間がかかるが(特に追加文字列の抽出)、今後の運用・保守のメドが立ったのでこの方法を使っていくことにしよう。
- - - -
引越やら何やらで作業進まず。
3月中リリースはちょっと無理かもしれない。
(プログラムはほぼ出来ているのだが翻訳やホームページ作成が間に合わない。。)
2009年3月25日水曜日
ローカライズ(その8)
ibtool を使ってマージを試してみる。
※以下、拡張子が xib だが Nibファイルと統一して呼ぶ。またターミナルの操作はすべてXcodeプロジェクトのフォルダ内で行った。
1. まず修正を入れる前に最後のバージョンをコピーしておく。ここでは old フォルダへ English.lproj と Japanese.lproj をコピーする。
$ mkdir old
$ cp -pr English.lproj old
$ cp -pr Japanese.lproj old
$ ls old
English.lproj MainMenu.strings MainMenu.xib
$
2. コピーしたら Nibファイルに修正を加えてみる。ここでは Interface Builder で英語の Nibファイルにラベル "Hello !" を追加する。

3. 英語の Nibファイルから文字列を抽出する。
ibtool --generate-stringsfile Japanese.lproj/MainMenu.strings \
English.lproj/MainMenu.xib
ラベル "Hello !" が最後に追加されている。文字列を再生成したので前回翻訳した "Japan"は英語に戻っている。

4. このファイルを編集して "Hello !"の行だけ残すようにする。

5. 準備ができたのでマージしてみよう。ADCページの例を参考にして用意した下記のコマンドを実行する。
ibtool --previous-file old/English.lproj/MainMenu.xib \
--incremental-file old/Japanese.lproj/MainMenu.xib \
--strings-file Japanese.lproj/MainMenu.strings \
--write Japanese.lproj/MainMenu.xib \
--localize-incremental \
English.lproj/MainMenu.xib
6. 日本語の Nibファイルを Interface Builder で開いてみる。

おお、"Hello" が "こんにちは"に置き換わっている。ただラベルサイズが不足しているので途中で切れている。
7. 手動でサイズを拡張してやる。

8. 最後にXcodeでビルドして実行してみる。

出た。
2009年3月24日火曜日
ローカライズ(その7)
Interface Builder User Guide: Localization - Localizing Your Nib File’s Content にPerforming Incremental Localization UpdatesというローカライズされたNibのアップデート方法についての説明がある。
これによれば差分をマージしてNibをアップデートできるとのこと。
ibtool に --localize-incremental オプションを指定する。
- - - -
今日も時間切れ。明日は実際に試したい。
2009年3月23日月曜日
ローカライズ(その6)
HK さんよりInterfaceBuilderを使ったローカライズ方法について情報をもらったのでこれを試してみる(情報ありがとうございました)。
Interface Builder User Guide: Localization
検証用に簡単なプロジェクトを作成して試してみる。
まずは先日のおさらいから。
単純なウィンドウを一つ用意して、この英単語を日本語にローカライズする。
(1) Nib(xib)ファイルからローカライズ用文字列ファイルを生成する。
ibtool --generate-stringsfile English.lproj/MainMenu.strings \
English.lproj/MainMenu.xib
(2) English.lproj/MainMenu.strings をプロジェクトへ追加する
(3) ローカリゼーション(Japanese)を追加する
(4) Japanese.lproj/MainMenu.strings を修正(翻訳)する
(5) Nib(xib)ファイルを書き出す
ibtool --strings-file Japanese.lproj/MainMenu.strings \
--write Japanese.lproj/MainMenu.xib \
English.lproj/MainMenu.xib
(6) 日本語のNib(xib)ファイルをプロジェクトへ追加する
ここまでの結果プロジェクトの中身はこうなる。

(7) ビルド&実行。日本語が出た。

- - - -
今日は時間切れ。
明日は変更を入れてマージをやってみる。
2009年3月22日日曜日
β版バグ修正 - マルチスクリーン(ウィンドウ-2)
ウィンドウキャプチャ時のボタンの位置がおかしい。
これも座標系の不一致の問題。ボタン位置を指定している箇所に前回用意した #convertFromCGWindowPointToLocal: をかます。
- (void)adjustButtonBar
{
if ([_selected_window_list count]) {
Window* wn = [_selected_window_list objectAtIndex:0];
NSRect rect = [wn rect];
rect.origin = [self convertFromCGWindowPointToLocal:rect.origin];
[_button_bar setButtonBarWithFrame:rect];
[_button_bar2 setButtonBarWithFrame:rect];
[_button_bar startFlasher];
}
}
その他にもウィンドウキャプチャ時にウィンドウを選択しようとしてクリックしても反応が無い。これもマウスクリック時の座標系(ローカル座標系)と内部管理しているウィンドウの座標系(CGWindow座標系)の違いによる。前回作った #convertFromCGWindowPointToLocal: をヒット判定処理に加える。
// (1) search a window on mouse down (already selected)
for (Window* swn in _selected_window_list) {
rect = [swn rect];
rect.origin = [self convertFromCGWindowPointToLocal:rect.origin];
if (NSPointInRect(cp, rect)) {
hit_flag = YES;
selected_window = swn;
break;
}
}
これで直った。

- - - -
内部管理しているウィンドウの座標を使うたびにいちいち変換するのではなく、管理上の座標系自体を CGWindowをやめて ローカル座標系に直した方が良さそうだ(続く)。
2009年3月21日土曜日
β版バグ修正 - マルチスクリーン(ウィンドウ)
スクリーン配置によってウィンドウキャプチャも表示が変になっているのに気が付いた。
例えば上にサブ画面、下にメイン画面があった場合。
ウィンドウキャプチャを起動すると下の画面のウィンドウが選択状態になるのだが、上のサブ画面に選択ウィンドウの画像が表示されてしまう。
これは以前調査した通り座標系の問題。
β版バグ修正 - マルチスクリーン(その2)
http://xcatsan.blogspot.com/2009/03/blog-post_06.html
内部的にウィンドウを管理している座標系が CGWindowなのに対して、ウィンドウを描画する座標系がカスタムビューのローカル座標であるのが原因。CGWindow系からローカル座標系へ変換してやればいいだろう。
以前、ローカル座標系からCGWindow系へ変換するメソッド #convertFromLocalToCGWindowPoint: を用意した。ここでは下記の数式を使っていた。
・スクリーン座標系上での全画面範囲(左下)-(右上):(sx1, sy1)-(sx2, xy2)
・メイン画面のサイズ(幅・高さ):(sw, sh)
・ローカル座標系上でキャプチャした範囲選択の開始点(左上):(lx1, ly1)
・CGWindow関数へ渡す開始点(左上):(cx1,cy1)
cx1 = lx1 + sx1
cy1 = ly1 - (sy2 - sh)
この最後数式を入れ替えれば逆変換になる。
lx1 = cx1 - sx1
ly1 = cy1 + (sy2 - sh)
この変換を行うメソッド #convertFromCGWindowPointToLocal: を用意し、これを使うように修正してみた。
- (NSPoint)convertFromCGWindowPointToLocal:(NSPoint)from_p
{
CGFloat cx1, cy1, lx1, ly1, sx1, sy2;
NSRect frame = [[Screen defaultScreen] frame];
NSRect m_frame = [[NSScreen mainScreen] frame];
CGFloat sh = m_frame.size.height;
cx1 = from_p.x;
cy1 = from_p.y;
sx1 = frame.origin.x;
sy2 = frame.origin.y + frame.size.height;
lx1 = cx1 - sx1;
ly1 = cy1 + (sy2 - sh);
return NSMakePoint(lx1, ly1);
}
実行してみる。

今度は大丈夫だ。
ただボタンの位置がおかしい??
2009年3月20日金曜日
β版バグ修正 - マルチスクリーン(マウスカーソル)
2009年3月19日木曜日
ローカライズ(その5)
試しに nib に新しくメニューアイテムを追加してみる。
ibtool で English.proj/MainMenu.nib.strings を生成する。
すると新しい ObjectIDが発行され、最後に項目が追加されていた。
これを手作業で Japanese.proj/MainMenu.nib.strings へマージ&翻訳する作業が必要ということか。
逆に項目を削除したら Japanese.proj/MainMenu.nib.strings を削除しないといけない。
Nibファイルを修正する時は常にこの辺りを意識しないと駄目みたいだ。
- - -
ObjectIDとかのメタ情報を使えばマージを楽にするツールが作れそうだ。
と、考えるのは私1人じゃないはずなので、既にツールがありそうだが。
2009年3月18日水曜日
ローカライズ(その4)
ふと思ったのだが nibを変更したり項目を追加した場合の他言語のメンテナンスはどうやるんだろうか?
昨日の方法のおさらいをしてみる。
(1) 最初に ibtoolを使い MainMenu.nib から文字列だけを抜き出したファイル English.proj/MainMenu.nib.strings を生成した。
(2) 次にローカリゼーションを追加して Japanese.proj/MainMenu.nib.strings を作成し、これを日本語に書き換えた。
(3) 最後に Japanse.proj/MainMenu.nib.strings から Japanese.lproj/MainMenu.nib を作成した。
この状態で InterfaceBuidlerを使い English.proj/MainMenu.nibを変更したとする。
その場合 (1)までは良いが、その後はどうやって変更分を日本語へ反映させるのか?
もしかして手作業でマージのような処理が必要とか?
むむ?
2009年3月17日火曜日
ローカライズ(その3)
katokichi さんより nib(xib) についてローカライズの情報をもらった。
2008/11/6 iPhone SDK勉強会 まとめ
http://iphone-dev.jp/modules/pico/index.php?content_id=9
UIをローカライズする手順
http://d.hatena.ne.jp/uasi/20081115/1226676887
なるほど ibtool というツールを使うと文言の翻訳だけで対応できるらしい。
他にもいくつか解説ページがあった。
ibtoolによるnibファイルのローカライズ
http://www.fraction.jp/log/archives/2008/01/23/localize_nib_file_with_ibtool
試してみよう。
(1) ターミナルを開き、Xcodeのプロジェクトディレクトリへ移動する。
(2) ibtool を実行する。
ibtool --generate-stringsfile English.lproj/MainMenu.nib.strings English.lproj/MainMenu.nib

すると English.lproj フォルダ配下に MainMenu.nib.strings が生成される。

(3) Xcodeプロジェクトへ追加する。
コンテキストメニューで追加するか、ドラッグ&ドロップで生成されたファイル MainMenu.nib.string をプロジェクトへ追加する。

この時、エンコーディングを Unicode(UTF-16)にしておく。

そうでないとこんな感じで化け化けになる。

(4) このファイルにさらにローカライズを追加する。追加するのは "Japanese"。
"MainMenu.nib.string"の上でコンテキストメニューを表示し「情報を見る」でダイアログを開く。ここで画面下の「ローカリゼーションの追加」ボタンを押す。


ファイルの中身はこんな感じ。

(5) 少し書き換えてみる。

(6) nibを生成する前に MainMenu.nib にローカリゼーション"Japanese"を追加しておく。

(7) さて書き換えたファイルを元に nibファイルを生成する。
ibtool --write Japanese.lproj/MainMenu.nib -d Japanese.lproj/MainMenu.nib.strings English.lproj/MainMenu.nib
ターミナルで実行。

Japanese.lproj フォルダを見ると MainMenu.nibが生成されている。

(8) ビルドして実行する。
さてどうだろう。

おお出た。
- - - -
この辺りの一連の作業はビルドに組み込んで自動化できるらしい。後で試してみよう。
それにしても量が多い。。
2009年3月16日月曜日
ローカライズ(その2)
2009年3月15日日曜日
ローカライズ(その1)
2009年3月14日土曜日
範囲選択の範囲を微調整
前から気になっていたのだが範囲選択で1ドット単位の細かい範囲指定をやろうとすると少しくせがあって使いにくかった。
例えば次の1ドットの黒枠の画像を、黒枠を含めてピッタリとキャプチャしたいとする。
SimpleCapの範囲選択を起動しラバーバンドの線を黒枠上にぴったり重ねてキャプチャする。
すると左と下の黒枠はキャプチャに含まれるのだが、右と上の黒枠は含まれない。
右と上も含めるにはラバーバンドを右上に1ドットづつ大きめに指定する必要があった。
これでは感覚的ではないのでキャプチャ対象をラーババンドの線上を含む範囲とするように微調整した。
描画する時に右と上の線が1ドット程度内側に表示すれば良いので、描画枠の位置と範囲を調整してみた。
NSRect d_rect = _rect;
d_rect.origin.y += 0.1;
d_rect.size.height -= 0.2;
d_rect.origin.x += 0.1;
d_rect.size.width -= 0.2;
[path appendBezierPathWithRect:d_rect];
さて試してみる。黒枠上にラバーバンドを重ねてパチリ。

うまくいった。
2009年3月13日金曜日
Screen 改良
Screenクラスを改良する。具体的にはシングルトンパターンでインスタンスを作り、それを使い回すようにした。
全画面の範囲は今まではクラスメソッドで取り出していた。
+ (NSRect)frame;
呼出し毎に範囲を計算していた。
これをやめてインスタンス化し、1度計算した値を使い回すようにした。
@interface Screen : NSObject {
NSRect _frame;
}
+ (Screen*)defaultScreen;
- (NSRect)frame;
実装はこんな感じ。
static Screen* _screen = nil;
+ (Screen*)defaultScreen
{
if (!_screen) {
_screen = [[Screen alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:_screen
selector:@selector(screenChanged:)
name:NSApplicationDidChangeScreenParametersNotification
object:nil];
[_screen update];
}
return _screen;
}
スクリーンの構成が変わった時を考慮して NSApplicationDidChangeScreenParametersNotification を受け取るようにしている。この通知を受け取ったら範囲を更新する。
- (void)screenChanged:(NSNotification *)notification
{
[self update];
}
2009年3月12日木曜日
β版バグ修正 - マルチスクリーン(ボタン位置)
今日は範囲選択のボタン位置の修正。
これまたマルチスクリーンの場合、画面の位置関係によってはボタンの位置が正しく表示されない。
原因は単純でボタンの位置決めでマルチスクリーンを考慮していないこと。
現在はメイン画面のサイズのみを見ている。
NSSize view_size = [[NSScreen mainScreen] frame].size;
これを変更して、全画面を包含する画面サイズとする。
NSSize view_size = [Screen frame].size;
これで直った。
- - - -
Screen#frame はクラスメソッドで毎回全画面範囲を計算している。利用頻度が高くなってきたのでそろそろインスタンス化した方がよさそうだ。あらかじめ計算しておけばオーバーヘッドを少しでも減らせる。スクリーンの数や配置が変更になった場合は Notificationを受けて再計算すれば良い。
2009年3月11日水曜日
新しい本
今日本屋へ寄ったら新しいCocoa本が出ていた。
初心者向けの内容でiPhoneにも少し触れていた。それほど厚みは無い。
少しずつだけど日本語のCocoa関係本が増えてきたのはいいことだ。昔は Ojective-Cをやろうなんて人は少数だったと思うのだけど iPhone効果かな。
- - - -
今日は進展なし。徐々に本業とプライベート両方が忙しくなってきた。
2009年3月10日火曜日
β版バグ修正 - マルチスクリーン(全画面)
マルチスクリーンのバグ取り、次は全画面。
全画面キャプチャの時に画面の配置状況によってはカウントダウン時の点線の位置がおかしいとの指摘を受けた。キャプチャ自体は問題なく行えている。
点線がおかしい例
点線が上の方に固まって表示されている。
描画コードはこう。
- (void)drawRect:(NSRect)rect
{
[[NSColor clearColor] set];
NSRectFill(rect);
for (NSScreen* screen in [NSScreen screens]) {
NSRect frame = [screen frame];
frame.origin.y = 0.1;
frame.size.width -= 0.1;
frame.size.height -= 0.1;
[self drawSelectedBoxRect:frame Counter:_animation_counter];
}
}
このコードでは y値を 0に固定し、さらに座標系の違いをまったく考えていない。NSScreen#screens で取得したスクリーン座標系の値を、そのままカスタムビューのローカル座標系で使っている。これではマルチスクリーンで変な描画になるわけだ。単一スクリーンの場合は y値を0(コードでは0.1)にすることで無理矢理?ローカル座標系に合わせているため問題がでなかった。なお 0.1 で調整しているのは、ぴったりの数値にすると線が画面の外にはみ出してしまう為、少しだけ小さくなる様に設定している。
このコードを、スクリーン座標系からローカル座標系に変換するように書き直す。こんな感じ。
- (void)drawRect:(NSRect)rect
{
[[NSColor clearColor] set];
NSRectFill(rect);
for (NSScreen* screen in [NSScreen screens]) {
NSRect frame = [screen frame];
frame.origin = [[_capture_controller view] convertPoint:[[_capture_controller window] convertScreenToBase:frame.origin] fromView:nil];
frame.origin.y -= frame.size.height;
frame.size.width -= 0.1;
frame.origin.y += 0.1;
frame.size.height -= 0.1;
[self drawSelectedBoxRect:frame Counter:_animation_counter];
}
}
基本的には NSWindow#convertScreenToBase: などを使い座標系変換すれば良い。ただし以前書いたように原点の取り方が異なることからその調整が必要になる。コード中、frame.origin.y から frame.size.heightを差し引いているのがその調整。0.1調整は実際の描画を見ながら適当に調整した。
実際にマルチスクリーンで試したところ問題なく点線が描画できた。
- - - -
マルチスクリーンはまだ続く。やれやれ。
2009年3月9日月曜日
β版バグ修正 - マルチスクリーン(検証)
昨日までの修正をマルチスクリーン環境で試してみる。
(1) 範囲選択の初期位置がおかしい件
これは問題なし。スクリーン座標系の情報を変換した結果を使い、意図通りの位置(メイン画面の中心)に表示できている。
(2) 範囲選択がうまく働かない件
これは問題あり。Y軸方向は問題無いがX軸方向で挙動がおかしい。そこで先日の数式を見直してみとX値を求める箇所で符号が逆になっているのに気が付いた。
(誤)cx1 = lx1 - sx1
(正)cx1 = lx1 + sx1
これではキャプチャしても全然変な場所を切り抜いているわけだ。符号を直してやりなおしたところ問題なくキャプチャできた。
メインとサブ画面について一通りの位置関係で範囲選択を試したところ問題無いことが確認できた。
数式の最終版は次のようになる。
・スクリーン座標系上での全画面範囲(左下)-(右上):(sx1, sy1)-(sx2, xy2)
・メイン画面のサイズ(幅・高さ):(sw, sh)
・ローカル座標系上でキャプチャした範囲選択の開始点(左上):(lx1, ly1)
・CGWindow関数へ渡す開始点(左上):(cx1,cy1)
cx1 = lx1 + sx1
cy1 = ly1 - (sy2 - sh)
- - - -
マルチスクリーンがらみの問題はまだある。なのでバグ修正はもう少し続く。
2009年3月8日日曜日
範囲選択の初期位置(マルチスクリーン対応)
引き続きマルチスクリーンの話題。
マルチスクリーンで最初に範囲選択した時にメイン画面ではなくサブ画面の中央に範囲選択枠(ラバーバンド)が表示される。これは現在の実装が単純にマルチスクリーンを考慮していないから。
初期位置を決める現在のコードはこんな感じ。
- (void)resetRect
{
NSScreen* screen = [NSScreen mainScreen]; // multi screen ok
NSSize screen_size = [screen frame].size;
_rect = NSMakeRect((screen_size.width - START_WIDTH)/2,
(screen_size.height - START_HEIGHT)/2,
START_WIDTH, START_HEIGHT);
}
単純にローカル座標系の(0,0)を基点としてメイン画面の中央に配置するようにしている。これでは画面の配置状況によっては意図した(メイン画面中央)位置に範囲選択枠がこない。これを直すにはスクリーン座標系上でメイン画面の左上の座標を求め、これをローカル座標系へ変換した上で描画位置の基点とすれば良いと思われる。スクリーン座標系からカスタムViewのローカル座標系への変換には NSWindow#convertScreenToBase: と NSView#convertPoint:fromView:が使える。
変換コードを追加するとこんな感じ。
- (void)resetRect
{
NSScreen* screen = [NSScreen mainScreen]; // multi screen ok
NSSize screen_size = [screen frame].size;
_rect = NSMakeRect((screen_size.width - START_WIDTH)/2,
(screen_size.height - START_HEIGHT)/2,
START_WIDTH, START_HEIGHT);
NSPoint bp = NSZeroPoint;
bp = [[_capture_controller view] convertPoint:[[_capture_controller window] convertScreenToBase:NSMakePoint(0, screen_size.height)] fromView:nil];
_rect.origin.x += bp.x;
_rect.origin.y += bp.y;
}
後半 #convertPoint:fromView: と #convertScreenToBase: を使って基点を生成している。
実行してみたところ単一スクリーンでは問題なさそうだ。
ただマルチスクリーンは今は試せる環境が無い。明日以降試してみよう。
2009年3月7日土曜日
β版バグ修正 - マルチスクリーン(その3)
昨日考えたロジックの実装に入る。
まず範囲選択キャプチャを行うメソッドに手を入れる。
NSRect s_rect = _rect;
s_rect.origin = [self convertFromLocalToCGWindowPoint:_rect.origin];
CGImageRef cgimage = CGWindowListCreateImage(NSRectToCGRect(s_rect), option,
[_capture_controller windowID],
kCGWindowImageDefault);
以前はローカル座標系の範囲 _rect を直接 CGWindowListCreateImage へ渡していたが、#convertFromLocalToCGWindowPoint: を使い CGWindow関数の座標系へ変換をかける。#convertFromLocalToCGWindowPoint のコードは次の通り。
- (NSPoint)convertFromLocalToCGWindowPoint:(NSPoint)from_p
{
CGFloat cx1, cy1, lx1, ly1, sx1, sy2;
NSRect frame = [Screen frame];
NSRect m_frame = [[NSScreen mainScreen] frame];
CGFloat sh = m_frame.size.height;
lx1 = from_p.x;
ly1 = from_p.y;
sx1 = frame.origin.x;
sy2 = frame.origin.y + frame.size.height;
cx1 = lx1 - sx1;
cy1 = ly1 - (sy2 - sh);
return NSMakePoint(cx1, cy1);
}
昨日の数式をそのまま実装しただけ。Screen#frame は全画面の範囲を返す。
さて実行してみよう、といきたいところだが、あいにく今はマルチスクリーンを試せる環境が無い。とりあえずシングルスクリーンで動作確認を取っておく。

大丈夫そうだ。
- - - -
マルチスクリーン環境は職場でないと試せないので月曜日に出社した時に試してみる。
(6/11追記)数式に一部誤りあり。下記を参照のこと。
β版バグ修正 - マルチスクリーン(検証) CommentsAdd Star
2009年3月6日金曜日
β版バグ修正 - マルチスクリーン(その2)
(前日からの続き)
さて、マルチスクリーンにまつわるSimpleCapの問題はいくつかあるのだが、まずは範囲選択時の問題から取りかかろう。
範囲選択キャプチャではカスタムビューの点情報を CGWindow関数へ渡す。
キャプチャ範囲の開始点、大きさ
↓
CGWindow関数
単一スクリーンだけの利用の場合は前回掲載したように2つの座標系はまったく同じになるので問題は生じない。一方マルチスクリーンになると画面の配置によって原点の取り方が異る為に意図したキャプチャ結果が得られないケースが出てくる。そこで座標系間で変換が必要になってくる。具体的なケースを元に変換ロジックを考えてみよう。
(X-1)サブ画面がメイン画面の左にあるケース
カスタムビューの原点は全画面を包含する矩形の左端となる。一方、CGWindow関数はメイン画面の左端となる。キャプチャする場合にこの時原点の位置の違いを反映させる必要がある。例のケースではX軸方向に -1280 だけ移動させる必要がある。

この -1280 という数値はどこから取れば良いのだろうか?
これは CGWindowの原点であるメイン画面の左端から、全画面の左端までの距離と一致する。そして、この数値は NSScreen から得られるスクリーン座標系の情報から求められる。

NSScreenから全画面を包含する矩形が得られて、左下(-1280,0)-右上(1600,1024)となる。この左にはみ出た分 1280 がメイン画面の左端から、全画面の左端までの距離となる。これは画面がたくさんあっても変わらない。
数式化してみると次のようになる。
・スクリーン座標系上での全画面範囲(左下)-(右上):(sx1, sy1)-(sx2, xy2)
・ローカル座標系上でキャプチャした範囲選択の開始点(左上):(lx1, ly1)
・CGWindow関数へ渡す開始点(左上):(cx1,cy1)
cx1 = lx1 -sx1
(X-2)サブ画面がメイン画面の右にあるケース

このケースは原点移動が発生しないので補正の必要はない。ただ実装上は先ほどの数式に当てはめられると場合分けが不要で扱いやすい。スクリーン座標系の方を見てみよう。

先ほどの sx1 は 0となる。
cx1 = lx1 -sx1 = lx1 - 0 = lx1
つまり原点移動は起こらず先ほどの数式が使える。
X軸は片づいた。次はY軸を考えよう。
(Y-1)サブ画面がメイン画面の上にあるケース。

X-1のケースと同様、原点移動が必要。こちらも NSScreenの情報が使える。

ただしY軸の場合、ローカル座標系およびCGWindow座標系は FlipしているのでX軸のように単純に sy1 が使えない(sy1 = 0)。Y軸の場合はメイン画面の高さを考慮する必要がある。原点移動に必要な高さは、画面全体の左上からメイン画面の左上までの高さなので、これを NSScreen座標系で考えると次の数式となる。
・スクリーン座標系上での全画面範囲(左下)-(右上):(sx1, sy1)-(sx2, xy2)
・メイン画面のサイズ(幅・高さ):(sw, sh)
・ローカル座標系上でキャプチャした範囲選択の開始点(左上):(lx1, ly1)
・CGWindow関数へ渡す開始点(左上):(cx1,cy1)
cy1 = ly1 - (sy2 - sh)
例の場合、sy2=1878, sh=1024 だから sy2 - sh = 864 となり、原点移動に必要な高さが得られる。
(Y-2)サブ画面がメイン画面の下にあるケース。

こちらのケースは原点移動が必要ない。
NSScreen情報はこんな感じ。

さて先ほどの数式が使えるかどうか。
sy2 = 1024
sh = 1024
sy2 - sh = 1024 - 1024 = 0
0となった。つまり原点移動は不要。
これまでの考察をまとめてみると次のようになる。
・スクリーン座標系上での全画面範囲(左下)-(右上):(sx1, sy1)-(sx2, xy2)
・メイン画面のサイズ(幅・高さ):(sw, sh)
・ローカル座標系上でキャプチャした範囲選択の開始点(左上):(lx1, ly1)
・CGWindow関数へ渡す開始点(左上):(cx1,cy1)
cx1 = lx1 -sx1
cy1 = ly1 - (sy2 - sh)
なお画面が3つ4つそれ以上存在し、例えばメイン画面の上下にサブ画面が存在する場合でも(恐らく)数式は正しく働くはず。これはスクリーン座標系とローカル座標系間でメイン画面の(左上もしくは左下の)位置関係は画面の数によらず取得できるはずだから。
さて数式は出た。後は実装するのみ。
結果はいかに(続く。。)
2009年3月5日木曜日
β版バグ修正 - マルチスクリーン
マルチスクリーン利用時にモニタの配置状況によっては範囲選択がうまく働かないとの報告をもらった。
例えば、サブ画面(メニューバーの無い方)が左に来ている場合。
サブ画面が右側にある場合は問題ない。同様にサブ画面が上にあるケースも問題が出る。
これに関連してスクリーン全体のキャプチャ時の点滅する点線の描画もおかしいようだ(キャプチャ自体は問題ない)。
何が起こっているか調査して整理してみよう。
まずは考慮すべき座標系について。SimpleCapでは次の3つを考える必要がある。
a. スクリーン座標系
b. SimpleCapのメインビューのローカル座標系
c. CGWindow系関数(キャプチャ関数)の座標系
それぞれは次のような独自の座標系を持っている。
a. スクリーン座標系
Cocoa(そしてQuartz)標準の座標系。左下が原点(0,0)となる。NSScreen で取得できる各種座標もこの座標系。

b. SimpleCapのメインビューのローカル座標系
filip(Y軸がスクリーン座標系と反転)させた座標系を使っている。左上が原点(0,0)になる。

c. CGWindow系関数(キャプチャ関数)の座標系
これも左上が原点(0,0)となる。

a,b,c共に異なる座標系を構成している。これらはスクリーンが一つの場合は特に支障をきたさないのだが、マルチスクリーンになると違いが出てくる。特に b.とc.は座標系は同じなのだがマルチスクリーンにした時の原点の取り方が異なっている。
これらを少し丁寧に調べてみよう。
検証にあたっては PowerBookG4(1280x854)とシネマディスプレイ(1600x1024)を使う。

スクリーンの位置関係は色々な組み合わせがあるが、整理しやすいようにX軸、Y軸方向で別々に考えてみる。
まずはY軸方向から。メインとサブ画面の上下関係は2種類ある。
(Y-1) サブ画面がメイン画面の上にあるケース

スクリーン座標系は、メイン画面の左下を原点として積み上げているのがわかる。一方、ローカル座標は2つのスクリーンを包含する矩形の左上を原点としている。最後の CGWindow系はメイン画面の左上を原点として、サブ画面は負の領域に配置されている。範囲選択では、ローカル座標をそのまま CGWindow系へ渡してキャプチャしている為、ここで座標系の不一致が起こり問題となっている。
(Y-2) サブ画面がメイン画面の下にあるケース

こちらはローカル座標とCGWindow系の座標系が一致しているのでキャプチャ自体は問題ない。ただしスクリーン系とは違っているので別の部分(ボタンの位置が変)に問題が出ている。
続いてX座標方向。Y軸方向と違ってFlipしていないのでこちらは原点の取り方だけが違う。
(X-1) サブ画面がメイン画面の左にあるケース

これもローカル座標系とCGWindow系で原点の取り方で違いが大きく出ている。
(X-2) サブ画面がメイン画面の右にあるケース

X軸だけ見ればこのケースはすべて同じに扱える。
- - - -
なるほど。マルチスクリーンになったとたん3つともてんでバラバラの座標系になっている。これは正常に動作しないわけだ。
さてどうするか。
2009年3月4日水曜日
2009年3月3日火曜日
マウスカーソルの画像
マウスカーソルの画像をアイコンで使えないか試行錯誤中。
またちょっとしたツールを作った。
実行形態:CursorImage.app.zip
ソース:CursorImage.zip
実行すると次の画面が現れる。上のエリアへ画像をドラッグ&ドロップするとマウスカーソルと合成した画像を作る。
save ボタンを押すとデスクトップに PNG画像を生成する。
2009年3月2日月曜日
β版バグ修正 - ファイル名変更でドットで始まるファイル他
今日もバグ取り。ユーザに指摘されたバグを直す。
ドットで始まるファイルに変更できてしまう件と、名称変更中にファイル切り替えを行うと拡張子が重なってしまう(例:abc.png.png)を直した。
ファイル変更中に、ファイル切り替えや次のキャプチャなどの操作に入った場合は直前のファイル名変更を終わらせてから操作を実行するように修正した。これも初歩的なミスだな。
今日はこれだけ。
- - - -
ファイル名の形式をカスタマイズしたいとの要望を受けた(例:YmdHisなど)。これは当初切り捨てた機能だが今後要望が多ければ付けていこうと思う。
それとftpなどを使った各種Webサイトへのアップロード機能があると便利との意見ももらった。これは同感。
ただ SimpleCap自体に実装することは考えていない。この用途の為に SimpleViewerへアプリ起動機能を持たせてある。何らかのアップローダソフトをここから呼出して連携できれば良いと考えている。ファイル名をコマンドライン引数で渡す仕様になっているので、それを受け取って所定のサイトへアップロードできるソフトがあれば簡単に連携できる。適当なソフトを知っていれば是非情報を教えてください。そのようなソフトを開発してくれる人があれば歓迎します。後日立ち上げる公式サイトでも紹介させてもらいます。














