ページ

2008年5月31日土曜日

RubberBand(その35)ThinButtonの位置を自動調整

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

ThinButtonは場所を取るため、RubberBandの大きさによっては重なって使いづらい場合がある。


RubberBand内にボタンが収まらない場合は別の位置に表示させたい。次のルールで位置を決めることにした。

 (1) RubberBandの 縦幅または横幅にボタンが収まらない場合は、RubberBandの下にボタンを表示する。
 (2) RubberBandの下に表示した場合、画面の外にはみ出してしまう場合は、RubberBandの上に表示する。
 (3) ボタンがRubberBandの下または上に表示されている場合、ボタンの左位置は RubberBandの左位置に合わせる。
 (4) ボタンの右側が画面の外にはみ出してしまう場合はボタンの左位置を調整して、ボタンすべてが表示できるように調整する。


RubberBand.mに ボタンの位置を決めるメソッドが元々用意されているのでこれに手を加える。上記ルールに従って実装すれば良い。実際には見た目を感覚的なものにする為に、マージンやオフセットで微調整してある。

RubberBand.m

#define SBBF_MARGIN 5.0
#define BUTTON_OFFSET 5.0
-(void)setButtonBarWithFrame:(NSRect)frame
{
NSPoint p;
NSSize button_size = [_button_bar bounds].size;
NSSize view_size = [self bounds].size;

// (1) standard position
p.x = frame.origin.x + frame.size.width - button_size.width - BUTTON_OFFSET;
p.y = frame.origin.y + frame.size.height - button_size.height -BUTTON_OFFSET;

// (2) over height/width
if (button_size.height + SBBF_MARGIN*2 > frame.size.height ||
button_size.width + SBBF_MARGIN*2 > frame.size.width) {
p.y = frame.origin.y + frame.size.height + BUTTON_OFFSET;

if (p.y + button_size.height + SBBF_MARGIN > view_size.height) {
p.y = frame.origin.y - button_size.height - BUTTON_OFFSET;
}
}

// (3) adjust origin.x
if (p.x - SBBF_MARGIN < _rect.origin.x) {
p.x = _rect.origin.x - BUTTON_OFFSET;
}
if (p.x + button_size.width + SBBF_MARGIN > view_size.width) {
p.x = frame.origin.x + frame.size.width - button_size.width + BUTTON_OFFSET;
}

[_button_bar setFrameOrigin:p];
}



実装後の動きをみてみよう。

まずは初期状態。ボタンは RubberBand内の右下に表示される。


縦幅を狭めていくとある時点でボタンが下に表示されるようになる。


横幅も同様。


ボタンが下に表示された状態で、RubberBandを画面下へ持っていくと今度はボタンが上に表示される。


ボタンが RubberBandの下に表示された状態で右端へ持っていくと、ボタンの表示位置が左にづれる(右の表示が切れることがない)。



ソース:RubberBand-18.zip

- - - - -
サイズは今のところスペースバーで消せるのでこのままにしておく。

2008年5月30日金曜日

任意の言語でアプリを起動する

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

多言語化したアプリケーションを各言語で確認するには、プリファレンスで言語設定を変えなければならない。面倒だと思っていたところコマンドラインのオプションで切り替えられることが分かった。コマンドラインオプションに「-AppleLanguages "(言語)"」を追加すれば良い。

(例)/Applications/TextEdit.App/Contents/MacOS/TextEdit -AppleLanguages English


前回の RubberBand の ThinButtonの Tooltipもこれで試してみる。英語で立ち上げてみた。
build/Debug/RubberBand.app/Contents/MacOS/RubberBand -AppleLanguages English


すると英語で表示される。


- - - -
(*)この情報は、他のあるホームページから得たものだがずいぶん前の事でソースを失念。

2008年5月29日木曜日

RubberBand(その34)ToolTipをローカライズ

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

ToolTipで表示される文字列をローカライズする。




ローカライズの手順は次の通り。

 (1) 英語のLocalizable.strings を作成
 (2) 日本語のローカリゼーションを追加
 (3) 日本語の Localizable.strings を作成
 (4) ソースコード中の文字列を NSLocalizedString( )に置き換える。

追って説明する。

(1) 英語のLocalizable.strings を作成

新規ファイルを Localizable.strings という名前で作成する。
ここに key = value; 形式で文字列を定義していく。key は後で NSLocalizedString()で使う。valueが実際のメッセージ文字列となる。

Localizable.strings

"CancelCapture" = "Cancel capture";
"TimerSelection" = "Timer selection";
"CopySelection" = "Copy selection";
"CaptureSelection" = "Capture selection";



(2) 日本語のローカリゼーションを追加

英語のメッセージが用意できたら日本語用のファイルを用意する。ファイルの情報を開き、一番下の「ローカリゼーションを追加...」で "Japanese"を選択する。


するとプロジェクト内にローカリゼーション用のフォルダが日本語、英語で作成される。日本語用フォルダには Localizable.strings が作成される。
|--English.lproj
| |--InfoPlist.strings
| |--Localizable.strings
| |--MainMenu.nib
|--Japanese.lproj
| |--Localizable.strings



(3) 日本語の Localizable.strings を作成

初期状態では英語のコピーになっているのでこれを日本語で書き換える。
Japanese.lproj/Localizable.strings
"CancelCapture" = "キャンセル";
"TimerSelection" = "タイマー起動";
"CopySelection" = "コピー";
"CaptureSelection" = "キャプチャ";


ファイルエンコーディングは "Unicode (UTF-16)" にしておく。
(参考)Internationalization Programming Topics / Strings


(4) ソースコード中の文字列を NSLocalizedString( )に置き換える。
こんな感じ。
[_button_bar addButtonWithImageResource:@"icon_cancel"
alterImageResource:@"icon_cancel2"
tag:TAG_CANCEL
tooltip:NSLocalizedString(@"CancelCapture", @"A")];



- - - -
Localizable.strings の最後の ; (セミコロン)を忘れると実行時に下記のエラーが出る。
2008-05-18 17:39:30.398 RubberBand[23503:10b] CFPropertyListCreateFromXMLData(): Old-style plist parser: missing semicolon in dictionary.

セミコロンを付けてもこのエラーは出続ける。その場合はクリーニングを一度かけてから再ビルドすると消える(これでハマってしまった。。)。

2008年5月28日水曜日

RubberBand(その33)ThinButtonにToolTip表示

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

ThinButton に ToolTipを表示させる。ToolTipとはマウスカーソルを置いてしばらくすると表示されるメッセージ。


