Cocoa右クリックメニュー

今回のお遊び実験

目的

  • NSViewを継承しているクラスで、右クリックした時にポップアップメニューを表示する。
  • NSMenuを少しいじくる

NSViewを継承しているクラスってたくさんあります。
というか、GUIに設置にするものはほとんど(といか全部?)がそうでしょう。
今回はあえてNSButtonでやってみたいと思います。
誰もそんなことしたいと思わないだろうと?
お遊びだからいいのです。


実は実装は簡単です。

+ (NSMenu *)defaultMenu;

をオーバライドすれば、完了です。
その他にも、

- (NSMenu*)menuForEvent:(NSEvent*)event;

をオーバーライドする方法があります。
後者はクリック情報を内部処理で使いたい場合に使います。
例えば、指定した範囲ないでのみポップアップメニューを表示するとか。


今回は、前者を使います。

ではでは、サンプルコードです。

@interface PJPopUpMenuButton : NSButton
@end

@implementation PJPopUpMenuButton
+ (NSMenu*)defaultMenu {
	NSMenu * menu = [[[NSMenu alloc] initWithTitle:@"PJPopUpMenu"] autorelease];
	[menu addItemWithTitle:@"menu1" action:nil keyEquivalent:@""];
	return menu;
}
@end

なんとも簡単ですね。

NSMenuをいじってみる

次はNSMenuをいじってこんなことをしてみます。

これは、NSMenuItemのsetView:メソッドを使えば実現できます。
ソース

+ (NSMenu*)defaultMenu {
	NSMenu * menu = [[[NSMenu alloc] initWithTitle:@"PJPopUpMenu"] autorelease];
	[menu addItemWithTitle:@"menu1"
					action:nil
			 keyEquivalent:@""];
	
	//
	// メニュー内にNSButtonを入れてみる
	//
	NSMenuItem *buttonItem = [[[NSMenuItem alloc] initWithTitle:@"buttonItem" action:nil keyEquivalent:@""] autorelease];
	NSButton *button = [[[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 100, 20)] autorelease];
	{
	[button setTitle:@"buttonItem"];
	[button setBezelStyle:NSTexturedSquareBezelStyle];
	}
	[buttonItem setView:button];
	[menu addItem:buttonItem];
	
	
	//
	// メニュー内にNSSliderを入れてみる
	//
	NSMenuItem *sliderItem = [[[NSMenuItem alloc] initWithTitle:@"sliderItem" action:nil keyEquivalent:@""] autorelease];
	NSSlider *slider = [[[NSSlider alloc] initWithFrame:NSMakeRect(0, 0, 100, 20)] autorelease];
	{
		[slider setMinValue:0.0f];
		[slider setMaxValue:100.0f];
		[slider setFloatValue:50.0f];
	}
	[sliderItem setView:slider];
	[menu addItem:sliderItem];
	return menu;
}

とまぁ、結構遊べるものです。
NSMenuに関しては別記事にて、もう少し色々してみたい思います。

メニューの表示位置を変えてみる

では、最後に、ポップアップメニューの表示位置を変えてみます。
まずポップアップメニューが表示されるフローについて簡単に説明します。
右クリックが検知された際の動作は

  1. - (void)rightMouseDown:(NSEvent*)theEvent
  2. - (NSMenu*)menuForEvent:(NSEvent*)theEvent
  3. + (NSMenu*)defaultMenu

右クリックが検知されると1が呼び出されます。

1のデフォルト動作は、
 2を呼び出し、その返り値がnilでなければ、
 クリックされた場所にメニューを表示する
2のデフォルト動作は、3を呼び出す
3のデフォルト動作はnilを返す

という様になっています。
(2のデフォルト動作がもしかしたら違うかもしれませんが、ここではこれで差し支えないので、こうしておきます。中途半端ですみません)
つまり、表示位置をコントロールするには、1までさかのぼる必要があります。


また、表示される位置についてです。
絵にするとこんな感じになっています。

座標系が左下基準というのに、注意する必要があります。

そして、クリックされた場所の情報は、
rightMouseDown:の引数であるNSEventに含まれています。
こいつをいじくってやれば、表示位置を変更できるわけです。NSEventについては割愛します。

というわけで、コードはこんな感じになります。

- (void)rightMouseDown:(NSEvent *)theEvent {
//
// クリック位置をずらす
//
        NSPoint displayPoint = NSMakePoint([theEvent locationInWindow].x-100, [theEvent locationInWindow].y+100);
//
// 新しいNSEventを作る
// これをもとにポップアップメニューを表示させる
//
	NSEvent * newEvent = [NSEvent mouseEventWithType:[theEvent type]
		location:displayPoint
		modifierFlags:[theEvent modifierFlags]	
		timestamp:[theEvent timestamp]
		windowNumber:[theEvent windowNumber]
		context:[theEvent context]
		eventNumber:[theEvent eventNumber] 
		clickCount:[theEvent clickCount]
		pressure:[theEvent pressure]];
//
// 新しく作ったNSEventを使ってポップアップメニューを表示
// デフォルトでは、theEventを使っているのでしょう
//
	[NSMenu popUpContextMenu:[[self class] defaultMenu] withEvent:newEvent forView:self];
}


さぁ、どんな使い道があるのか・・・
僕にはさっぱりです。