EnclosureViewに選択状態を持たせる
EnclosureViewに選択されているOscillatorViewを表すインスタンス変数と、それを切替えるメソッドを追加します。
dirtyフラグは追加したものの不要になってしまいました。とりあえずコメントアウトしておきます。
//
// EnclosureView.h
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class OscillatorView;
@interface EnclosureView : NSView
{
IBOutlet NSView *xView, *yView, *lissajousView;
int whiteSpace,viewMargin;
//BOOL dirty;
OscillatorView *selectedView;
}
- (void)selectOscillator:(OscillatorView *)sender;
@end
selectedViewをxViewに初期化します。xViewはアウトレットなので、これが有効になるawakeFromNibで初期化します。
selectOscillatorメソッドを実装します。このメソッドはOscillatorViewが自らを引数にして呼び出す事を想定しています。
選択状態が変わらない場合は何もせずにリターンします。
変わる場合は今まで選択されていたビューを非選択状態にして、引数のビューを選択状態にし、インスタンス変数を更新します。
//
// EnclosureView.m
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import "EnclosureView.h"
#import "OscillatorView.h"
@interface EnclosureView(Private)
- (void)frameDidChange:(NSNotification *)aNotification;
@end
@implementation EnclosureView
#pragma mark -
#pragma mark Initializer and Deallocator
- (id)initWithFrame:(NSRect)frame
{
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
self = [super initWithFrame:frame];
if(self)
{
whiteSpace = 4;
viewMargin = 8;
//dirty = NO;
[nc addObserver:self
selector:@selector(frameDidChange:)
name:NSViewFrameDidChangeNotification
object:self];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)awakeFromNib
{
selectedView = xView;
[self frameDidChange:nil];
}
#pragma mark -
#pragma mark Selection
- (void)selectOscillator:(OscillatorView *)sender
{
if(sender == selectedView) return;
[selectedView setSelected:NO];
[sender setSelected:YES];
selectedView = sender;
}
@end
@implementation EnclosureView(Private)
#pragma mark -
#pragma mark Event Handler
- (void)frameDidChange:(NSNotification *)aNotification
{
int width,height,viewSize,marginWidth,marginHeight;
NSSize sizeOfView;
NSPoint originViewX,originViewY,originViewLissajous;
width = ([self bounds].size.width - whiteSpace*2 - viewMargin)/2;
height = ([self bounds].size.height - whiteSpace*2 - viewMargin)/2;
viewSize = width < height ? width : height;
marginWidth = ([self bounds].size.width - viewSize*2 - viewMargin)/2;
marginHeight = ([self bounds].size.height - viewSize*2 - viewMargin)/2;
originViewY.x = marginWidth;
originViewY.y = marginHeight;
originViewX.x = originViewY.x + viewSize + viewMargin;
originViewX.y = originViewY.y + viewSize + viewMargin;
originViewLissajous.x = originViewX.x;
originViewLissajous.y = originViewY.y;
[xView setFrameOrigin:originViewX];
[yView setFrameOrigin:originViewY];
[lissajousView setFrameOrigin:originViewLissajous];
sizeOfView = NSMakeSize(viewSize,viewSize);
[xView setFrameSize:sizeOfView];
[yView setFrameSize:sizeOfView];
[lissajousView setFrameSize:sizeOfView];
}
- (void)drawRect:(NSRect)rect
{
}
@end
マウスクリックで選択を切替える
これでクリックされて選択された発振器のOscillatorViewが強調表示される様になりました。あとは発振器のパラメータを設定するビューのバインド先を切替えれば完成です。
//
// OscillatorView.m
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import "OscillatorView.h"
#import "EnclosureView.h"
#define AMPLITUDE_MAX 100
@implementation OscillatorView
#pragma mark -
#pragma mark Methods to be overrided
- (NSString *)keyPath
{
NSString *viewName;
viewName = [[self toolTip] substringWithRange:NSMakeRange(0,1)];
isXView = [viewName isEqualToString:@"x"];
selected = isXView;
return [NSString stringWithFormat:@"selection.%@.bezierPath",viewName];
}
- (float)pathHeight
{
return AMPLITUDE_MAX*2.0;
}
#pragma mark -
#pragma mark Accessor methods
- (BOOL)selected {return selected;}
- (void)setSelected:(BOOL)isSelected
{
if(selected != isSelected)
{
selected = isSelected;
[self setNeedsDisplay:YES];
}
}
#pragma mark -
#pragma mark Event handler
- (NSRect)visibleRect
{
return NSInsetRect([self bounds],-2,-2);
}
- (void)mouseUp:(NSEvent *)theEvent
{
if(!selected)
[(EnclosureView *)[self superview] selectOscillator:self];
}
- (void)drawRect:(NSRect)rect
{
NSRect bounds,frame,savedFrame;
NSBezierPath *borderPath;
[super drawRect:rect];
if(![self inLiveResize])
{
savedFrame = [self frame];
frame = NSInsetRect(savedFrame,-3,-3);
[self setFrame:frame];
bounds = [self bounds];
bounds = NSInsetRect(bounds,1,1);
borderPath = [NSBezierPath bezierPathWithRect:bounds];
[borderPath setLineWidth:2];
if(selected)
[[NSColor blackColor] set];
else
[[NSColor windowBackgroundColor] set];
[borderPath stroke];
[self setFrame:savedFrame];
}
}
@end
バインド先を切替える

最後に発振器のパラメータを入力するビューのバインド先を切替えます。ビューはこれを機会にレイアウトを見直し、スライダーを復活させました。またステッパを追加しました。
レイアウトを右図に示します。
バインド先を切替えるためにEnclosureViewにNSArrayControllerのアウトレットを追加します。そしてバインド先を切り替えるビューのアウトレットも追加します。
ついでなのでxViewとyViewアウトレットのクラス指定をNSViewからOscillatorViewに変更しました。これはコンパイル時の警告を消すためです。
//
// EnclosureView.h
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class OscillatorView;
@interface EnclosureView : NSView
{
IBOutlet OscillatorView *xView, *yView;
IBOutlet NSView *lissajousView;
IBOutlet NSArrayController *controller;
IBOutlet NSTextField *frequencyTextField, *phaseLagTextField;
IBOutlet NSSlider *frequencySlider, *phaseLagSlider;
IBOutlet NSStepper *phaseLagStepper;
int whiteSpace,viewMargin;
//BOOL dirty;
OscillatorView *selectedView;
}
- (void)selectOscillator:(OscillatorView *)sender;
@end
バインディングの解除と再設定
ビューにunbind:メッセージを送って現在のバインディングを解除してから新たなバインディングを設定しています。unbind:メッセージを送らなくても動作はするのですが、メモリリークが心配なのでunbindしてみました。
unbindしないとメモリリークするのかどうかを確かめようとしてXocdeの実行メニューからパフォーマンスツールを使って開始 - Leaksを実行してみましたがInstrumentsがフリーズしてしまいます。Instrumentsを単体で起動するとフリーズしないのですが…。
Instrumentsを単体起動して、unbind有と無でそれぞれLeaksを確認してみましたが、どちらもメモリリークは検出されませんでした。何度バインドし直しても大丈夫だし、unbindする必要もなさそうです。
//
// EnclosureView.m
//
#pragma mark -
#pragma mark Selection
- (NSString *)frequencyKeyPath
{
if(selectedView == xView)
return @"selection.x.frequency";
else
return @"selection.y.frequency";
}
- (NSString *)phaseLagKeyPath
{
if(selectedView == xView)
return @"selection.x.phaseLag";
else
return @"selection.y.phaseLag";
}
- (void)bind:(id)object withKeyPath:(NSString *)keyPath
{
[object unbind:@"value"];
[object bind:@"value" toObject:controller withKeyPath:keyPath options:nil];
}
- (void)selectOscillator:(OscillatorView *)sender
{
if(sender == selectedView) return;
[selectedView setSelected:NO];
[sender setSelected:YES];
selectedView = sender;
[self bind:frequencyTextField withKeyPath:[self frequencyKeyPath]];
[self bind:frequencySlider withKeyPath:[self frequencyKeyPath]];
[self bind:phaseLagTextField withKeyPath:[self phaseLagKeyPath]];
[self bind:phaseLagSlider withKeyPath:[self phaseLagKeyPath]];
[self bind:phaseLagStepper withKeyPath:[self phaseLagKeyPath]];
}

ホーム
前へ