ページ

ラベル RubberBand の投稿を表示しています。 すべての投稿を表示
ラベル RubberBand の投稿を表示しています。 すべての投稿を表示

2008年8月9日土曜日

SimpleCap (38) 範囲選択:Shiftキーによる平行移動

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

範囲選択で平行移動ができると便利だ。エクセルなどでは Shiftキーを押しながら図形を移動するとX軸、またはY軸方向のどちらかに沿っての平行移動が可能になる。



範囲選択(RubberBand)の移動個所に手を入れてみた。
SelectionHandler.m

NSPoint p_cp = cp; 
int constrain_mode = 0;
while ([theEvent type] != NSLeftMouseUp) {
theEvent = [window nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
cp = [view convertPoint:[theEvent locationInWindow] fromView:nil];
BOOL is_shiftkey = ([theEvent modifierFlags] & NSShiftKeyMask) ? YES : NO;

// constrain rule (1)
if (is_shiftkey) {
CGFloat dcpx = fabs(cp.x - p_cp.x);
CGFloat dcpy = fabs(cp.y - p_cp.y);
if (constrain_mode == 0) {
constrain_mode = (dcpx > dcpy) ? 1 : 2;
} else {
if (dcpx == 0 && dcpy > 5.0) {
constrain_mode =2;
} else if (dcpx > 5.0 && dcpy == 0) {
constrain_mode =1;
}
}
switch (constrain_mode) {
case 1: // horizontal moving
_rect.origin.x = cp.x + dx;
break;
case 2: // vertical moving
_rect.origin.y = cp.y + dy;
break;
default:
break;
}
} else {
_rect.origin.x = cp.x + dx;
_rect.origin.y = cp.y + dy;
}
  :
  :
p_cp = cp;
}


イベントループを形成(while)し、マウスのドラッグに合わせて RubberBandの位置を調整している。Shiftキーが押された場合には constrain_modeを設定し、水平方向または垂直方向のみに移動を強制するようにしている(switch文の個所)。

ただエクセルでもそうだが、例えば垂直方向に移動している途中で、左右に少し大きな移動を行なうと水平方向の平行移動に切り替わる。これは便利なので SimpleCap でもまねする。上記コードで下記の個所がモードきりかえの部分。
 if (constrain_mode == 0) {
constrain_mode = (dcpx > dcpy) ? 1 : 2;
} else {
if (dcpx == 0 && dcpy > 5.0) {
constrain_mode =2;
} else if (dcpx > 5.0 && dcpy == 0) {
constrain_mode =1;
}
}


最初の ==0 は初期処理。その後の if分が途中でのモード切り替え。例えば、水平方向の移動量が0(dcpx==0)で、垂直方向の移動量が5.0以上(dcpy > 5.0)なら、移動モードを垂直方向に切り替える。 "5.0" は実際に試してみて感覚的に決めた数値。

2008年6月4日水曜日

RubberBand(その38)SimpleCapへ組み込み

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

さていよいよ RubberBandを SimpleCap へ組み込む。まずは SimpleCap のアーキテクチャのおさらいから。


SimpleCap は単一の NSWindowとNSViewから構成されていて、キャプチャ毎に処理クラス(ハンドラ)を用意している。NSViewへの表示やイベントハンドリングは主要メッセージをこれらのハンドラへ転送している。転送対象のメソッドは Handlerプロトコルで定義されている。

Handler.h

@protocol Handler

- (void)reset;
- (void)start;
- (void)tearDown;
- (void)drawRect:(NSRect)rect;
- (void)mouseMoved:(NSEvent *)theEvent;
- (void)mouseDown:(NSEvent *)theEvent;
- (void)keyDown:(NSEvent *)theEvent;

@end


今まで作ってきた RubberBandは NSViewのサブクラスだが使っているメソッドは上記に含まれるのでハンドラで十分実装できる。そこで SelectionHandler へ RubberBand のソースコードを移植する。これは若干の手直しでできた。とりあえず組み立て完了。




- - - -
RubberBandを開始してからようやくここまで来られた。時間がかかっただけに今回はちょっとうれしい。趣味でやっていると遅々として進まない部分もあるが、こうして形が見えてくるとやっぱり楽しい。

次回以降、いよいよ SimpleCapの選択範囲キャプチャの実装に入っていく。

2008年6月3日火曜日

RubberBand(その37)コピー&ペースト(2)

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

RubberBandへコピー機能を組み込んでみる。ボタンを押したときの RubberBandの内容をペーストボードへコピーする。

ボタンを押したときのイベントハンドラにコードを実装する。今回は背景画像を表示している ImageView.m の中に記述することにした。

ImageView.m

-(void)clickedAtTag:(NSNumber*)tag
{
[self lockFocus];
NSBitmapImageRep *bitmap_rep =[[NSBitmapImageRep alloc] initWithFocusedViewRect:_rect];
[self unlockFocus];
NSData* data = [bitmap_rep representationUsingType:NSTIFFFileType
properties:[NSDictionary dictionary]];
NSPasteboard *pb = [NSPasteboard generalPasteboard];
[pb declareTypes:[NSArray arrayWithObject:NSTIFFPboardType] owner:self];
[pb setData:data forType:NSTIFFPboardType];
}


RubberBandの選択範囲に表示されている内容を NSBitmapImageRep へ落とし、これを TIFFデータに変換してペーストボードへ書き込む。ペーストボードタイプには NSTIFFPboardType を使ってみた。

実行して任意の範囲をコピーし、プレビューに貼付けてみる。

できた。RubberBandやボタンが含まれているのは手抜き。。

前回のソフトを使ってペーストボードの内容を見てみる。
(
"public.tiff",
"NeXT TIFF v4.0 pasteboard type",
"com.apple.pict",
"Apple PICT pasteboard type"
)

TIFFだけでなく PICTも自動的に用意してくれるようだ。

2008年6月2日月曜日

RubberBand(その37)コピー&ペースト(1)

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

次は選択範囲をコピー&ペーストできるようにする。どんなデータ型をペーストボードへ入れるべきか?他のアプリを調べてみよう。他のアプリでコピーした内容を表示する簡単なアプリを作ってみた。



ソース:PasteType.zip

コードはこれだけ。ペーストボード内のデータ型一覧をテキストビューへ表示する。
AppController.m

-(IBAction)paste:(id)sender
{
NSPasteboard *pb = [NSPasteboard generalPasteboard];
[_text insertText:[[pb types] description]];
[_text insertText:@"\n"];
}



まずはプレビューから。
プレビュー
(
"public.tiff",
"NeXT TIFF v4.0 pasteboard type",
"dyn.agu8yww4xqz0gn25yrf106wpsq31u",
IKSelectionInfo,
"com.apple.pict",
"Apple PICT pasteboard type"
)

TIFF と PICT、それに珍しいものとして IKSelectionInfo というのがある。ImageKit に関係するものだろうか。

次にエクセル。PICTを除きオリジナルのものが多いようだ。
Excel
(
"dyn.agk8yky4mk6",a
"CorePasteboardFlavorType 0x44534947",
"dyn.agk8y4znwk2",
"CorePasteboardFlavorType 0x4D555246",
"dyn.agk8yny4unq",
"CorePasteboardFlavorType 0x45535053",
"com.apple.pict",
"Apple PICT pasteboard type",
"dyn.agk8y8vwxkq",
"CorePasteboardFlavorType 0x4F465343"
)


サファリは画像だけでなく URLや文字列もペーストボードに入れている。画像だけ見るとやはり TIFFとPICT。
Safari
(
"public.tiff",
"NeXT TIFF v4.0 pasteboard type",
"dyn.agu8zs3pcnzme2641rf4guzdmsv0gn64uqm10c6xenv61a3k",
WebURLsWithTitlesPboardType,
"dyn.agu8yc6durvwwaznwmuuha2pxsvw0e55bsmwca7d3sbwu",
"Apple URL pasteboard type",
"public.url",
"CorePasteboardFlavorType 0x75726C20",
"public.url-name",
"CorePasteboardFlavorType 0x75726C6E",
"public.utf8-plain-text",
NSStringPboardType,
"com.apple.flat-rtfd",
"NeXT RTFD pasteboard type",
"dyn.agu8y63n2nuuha5dbrf1ca2pxqry0wkduqf31k3pcr7u1e3basv61a3k",
"NeXT plain ascii pasteboard type",
"com.apple.pict",
"Apple PICT pasteboard type"
)


FireFoxは PICTだけ。
Firefox
(
"com.apple.pict",
"Apple PICT pasteboard type",
"dyn.agk8y4x44ry",
"CorePasteboardFlavorType 0x4D4F5A6D"
)


グラブも TIFFとPICT。
グラブ
(
"public.tiff",
"NeXT TIFF v4.0 pasteboard type",
"com.apple.pict",
"Apple PICT pasteboard type"
)



- - - - -
概ね TIFFとPICTを用意すればよさそうだ。次回以降、選択範囲のコピーを実装していく。

2008年6月1日日曜日

RubberBand(その36)拡大縮小時にボタンを隠す

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

RubberBandを拡大縮小しているとケースによってはボタンの位置があちこち動いてみづらい。移動中はボタンを隠すようにしよう。

これは簡単でドラッグ開始時に ThinButtonBar (NSView)を非表示すれば良い。

[_button_bar setHidden:YES];




ドラッグが終わったら再び表示すれば良い。

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月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月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に関するおかしな挙動はなくなった。