ずいぶん昔に取り上げことがある。これを参考にして ThinButtonを改良してみる。
NSViewの特定の領域でToopTipを表示する - NSView

まず ThinButtonで ToolTipへ表示する文字列を保持できるよう各イニシャライザに引数を追加する。

ThinButton.h
- (id)initWithImage:(NSImage*)image1 alterImage:(NSImage*)image2 frame:(NSRect)frame tag:(UInt)tag tooltip:(NSString*)tooltip;

ThinButtonBar.h
- (void)addButtonWithImageResource:(NSString*)resource alterImageResource:(NSString*)resource2 tag:(UInt)tag tooltip:(NSString*)tooltip;

最後にそれぞれ tooltipを追加してある。


次に NSViewへTooltipを登録する。これは ThinButtonBar に新しいボタンが追加されるタイミングで行なう。
ThinButtonBar.m
- (void)addButtonWithImageResource:(NSString*)resource alterImageResource:(NSString*)resource2 tag:(UInt)tag tooltip:(NSString*)tooltip
{
:
//
// Add tooltip
//
[self addToolTipRect:frame owner:self userData:nil];

:
}


frame は追加する ThinButton のNSRectを表しており、ボタン毎に登録していく。

最後にボタンを登録するコードへ Tooltip文字列を追加する。ここではじかに英語の文字列を用意した。
RubberBand.m
- (id)initWithFrame:(NSRect)frame {
:
[_button_bar addButtonWithImageResource:@"icon_cancel"
alterImageResource:@"icon_cancel2"
tag:TAG_CANCEL
tooltip:@"Cancel capture"];
:


ここまででも ToolTip が表示されるようになる。


ただし指定した Tooltip文字列が表示されていない。デフォルトの動作では NSView#addToolTipRect:owner:userdata: の ownerの #description メソッドの戻り値が表示に使われる。任意の文字列を出すには NSToolTipOnwerプロトコル(非形式プロトコル)を ownerで実装してやれば良い。今回は ownerが ThinButtonBar なのでここで下記コードを実装した。

ThinButtonBar.m
- (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)userData
{
for(ThinButton* button in _list) {
if (NSPointInRect(point, [button frame])) {
return [button tooltip];
}
}
return @"non";
}

マウスカーソル位置が渡されるのでどのボタンの上かをチェックし、そのToolTip文字列を返す。するとできあがり。


なお ThinButtonBar のframeを ToolTipのエリアとして登録する方法も考えられる。ただし試したところこれはうまくいかなかった。ToolTipが表示された後、マウスを動かして別のボタンの上へ動かしても最初に表示された ToolTipの文字列が表示されたままになった。恐らく領域が一つであるため、切り替わりのイベントが発生しない為だと思われる。

ソース:RubberBand-17.zip

2008年5月27日火曜日

RubberBand(その32)ThinButton改良(2)

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

前回の続き。今回はコード解説。

まずボタンが2つのイメージを扱えるようにメンバ変数とイニシャライザを変更する。
ThinButton.h

@interface ThinButton : NSObject {

NSImage *_image1;
NSImage *_image2;
:
}
- (id)initWithImage:(NSImage*)image1 alterImage:(NSImage*)image2 frame:(NSRect)frame tag:(UInt)tag;
- (NSImage*)image;
- (NSImage*)alterImage;


上記に合わせてThinButton.mの実装を変更した上でボタンを管理する ThinButtonBarクラスを書き直す。こちらもイニシャライザを2つの画像に対応させる。
ThinButtonBar.h
- (void)addButtonWithImageResource:(NSString*)resource alterImageResource:(NSString*)resource2 tag:(UInt)tag;

alter が2つ目の画像を表す。実装では1つ目の画像同様に画像を作り、ThinButtonのインスタンスを作っているだけ。

そして描画コードでこの2つの画像を使い分けるようにする。
- (void)drawRect:(NSRect)rect {

[NSGraphicsContext saveGraphicsState];
[_shadow set];

NSImage *image;
CGFloat alpha;
for (ThinButton *button in _list) {
switch ([button state]) {
case TB_STATE_NORMAL:
image = [button alterImage];
alpha = 1.0;
break;
case TB_STATE_OVER:
alpha = 1.0;
image = [button image];
break;
case TB_STATE_PUSHED:
alpha = 0.75;
image = [button image];
break;
}

[image drawAtPoint:[button frame].origin
fromRect:NSZeroRect
operation:NSCompositeSourceOver
fraction:alpha];
:
:

通常状態(TB_STATE_NORMAL)の時には、alterImage(不透明度100%)を使い、マウスオーバー時に image(不透明度100%)を使っている。ボタンがおされた時は imageを不透明度のみ変えて使い回している。

- - - -
大分形になってきた。

2008年5月26日月曜日

RubberBand(その31)ThinButton改良

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

ThinButton は1種類の画像の透明度を変化させていたが表現としてはちょっとしょぼい。というか視認しづらい。


そこで2種類の画像を用意して状態によって描き分けるように変更した。画像を2種類用意する手間がかかるが見た目はこちらの方が良くなった(と思う)。灰色の部分だけ透明度を 25%にした画像を用意してこれを初期表示させる。


そしてマウスオーバー時に最初の画像を不透明度100%で表示させる。


ソース:RubberBand-16.zip

- - - -
ソースコード解説は次回。

2008年5月25日日曜日

RubberBand(その30)ThinButton張り付け(ピンぼけ)

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

RubberBandを拡大縮小しているとボタン画像がぼやけて見えるのに気がついた。


どうも描画位置に少数点以下が含まれているのが原因のようだ。そこで ThinButtonBar で setFrameOrigin: をオーバライドして強制的に座標を整数にしてしまう。

ThinButtonBar.m

- (void)setFrameOrigin:(NSPoint)p
{
p.x = floor(p.x);
p.y = floor(p.y);
[super setFrameOrigin:p];
}


これで直った。

2008年5月24日土曜日

RubberBand(その29)ThinButton張り付け(2)

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

さていよいよ選択範囲に ThinButtonを貼付ける。

完成コードはこんなかんじ。選択範囲の拡大縮小・移動に伴い ThinButtonも移動する。


ソース:RubberBand-15.zip

位置決め用にメソッドを追加する。指定された NSRect の右下 (-5, -5)にボタンの右下が来るように位置決めする。

-(void)setButtonBarWithFrame:(NSRect)frame
{
NSPoint p;
NSRect bounds = [_button_bar bounds];
p.x = frame.origin.x + frame.size.width - bounds.size.width - 5.0;
p.y = frame.origin.y + frame.size.height - bounds.size.height - 5.0;

[_button_bar setFrameOrigin:p];
}


その上で RubberBandが拡大縮小、移動を行なう個所でこのメソッドを呼出せば良い。

なお初期状態ではボタンを表示させたくないので初期化時に NSView#setHidden:NO を使い隠しておく。範囲選択が始まったところで今度は setHidden:YES を呼出して表示する。

- - - -
ThinButton が部品化されていたのであっけないほど簡単にできてしまった。
範囲サイズが小さい時にボタンが使いづらいのでここを少し調整するか。

2008年5月23日金曜日

RubberBand(その28)ThinButton張り付け

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

再び RubberBandへ戻り、前回までに作った ThinButton を貼付ける。今回は動作確認の為、単純に RubberBandView 上に表示させるだけにした。

こんな感じ。


実装は簡単で、ThinButton と ThinButtonBar のソースを RubberBandのプロジェクトへ追加し、initWithFrame: へ下記コードを書くだけ。部品化しておいたので簡単に済む。
RubberBandView.m

- (id)initWithFrame:(NSRect)frame {
:
NSRect buttonBarFrame = NSMakeRect(50, 50, 0, 0);
ThinButtonBar *button_bar = [[ThinButtonBar alloc] initWithFrame:buttonBarFrame];

[button_bar addButtonWithImageResource:@"icon_cancel" tag:TAG_CANCEL];
[button_bar addButtonWithImageResource:@"icon_timer" tag:TAG_TIMER];
[button_bar addButtonWithImageResource:@"icon_record" tag:TAG_RECORD];
[button_bar setDelegate:self];

[self addSubview:button_bar];
[button_bar release];
:


- - - -
次回はRubberBand内(枠線)に表示されるようにする。これが最終目標で、完成したら SimpleCap へ組み込む。

2008年5月22日木曜日

NSTrackingArea(options指定が不足するとクラッシュ)

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

Tips: NSTrackingArea#initWithRect:options:owner:userInfo: を使ってインスタンスを作成する時の注意点。

options に NSTrackingActiveInKeyWindow を含めないとクラッシュする。


実際にはトラッキング範囲を決めるにあたって NSTrackingActiveInKeyWindowを含むオプションのグループがあるので、おそらくこのどれかが含まれていないとクラッシュすると思われる。

NSTrackingArea.h

/* When tracking area is active.  You must specify one of the following in the NSTrackingAreaOptions argument of -initWithRect:options:owner:userInfo: */
enum {
NSTrackingActiveWhenFirstResponder = 0x10, // owner receives mouseEntered/Exited, mouseMoved, or cursorUpdate when view is first responder
NSTrackingActiveInKeyWindow = 0x20, // owner receives mouseEntered/Exited, mouseMoved, or cursorUpdate when view is in key window
NSTrackingActiveInActiveApp = 0x40, // owner receives mouseEntered/Exited, mouseMoved, or cursorUpdate when app is active
NSTrackingActiveAlways = 0x80, // owner receives mouseEntered/Exited or mouseMoved regardless of activation. Not supported for NSTrackingCursorUpdate.
};



それだけ。

2008年5月21日水曜日

ThinButton(その8)ボタンの部品化(クライアント)

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

コード解説の最後は ThinButtonBarを使うクライアントコード。サンプルでは AppControllerが ThinButtonBarを作り、ボタンを押した時のイベントを受け取っている。

サンプルコードでは、ThinButtonBarを作り、3つのボタンを登録する。その後、NSWindowのcontent viewのサブビューとして追加している。AppController.m

@implementation AppController

-(void)awakeFromNib
{
NSRect buttonBarFrame = NSMakeRect(50, 50, 0, 0);
ThinButtonBar *button_bar = [[ThinButtonBar alloc] initWithFrame:buttonBarFrame];

[button_bar addButtonWithImageResource:@"icon_cancel" tag:TAG_CANCEL];
[button_bar addButtonWithImageResource:@"icon_timer" tag:TAG_TIMER];
[button_bar addButtonWithImageResource:@"icon_record" tag:TAG_RECORD];
[button_bar setDelegate:self];

[[_window contentView] addSubview:button_bar];
[button_bar release];
}



サンプルではイベント処理の実装はログへ書き出すだけ。どのボタンが押されたのかは tag::NSNumber で判断できる。tag値は上で見たようにボタン登録時に AppController側で用意して渡してやる。
-(void)clickedAtTag:(NSNumber*)tag
{
NSLog(@" %@", tag);
}



- - - -
ボタンができた。次は RubberBandへ戻ってこのボタンを貼付けてみよう。

2008年5月20日火曜日

ThinButton(その7)ボタンの部品化(ThinButtonBar)

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

次は描画とイベントハンドリングを司る ThinButtonBarクラス。まずはインターフェイス定義から。

ThinButtonBar

@interface ThinButtonBar : NSView {

NSMutableArray* _list;
CGFloat _offsetX;
NSTrackingArea* _tracking_area;
id _delegate;
ThinButton* _pushed_button;
}

- (void)addButtonWithImageResource:(NSString*)resource tag:(UInt)tag;
- (void)setDelegate:(id)delegate;

@end


NSViewのサブクラスで drawRect: や mouseDown:などのオーバライドが中心となる。クライアントコードでは addButtonWithImageRsource:tag: を使ってボタンを追加(定義)していく。今回は登録された順番に左から右へ横並びでボタンが表示されるようにレイアウトする。 tag はボタンが押されたときに Delegateへ渡される値。

まずは初期化コードから。ThinButtonの配列を格納する NSMutableArrayや NSShadowを作っておく。
ThinButtonBar
static NSShadow* _shadow = nil;

-(id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_list = [[NSMutableArray alloc] init];
_offsetX = 0.0;

if (!_shadow) {
_shadow = [[NSShadow alloc] init];
[_shadow setShadowOffset:NSMakeSize(1.0, -1.0)];
[_shadow setShadowBlurRadius:2.0];
[_shadow setShadowColor:[[NSColor blackColor] colorWithAlphaComponent:0.5]];
}

_tracking_area = nil;
_delegate = nil;
}
return self;
}


続いてボタン追加のメソッド。ちょっぴり長い。最初(1)に NSImageを作った後、ThinButtonを生成し _listへ追加する。次(2)に追加したボタンの幅だけ NSViewの大きさを拡張する。NSViewの拡張に合わせて(3)マウストラッキングエリアを更新する。これはNSViewの大きさ全体を登録しておく。どのボタンがマウスカーソルの下に来ているかはこのクラスで判定して処理する。最後(4)に再描画しておしまい。
- (void)addButtonWithImageResource:(NSString*)resource tag:(UInt)tag
{
//
// (1) setup ThinButton object
//
NSImage *image = [[[NSImage alloc]
initWithContentsOfFile:[[NSBundle mainBundle]
pathForImageResource:resource]] autorelease];

[image setFlipped:YES];

NSRect frame;
frame.origin.x = _offsetX;
frame.origin.y = 0.0;
frame.size = [image size];
frame.size.width += 1.0;
frame.size.height += 1.0;

ThinButton *button = [[[ThinButton alloc] initWithImage:image
frame:frame
tag:tag] autorelease];
[_list addObject:button];


//
// (2) managing offset and frame
//
_offsetX += frame.size.width + TB_MARGIN_WIDTH;

NSSize new_size = [self frame].size;
new_size.width = _offsetX;
if (new_size.height < frame.size.height) {
new_size.height = frame.size.height;
}
[self setFrameSize:new_size];


//
// (3) rearrange tracking area
//
if (_tracking_area) {
[self removeTrackingArea:_tracking_area];
[_tracking_area release];
}

NSRect tracking_rect = [self frame];
tracking_rect.origin = NSZeroPoint;
_tracking_area = [[NSTrackingArea alloc] initWithRect:tracking_rect
options:(NSTrackingMouseEnteredAndExited |
NSTrackingMouseMoved |
NSTrackingActiveInKeyWindow)
owner:self
userInfo:nil];
[self addTrackingArea:_tracking_area];

//
// (4) redraw
//
[self setNeedsDisplay:YES];
}



続いて描画コード。_list からボタン(ThinButton)をひとつずつ取り出して NSImage#drawAtPoint: で描画する。ボタンの状態によって描画時の透明度を制御している。
- (void)drawRect:(NSRect)rect {

[NSGraphicsContext saveGraphicsState];
[_shadow set];

CGFloat alpha;
for (ThinButton *button in _list) {
switch ([button state]) {
case TB_STATE_NORMAL:
alpha = 0.25;
break;
case TB_STATE_OVER:
alpha = 1.0;
break;
case TB_STATE_PUSHED:
alpha = 0.5;
break;
}

[[button image] drawAtPoint:[button frame].origin
fromRect:NSZeroRect
operation:NSCompositeSourceOver
fraction:alpha];

}
[NSGraphicsContext restoreGraphicsState];
}


後はマウスイベントハンドリングのコードが続く。オーバライドしたのは次のメソッド。

mouseEntered: マウスカーソルがボタンの上に載った時の処理(状態を TB_STATE_OVERへ変更)
mouseExited: マウスカーソルがビューの外に出た時の処理(状態を TB_STATE_NORMALへ戻す)
mouseMoved: マウスカーソルがビュー内で移動した時の処理(カーソル下のボタンの状態を TB_STATE_OVERへ変更し、それ以外を TB_STATE_NORMALにする)
mouseDown: マウスが押されたときの処理(状態を TB_STATE_PUSHEDへ変更)
mouseUp:   マウスが離された時の処理(状態を TB_STATE_OVERへ変更し、_delegateの clickedAtTag: を呼出す)

- (void)mouseEntered:(NSEvent *)theEvent {

[self changeState:TB_STATE_OVER withEvent:theEvent];
}

- (void)mouseExited:(NSEvent *)theEvent {
if (!_pushed_button) {
[self changeState:TB_STATE_NORMAL withEvent:theEvent];
}
}

- (void)mouseMoved:(NSEvent *)theEvent {
[self changeState:TB_STATE_OVER withEvent:theEvent];
}

- (void)mouseDown:(NSEvent *)theEvent {
_pushed_button = [self changeState:TB_STATE_PUSHED withEvent:theEvent];
}

- (void)mouseUp:(NSEvent *)theEvent {
ThinButton *hitButton = [self changeState:TB_STATE_OVER withEvent:theEvent];

if (hitButton == _pushed_button) {
if (hitButton && [_delegate respondsToSelector:@selector(clickedAtTag:)]) {
[_delegate performSelector:@selector(clickedAtTag:)
withObject:[NSNumber numberWithInt:[hitButton tag]]];
}
}
_pushed_button = nil;
}


ボタンを離す mouseUp: で _delegate#clickedAtTag: を呼出すようにしている。コンパイル時の Warningが嫌だったので直接[_delegate clickedAtTag:]と呼出すのではなく performSelector:withObject: を使ってイベントを伝達させている。念のため respondsToSelector: でメソッドの実装チェックも行っておいた。


どのメソッドも ThinButtonBar#changeState:withEvent: を呼出している。ここではマウスカーソル下のボタンを指定状態(state)へ変更し、それ以外のボタンの状態を TB_STATE_NORMALへ変更している。状況によってはこのコード処理は冗長な部分もあるのだが、この1つのメソッドに処理がまとめられたので、呼出しもとのメソッドがかなりすっきりとした。
- (ThinButton*)changeState:(UInt)state withEvent:(NSEvent*)theEvent
{
NSPoint p = [self convertPoint:[theEvent locationInWindow] fromView:nil];
ThinButton* hitButton = nil;
for (ThinButton* button in _list) {
if ([button hitAtPoint:p]) {
hitButton = button;
[button setState:state];
} else {
[button setState:TB_STATE_NORMAL];
}
}
[self setNeedsDisplay:YES];
return hitButton;
}

2008年5月19日月曜日

ThinButton(その6)ボタンの部品化(ThinButton)

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

まず ThinButtonの解説から。このクラスは1つのボタンを表しており、表示する画像や大きさ状態を管理する。描画やイベントハンドリングはここでは行わない。

ThinButton.h

enum TB_STATE {
TB_STATE_NORMAL,
TB_STATE_OVER,
TB_STATE_PUSHED
};

@interface ThinButton : NSObject {

NSImage *_image;
NSRect _frame;
UInt _state;
UInt _tag;
}
- (id)initWithImage:(NSImage*)image frame:(NSRect)frame tag:(UInt)tag;
- (BOOL)hitAtPoint:(NSPoint)point;

- (UInt)state;
- (void)setState:(UInt)state;

- (NSImage*)image;
- (NSRect)frame;
- (UInt)tag;

@end



値の保持が目的なので処理らしい処理は特に行っていない。面倒な処理は ThinButtonBar で行なう。主要なメソッド実装を下記に示す。
ThinButton.m
@implementation ThinButton

- (id)initWithImage:(NSImage*)image frame:(NSRect)frame tag:(UInt)tag
{
self = [super init];
if (self) {
_image = [image retain];
_frame = frame;
_tag = tag;
_state = TB_STATE_NORMAL;
}
return self;
}

- (BOOL)hitAtPoint:(NSPoint)point
{
return NSPointInRect(point, _frame);
}

2008年5月18日日曜日

ThinButton(その5)ボタンの部品化

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

前回からの続き。

最初の実装はボタン1つ1つが NSViewのサブクラスだった。描画からイベントのハンドリングをそれぞれのボタンが行っていた事になる。この方法は個々のクラスの実装は簡単になるが、ボタンが多くなるとリソースを多く使う事になる。また個々の並びを結局利用側で制御することになって面倒だ。


そこで NSViewは一つにして複数のボタンの描画とイベントハンドリングを自前で行なうことにした。


実装はやや複雑になるがこの方法のメリットはボタンの増加にともなうリソース増加を(ちょっぴり)抑えられるのと、レイアウトの責務も持たせることができる。

2008年5月17日土曜日

ThinButton(その4)ボタンの部品化(改良..単一ビュー化)

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

前回ThinButtonの部品化を行ったが、ボタン一つ一つに NSViewを割り振るのはなんだかもったいないし、用途からすると1つ1つ作成して位置指定などするのは面倒だ。そこで複数のボタンを単一ビュー化して取り扱えるようにした。

ソース:ThinButton-4.zip

見た目は全然変わっていないが実装は大幅に変えた。




コードの解説はまた今度に。

2008年5月16日金曜日

ThinButton(その3)ボタンの部品化

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

感じがつかめたので使い回せるように手を入れる。

ソース:ThinButton-3.zip

ボタンを押した時のメッセージ送り先 _target と ボタンを識別するための_tagを追加した。また影を付けたいので shadowも保持することにした。
ThinButton.h

@interface ThinButton : NSView { 
NSImage *_image;
NSTrackingArea* _tracking_area;
UInt _state;
NSShadow* shadow;
id _target;
UInt _tag;
}
- (id)initWithImageResource:(NSString*)resource atPoint:(NSPoint)point target:(id)target tag:(UInt)tag
;
- (UInt)tag;
@end


初期化は専用のイニシャライザで行なう。この中で画像をバンドルから取り出した後、NSViewの初期化を行なう。その他、トラッキングエリアの登録と影の準備を行なう。
ThinButton.m
- (id)initWithImageResource:(NSString*)resource atPoint:(NSPoint)point target:(id)target tag:(UInt)tag
{
NSImage *image = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForImageResource:resource]];

NSRect frame;
frame.origin = point;
frame.size = [image size];
frame.size.width += 1.0;
frame.size.height += 1.0;

self = [super initWithFrame:frame];
if (self) {
_image = image;
[_image setFlipped:YES];

NSRect track_rect = frame;
track_rect.origin = NSZeroPoint;
_tracking_area = [[NSTrackingArea alloc] initWithRect:track_rect
options:(NSTrackingMouseEnteredAndExited|NSTrackingActiveInKeyWindow)
owner:self
userInfo:nil];
[self addTrackingArea:_tracking_area];

shadow = [[NSShadow alloc] init];
[shadow setShadowOffset:NSMakeSize(1.0, -1.0)];
[shadow setShadowBlurRadius:2.0];
[shadow setShadowColor:[[NSColor blackColor] colorWithAlphaComponent:0.5]];
_state = 0;

_target = target;
_tag = tag;
}
return self;
}


その他は前回までとほとんど同じ。違うのはマウスを押した時にターゲットへイベントを送る処理を追加したところ。メソッドの存在をチェックした上でメッセージを送る。
- (void)mouseUp:(NSEvent *)theEvent {
[self changeState:1];
if ([_target respondsToSelector:@selector(click:)]) {
[_target performSelector:@selector(click:) withObject:self];
}
}



一方、このボタンを使う側のコードはこんな感じ。
AppController.m
-(void)awakeFromNib
{
ThinButton *button;
button = [[[ThinButton alloc] initWithImageResource:@"icon_cancel"
atPoint:NSMakePoint(100,100)
target:self
tag:TAG_CANCEL] autorelease];
[[_window contentView] addSubview:button];
button = [[[ThinButton alloc] initWithImageResource:@"icon_timer"
atPoint:NSMakePoint(122,100)
target:self
tag:TAG_TIMER] autorelease];
[[_window contentView] addSubview:button];
button = [[[ThinButton alloc] initWithImageResource:@"icon_record"
atPoint:NSMakePoint(144,100)
target:self
tag:TAG_RECORD] autorelease];

[[_window contentView] addSubview:button];
}

initWithImageResource: を使い初期化した後、NSWindowの contentViewのサブビューとして追加していく。

ボタンが押されたら click: が呼ばれる。[sender tag] によりどのボタンが押されたかが判別できる。
-(void)click:(id)sender
{
NSLog(@"%@, %d", sender, [sender tag]);
}




サンプルを実行すると3つのボタンが薄らと表示される。


マウスカーソルを載せたり、押すと、透明度が変化する。


- - - -
簡単にできたがボタン一つ一つが NSViewなのがちょっとひっかかる。1つの NSViewで複数のボタンを処理した方が良い気がしてきた。

2008年5月15日木曜日

ThinButton(その2)

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

カスタムボタンのコード解説。

ボタンは NSView のサブクラスとして実装する。

ThinButton.h

@interface ThinButton : NSView {

NSImage *icon;
NSRect icon_frame;
NSTrackingArea* icon_tracking_area;
int state;
}
@end

メンバ変数として、表示するアイコン画像(icon)、表示領域(icon_frame)、マウストラッキングエリア、そしてボタンの状態(state)を用意する。

次に実装コード。まずはアイコン画像から。
ThinButton.m
- (id)initWithFrame:(NSRect)frame {
:
NSString *file = [[NSBundle mainBundle] pathForResource:@"icon_cancel"
ofType:@"png"];
icon = [[NSImage alloc] initWithContentsOfFile:file];}
:
:

初期化の時に NSImageを作っておく。画像はあらかじめバンドル内に用意しておく(開発時にXCodeプロジェクトへ加えておけば自動的にアプリケーション内にバンドルされる)。

描画は drawAtPoint: を使う。ボタンの状態(state)によって透明度を変える。
- (void)drawRect:(NSRect)rect {
CGFloat alpha;
switch (state) {
case 0:
alpha = 0.25;
break;
default:
alpha = 1.0;
break;
}
[icon drawAtPoint:icon_frame.origin
fromRect:NSZeroRect
operation:NSCompositeSourceOver
fraction:alpha];
}


これでアイコンが表示されるようになった。次はマウスカーソルオーバー時の処理。NSTrackingAreaを使う。これは以前ブログで紹介した。

初期化コード内で NSTrackingAreaを作成し、NSViewへ登録する。
- (id)initWithFrame:(NSRect)frame {
:

icon_tracking_area = [[NSTrackingArea alloc] initWithRect:icon_frame
options:(NSTrackingMouseEnteredAndExited|NSTrackingActiveInKeyWindow)
owner:self
userInfo:nil];
[self addTrackingArea:icon_tracking_area];
:


これでマウスカーソルオーバー時に mouseEntered: と mouseEnteredExited: が呼び出されるようになる。
- (void)mouseEntered:(NSEvent *)theEvent {
if (state == 0) {
[self changeState:1];
}
}
- (void)mouseExited:(NSEvent *)theEvent {
if (state == 1) {
[self changeState:0];
}
}


状態変更はメソッド化して併せて再描画要求も出すようにしておく。
- (void)changeState:(int)aState
{
state = aState;
[self setNeedsDisplayInRect:icon_frame];
[self setNeedsDisplay:YES];
}

2008年5月14日水曜日

ThinButton (その1)

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

用意したアイコン画像を使ったカスタムボタンを使いたい。NSButton でも画像の貼り付けはできるが、画像を半透明にする(良い)方法が見つからなかった。意図しているカスタムボタンを作るのはそれほど難しくないので後々を考えて作ることにした。

仕様は、初期状態で画像を半透明表示し、マウスカーソルが乗ると不透明表示にする。ボタンを押したり・離したりしたときに、同様に透明度を制御してユーザへフィードバックする。

で、作ってみた。
ソース:ThinButton-01.zip

サンプルを実行するとウィンドウ内にアイコンが表示される。この時点では透明(0.25)になっている。


このボタンの上にマウスカーソルを載せると不透明(1.0)表示になる。


ボタンの押し・離しも同様に透明度の制御を行って、押していることがわかるようにした。

解説は次回。

2008年5月13日火曜日

RubberBand(その27)サイズ制御

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

続いて移動制御の次はサイズ制御。ビューの領域を超えた拡大・縮小ができないように制限を加える。



前回の移動制御とほぼ同様のコードだが微妙に異なる。この為コードは別に用意した。0.1は前回同様境界で点線を表示させる為。

 NSRect bounds = [self bounds];
if (nx < 0) {
nw = _rect.size.width + _rect.origin.x;
nx = 0.0;
} else if (nx + nw > bounds.size.width) {
nw = bounds.size.width - nx - 0.1;
} else if (nx + nw < 0) {
nw = -_rect.origin.x;
}
if (ny < 0) {
nh = _rect.size.height + _rect.origin.y;
ny = 0.1;
} else if (ny + nh > bounds.size.height) {
nh = bounds.size.height - ny;
} else if (ny + nh < 0) {
nh = -_rect.origin.y + 0.1;
}
_rect = NSMakeRect(nx, ny, nw, nh);


- - - -
拡大縮小は制約されるようになったがマウスカーソルの移動までは制約していない。この為、ウィンドウの外での動きが拡大縮小に反映されてしまう。マウスの動きを制約すべきだろうか?

2008年5月12日月曜日

RubberBand(その26)移動制御

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

現在の実装では RubberBand は表示領域を超えて移動できてしまう。


想定しているアプリでは、これは無意味なので表示領域を超えた移動をさせないように制約を加える事にする。

RubberBandView#mouseDown: へ下記コードを加える。

 // constrain rules
_rect.origin.x = fmax(_rect.origin.x, 0.0);
_rect.origin.y = fmax(_rect.origin.y, 0.1);
NSRect bounds = [self bounds];
if (_rect.origin.x + _rect.size.width > bounds.size.width) {
_rect.origin.x = bounds.size.width - _rect.size.width - 0.1;
}
if (_rect.origin.y + _rect.size.height > bounds.size.height) {
_rect.origin.y = bounds.size.height - _rect.size.height;
}

上下、左右の領域ではみ出すケースを検出して、強制的にサイズと位置を補正する。

すると領域をはみ出す移動はできなくなる。


なお補正値で 0.0 ではなく一部 0.1 を使っている。これは 0.0のままだと上と右の領域一杯へ移動した場合に線が消えてしまう為。


0.0 を 0.1 に変えると表示される。ちょっと場当たり的だが十分役に立つ。


ソース:RubberBand-14.zip

2008年5月11日日曜日

RubberBand(その25)サイズの表示を制御する

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

スペースバーを押してサイズ表示を制御できるようにする。

メンバ変数へフラグを用意する。初期状態は YES(表示)。

@interface RubberBandView : NSView {
:
BOOL _is_display_info;
}


スペースバーが押されたらフラグを反転させる。
- (void)keyDown:(NSEvent*)theEvent
{
switch ([theEvent keyCode]) {
case 49:
// spacebar
[self setDisplayInfo:!_is_display_info];
break;
:


後はセッターと描画制御を加えればできあがり。
- (void)setDisplayInfo:(BOOL)flag
{
_is_display_info = flag;
[self setNeedsDisplayInRect:_rect];
[self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)rect {
if (_is_display_info) {
[self drawInformation];
}
:



スペースバーを押すと左上のサイズ数値が消える。もう一度押すと表示される。

2008年5月10日土曜日

RubberBand(その24)初期状態

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

RubberBand もずいぶん長くなったが、アプリに必要な検証および機能実装も大分できてきた。後もう少し。

今回はアプリへの組み込みを想定して、初期状態を導入する。今までは初期状態はあらかじめ決まった大きさの RubberBandを表示していたが、アプリで使う場合の初期状態は RubberBandが表示されない状態を想定している。


ここである点をクリックしてドラッグするとRubberBandが現れる。この後はいままでと同じ。


状態を管理する為にメンバ変数に _state を用意する。0 は初期状態(非表示)、1 を表示状態とする。

@interface RubberBandView : NSView {
:
int _state;
}


_state == 0 の時は描画させない。
- (void)drawRect:(NSRect)rect {

if (_state == 0) {
return;
}
:


マウスクリック時の処理を状態によって変える。
- (void)mouseDown:(NSEvent*)event
{
NSPoint pp, cp;
cp = [self convertPoint:[event locationInWindow] fromView:nil];
int knob_type;

switch (_state) {
case 0:
while ([event type] != NSLeftMouseUp) {
event = [[self window] nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
NSPoint cp2 = [self convertPoint:[event locationInWindow] fromView:nil];
if (fabs(cp2.x - cp.x) > KNOB_WIDTH || fabs(cp2.y - cp.y) > KNOB_WIDTH) {
knob_type = 7; // BOTTOM_RIGHT;
_rect = NSMakeRect(cp.x, cp.y, 0, 0);
_state = 1;
break;
}
if ([event type] == NSLeftMouseUp) {
return;
}
}
break;
case 1:
knob_type = [self knobAtPoint:cp];
break;
default:
break;
}
:
(今までのドラッグ処理)


初期状態(_state ==0)の時にマウスクリックがあったらイベントループを作りドラッグとボタン離しを監視する。一定以上(KNOB_WIDTH)以上の移動があったらドラッグ開始とみなしてループを抜ける。この時、初期位置・サイズを決定した上で、右下のKnobを押しているように振る舞わせる。そして状態を 1にする。この後は今まで作ってきた通常のドラッグ処理に合流する。一方、ドラッグ無しでマウスボタンが押された場合は何もせずにその場でメソッド処理を抜ける。

ソース:RubberBand-13.zip

2008年5月9日金曜日

RubberBand(その23)コンテキストメニュー(2)

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

コンテキストメニュー続き。ビューの特定のエリアだけメニューが出るようにしたい。

コンテキストメニューを表示する直前に NSView#menuForEvent: が呼ばれる。メニューを動的に変えたい場合はこのメソッドをオーバライドするので、これを使ってみることにする。

- (NSMenu *)menuForEvent:(NSEvent *)event
{
NSPoint p = [self convertPoint:[event locationInWindow] fromView:nil];
if (NSPointInRect(p, _rect)) {
return [self menu];
} else {
return nil;
}
}


event からマウスクリック位置を取り出して、それが RubberBand上か調べる。もしそうなら Outletに接続された NSMenu を返し、そうでないなら nil を返す。

結果はうまくいった。

2008年5月8日木曜日

RubberBand(その22)コンテキストメニュー

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

しつこく RubberBand。今度はコンテキストメニューをつける。

Interface Builderを使えば簡単にできる。メニューを新規に用意した後、NSViewの Outletへ接続してやれば良い。




ビューの上で右クリックするとメニューが表示される。


簡単にできたのは良いが現在はビューの上ならどこでもメニューが出てしまう。RubberBandの上だけに制限したい。どうするんだろうか。

2008年5月7日水曜日

RubberBand(その21)矢印キーでの移動(2)

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

矢印キーの移動を実装した後、Undoオペレーションで挙動がおかしくなった。Command+Z キーを押しただけで Undoが可能になってしまう。どうも Command+Z キーのイベントを keyDown: で処理しているのがまずいようだ。

- (void)keyDown:(NSEvent*)theEvent
{
NSUndoManager* undoManager = [self undoManager];
[[undoManager prepareWithInvocationTarget:self]
setRubberBandFrame:_rect];
:


そりゃそうだ。どんなキーが押されても NSUndoManager へ復元用のメソッドを登録しているからだ。手直しして少しましな形にしよう。
- (void)keyDown:(NSEvent*)theEvent
{
NSRect rect = _rect;
BOOL is_modified = NO;
NSString *action_name;
switch ([theEvent keyCode]) {
case 123:
// left
rect.origin.x += -1.0;
is_modified = YES;
action_name = @"Moved left";
break;
case 124:
// right
rect.origin.x += +1.0;
action_name = @"Moved right";
is_modified = YES;
break;
case 125:
// down
rect.origin.y += +1.0;
action_name = @"Moved down";
is_modified = YES;
break;
case 126:
// up
rect.origin.y += -1.0;
action_name = @"Moved up";
is_modified = YES;
break;
default:
break;
}
if (is_modified) {
NSUndoManager* undoManager = [self undoManager];
[[undoManager prepareWithInvocationTarget:self]
setRubberBandFrame:_rect];
[undoManager setActionName:action_name];
[self setRubberBandFrame:rect];
}
}


これで Undoに関するおかしな挙動はなくなった。

2008年5月6日火曜日

RubberBand(その20)矢印キーでの移動

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

矢印キーで RubberBand を移動させる。これは簡単。

まず NSView の #acceptsFirstResponder をオーバライドする。

- (BOOL)acceptsFirstResponder
{
return YES;
}


後は #keyDown: を実装するだけ。前回導入した Undo 向けの処理も加えてある。
- (void)keyDown:(NSEvent*)theEvent
{
NSUndoManager* undoManager = [self undoManager];
[[undoManager prepareWithInvocationTarget:self]
setRubberBandFrame:_rect];

NSRect rect = _rect;
switch ([theEvent keyCode]) {
case 123:
// left
rect.origin.x += -1.0;
[undoManager setActionName:@"Moved left"];
break;
case 124:
// right
rect.origin.x += +1.0;
[undoManager setActionName:@"Moved right"];
break;
case 125:
// down
rect.origin.y += +1.0;
[undoManager setActionName:@"Moved down"];
break;
case 126:
// up
rect.origin.y += -1.0;
[undoManager setActionName:@"Moved up"];
break;
default:
break;
}
[self setRubberBandFrame:rect];

}

2008年5月5日月曜日

rubberBand(その19)Undoの実装 / NSUndoManager

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

今度は拡大縮小と移動の Undo/Redo ができるようにする。

Cocoa には Undo用の強力なフレームワークが用意されていて簡単に実装できる。Undo時に適用するメソッドを NSUndoManager へ登録しておくと、Undoが呼ばれた時に自動的にそのメソッドが呼出される。プログラマは複雑なUndoの管理をする必要はなく、ただ復元の為のメソッドをNSUndoManagerへ登録するだけで良い。


その状態でユーザが Undoを実行すると NSUndoManagerが自動的に登録されたメソッドを自動的に呼出す。



RubberBandでの実装ポイントは2つ。

まず拡大縮小および移動直前に、現在の情報を NSUndoManager へ登録しておく。

- (void)mouseDown:(NSEvent*)event
{
NSUndoManager* undoManager = [self undoManager];
[[undoManager prepareWithInvocationTarget:self]
setRubberBandFrame:_rect];
:
}


上記だけでも Undoが効くようになるが Redoさせるには #setRubberBandFrame: の中でも NSUndoManager への登録が必要。この時、NSUndoManager は自ら Undo中なのか、Redo中なのかを認識している為、Undo/Redoどちらの用途でも同じメソッドが使える。
-(void)setRubberBandFrame:(NSRect)frame
NSUndoManager* undoManager = [self undoManager];
[[undoManager prepareWithInvocationTarget:self]
setRubberBandFrame:_rect];
:
}


これで Undo/Redo の実装は終わり。実に良くできている。なお #setRuberBandFrame: はUndo/Redo以外でも呼出されるため、このままだとまだ操作をしていない初期状態でも Undoが効いてしまう。そこで Undo/Redo 中かどうかの判断を入れておく。
 NSUndoManager* undoManager = [self undoManager];
if ([undoManager isUndoing] || [undoManager isRedoing]) {
[[undoManager prepareWithInvocationTarget:self]
setRubberBandFrame:_rect];
}



NSUndoManager には #setActionName: が用意されており、これを使うとメニューの Undo欄に表示するメッセージを指定することができる。
[undoManager setActionName:@"Resized rectangle"];




ソース:RubberBand-12.zip

2008年5月4日日曜日

rubberBand(その18)縦横比率を維持したままリサイズ(その5)

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

前回の補足。

縦横比維持のリサイズの場合、x, y, width, height の小数点が発生することになる。その為、そのまま描画すると画面上はKnobの線が微妙に太く見えたりする場合がある。


描画時に整数になるよう小数点を切り捨てる処理を加える。

 NSRect rect;
rect.origin.x = floor(_rect.origin.x);
rect.origin.y = floor(_rect.origin.y);
rect.size.width = floor(_rect.size.width);
rect.size.height = floor(_rect.size.height);


すると線が奇麗に決まる。

2008年5月3日土曜日

rubberBand(その17)縦横比率を維持したままリサイズ(その4)

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

縦横比維持のリサイズ続き。


コーディングを重ねるもなかなか思い通りに行かず混乱してきた。一旦立ち止まって整理してみることにした。

その結果、マウスの制約うんぬんよりも、ドラッグの方向のルールをきちんと決めないから意図通りの動きになっていないことに気がついた。

右下の Knob を例に取ると拡大縮小のルールは下記のようになる。


Knob上に45度の仮想的な直線を想定し、そこを境に右下方向へマウスがドラッグされた場合は x,y共に拡大させる。逆に左上の領域の場合は縮小させる。

どちらの領域になるかは、ある点が直線の上下どちらにあるかを判断すればわかる。


点(x1, y1)が直線 y=ax+b の上下どちらにあるかは、a*x1+b と y1 の値を比較すれば分かる。これを使って拡大・縮小処理を行なう。

この考えで四隅のKnobのルールをまとめると次のようになる。


左上・右下と、左下・右上は仮想線に対する拡大・縮小のルールが共通なのがわかる。つまりこの2グループ x 2領域 の4パターンを考えれば良い。グループ分けは Knobの位置で判断し、後者の領域は先の直線に対する点の位置判定で判断できる。

後はこれをコーディングに落とすだけ。

 CGFloat rule_s = rule.w * rule.h;
CGFloat sx, sy;
if (rule_s > 0) {
if (-dx < dy) {
sx = +1;
sy = +1;
} else {
sx = -1;
sy = -1;
}
resized = YES;
} else if (rule_s < 0) {
if (dx < dy) {
sx = -1;
sy = +1;
} else {
sx = +1;
sy = -1;
}
resized = YES;
}
if (resized) {
CGFloat dl = sqrt(dx*dx + dy*dy);
dx = sx * dl * pcos;
dy = sy * dl * psin;
}


・rules_s は以前定義したknobの位置情報からグループ判断に必要な情報を用意している。
・仮想線は切片b=0、傾き45度(or -45度)なので a=1 or -1。点の位置は (-dx < dy) もしくは (dx < dy)で判断できる。
・pcos , psin はあらかじめ 45度の cos, sin値を用意してある。

紆余曲折の上、やっとできた。やれやれ。


RubberBand-11.zip

2008年5月2日金曜日

RubberBand(その16)縦横比率を維持したままリサイズ(その3)

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

前回からの続きで、マウスカーソルに制約を加える。NSView上の任意の点にマウスカーソルを留まらせる為に CGDisplayMoveCursorToPoint( ) を使う。CGDisplayMoveCursorToPoint は表示されているディスプレイの左上を原点(0,0)とする座標系を使う。つまり NSViewの座標系から、最終的には画面左上が原点(0,0)の座標系に変換する必要がある。

こんな感じ。


ビューの座標系の原点が左上(0,0)になっているのはフリップ(flip)している為。

RubberBandの左下のKnobの座標値は、実際次のようになった。

(1) ビュー座標   (50,185)
(2) ウィンドウ座標 (50,52) ...フリップが補正されている
(3) スクリーン座標 (71,614) ...画面上の方にあるので Y値が大きくなった
(4) CGDisplay系座標 (71,240) ...Y値が補正された(フリップした)


座標変換のコードは次の通り。

CGDirectDisplayID displayID = kCGDirectMainDisplay;
CGRect screen_rect = CGDisplayBounds(displayID);
NSPoint p1 = [self convertPointToBase:pp];
NSPoint p2 = [[self window] convertBaseToScreen:p1];
CGPoint cpp = NSPointToCGPoint(p2);
cpp.y = screen_rect.size.height - cpp.y;
CGDisplayMoveCursorToPoint(displayID, cpp);


各変数がそれぞれの座標系における位置を表す。
pp ... ビュー座標
p1 ... ウィンドウ座標
p2 ... スクリーン座標
cpp .. CGDisplay系座標

スクリーン座標から、CGDisplay系座標への変換方法が分からなかったのでディスプレイの縦ピクセルから最終的に求めている。

- - - -
これでマウスの動きに制約が加えられる。
次は本題の縦横比率リサイズへ戻る。

2008年5月1日木曜日

rubberBand(その15)縦横比率を維持したままリサイズ(その2)

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

縦横比を維持しながらのリサイズの続き。

そもそもマウスカーソルを自由に動かせるから不自然な動きになってしまうのでは?
縦横比によって決まる傾きに沿ってのみカーソルが動くようにすれば不自然感はなくなるのではないか?



参考のためエクセルがどういう動きになっているかを調べてみた。


エクセルでもマウスカーソルの動きに制限を加えていた。エクセルでシフトキーを押しながらリサイズをするとマウスカーソルは矢印の範囲でしか動かない(この説明では分かりづらいと思うのでエクセルを持っている人は試してみて下さい)。やっぱりそうなるのか。


方式はともかくマウスカーソルの動きに制約を加えることにする。マウスカーソルの位置を指定する関数が CGDirectDisplay.h にある。

CGDisplayMoveCursorToPoint(CGDirectDisplayID display, CGPoint point);


- - - -
まずはこの関数を使いマウスカーソル位置の固定を試してみた。
ところが NSView はフリップしている(Y座標系が反転している)のでマウスの Y位置が意図通りの場所にならない。
うーむ。