BezierPathViewを作る
ファイルメニューから「新規ファイル…」を実行します。現れた新規ファイルアシスタントが示すリストからObjective-C NSView subclassを選び、次に進みます。

ファイル名をBezierPathViewとします。

するとBezierPathView.hとBezierPathView.mができ上がります。とりあえずOscillatorViewの中身を全部移してしまいましょう。するとこうなります。
//
// BezierPathView.h
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface BezierPathView : NSView
{
IBOutlet NSArrayController *controller;
NSBezierPath *dataPath, *displayPath;
}
- (NSBezierPath *)dataPath;
- (void)setDataPath:(NSBezierPath *)newPath;
@end
//
// BezierPathView.m
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import "BezierPathView.h"
@implementation BezierPathView
#pragma mark -
#pragma mark Initializer and Deallocator
- (id)initWithFrame:(NSRect)frame
{
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
self = [super initWithFrame:frame];
if(self)
{
dataPath = displayPath = nil;
[nc addObserver:self
selector:@selector(frameDidChange:)
name:NSViewFrameDidChangeNotification
object:self];
}
return self;
}
- (void)dealloc
{
[dataPath release];
[displayPath release];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)awakeFromNib
{
[self bind:@"dataPath"
toObject:controller
withKeyPath:@"selection.waveform"
options:nil];
}
#pragma mark -
#pragma mark Path
- (void)updateDisplayPath
{
NSAffineTransform *at = [NSAffineTransform transform];
[displayPath release];
displayPath = [dataPath copy];
// 変換操作を逆順で登録する
// ベジエパスの中心をフレームの中心に移動
[at translateXBy:[self bounds].size.width/2
yBy:[self bounds].size.height/2];
// サイズをフィットさせる
[at scaleXBy:([self bounds].size.width-5)/[displayPath bounds].size.width
yBy:([self bounds].size.height-5)/(AMPLITUDE_MAX*2.0)];
// ベジエパスの中心を原点に移動
[at translateXBy:-NSMidX([displayPath bounds])
yBy:-NSMidY([displayPath bounds])];
[displayPath transformUsingAffineTransform:at];
[self setNeedsDisplay:YES];
}
#pragma mark -
#pragma mark Event Handler
- (void)drawRect:(NSRect)rect
{
[[NSBezierPath bezierPathWithRect:[self bounds]] stroke];
if(![self inLiveResize])
[displayPath stroke];
}
- (void)viewDidEndLiveResize
{
[self updateDisplayPath];
[super viewDidEndLiveResize];
}
- (void)frameDidChange:(NSNotification *)aNotification
{
if(![self inLiveResize])
[self updateDisplayPath];
}
#pragma mark -
#pragma mark Accessor Methods
- (NSBezierPath *)dataPath {return dataPath;}
- (void)setDataPath:(NSBezierPath *)newPath
{
[newPath retain];
[dataPath release];
dataPath = newPath;
[self updateDisplayPath];
}
@end
サブクラスで変更したい部分はどこか
まずバインディングを設定する際のキーパスが異なる筈です。LissajousViewのキーパスが"selection.bezierPath"であるのに対してOscillatorViewのキーパスは"selection.x.bezierPath"あるいは"selection.y.bezierPath"であるはずです。
次に異なる点はパスの表示方法です。OscillatorViewの場合、最大振幅が100であるとして大きさを調整しています。LissajousViewはパスを自分の大きさにフィットさせて表示します。
その点を考慮すると以下の様に変更するのがよさそうです。
//
// BezierPathView.m
//
#pragma mark -
#pragma mark Methods to be overrided
- (NSString *)keyPath
{
return nil;
}
- (float)pathHeight
{
return [displayPath bounds].size.height;
}
#pragma mark -
#pragma mark Initializer and Deallocator
- (void)awakeFromNib
{
[self bind:@"dataPath"
toObject:controller
withKeyPath:[self keyPath]
options:nil];
}
#pragma mark -
#pragma mark Path
- (void)updateDisplayPath
{
NSAffineTransform *at = [NSAffineTransform transform];
[displayPath release];
displayPath = [dataPath copy];
// 変換操作を逆順で登録する
// ベジエパスの中心をフレームの中心に移動
[at translateXBy:[self bounds].size.width/2
yBy:[self bounds].size.height/2];
// サイズをフィットさせる
[at scaleXBy:([self bounds].size.width-5)/[displayPath bounds].size.width
yBy:([self bounds].size.height-5)/[self pathHeight]];
// ベジエパスの中心を原点に移動
[at translateXBy:-NSMidX([displayPath bounds])
yBy:-NSMidY([displayPath bounds])];
[displayPath transformUsingAffineTransform:at];
[self setNeedsDisplay:YES];
}
X軸のOscillatorViewで波形を横向きに表示させる
X軸の発振器出力は波形を横向きに変化させるのに使われるので、横向きに表示された方が自然です。そこでX軸のビューであるかどうかを表すフラグを追加します。
//
// BezierPathView.h
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface BezierPathView : NSView
{
IBOutlet NSArrayController *controller;
BOOL isXView;
NSBezierPath *dataPath, *displayPath;
}
- (NSBezierPath *)dataPath;
- (void)setDataPath:(NSBezierPath *)newPath;
@end
イニシャライザでフラグをNOに初期化します。サブクラスは必要であればこのフラグをYESに初期化します。
updateDisplayPathメソッドで、フラグに応じてパスを回転させます。
//
// BezierPathView.m
//
#pragma mark -
#pragma mark Initializer and Deallocator
- (id)initWithFrame:(NSRect)frame
{
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
self = [super initWithFrame:frame];
if(self)
{
isXView = NO;
dataPath = displayPath = nil;
[nc addObserver:self
selector:@selector(frameDidChange:)
name:NSViewFrameDidChangeNotification
object:self];
}
return self;
}
#pragma mark -
#pragma mark Path
- (void)updateDisplayPath
{
NSAffineTransform *at = [NSAffineTransform transform];
[displayPath release];
displayPath = [dataPath copy];
// 変換操作を逆順で登録する
// ベジエパスの中心をフレームの中心に移動
[at translateXBy:[self bounds].size.width/2
yBy:[self bounds].size.height/2];
// 必要なら90度回転させる
if(isXView)
[at rotateByDegrees:-90];
// サイズをフィットさせる
[at scaleXBy:([self bounds].size.width-5)/[displayPath bounds].size.width
yBy:([self bounds].size.height-5)/[self pathHeight]];
// ベジエパスの中心を原点に移動
[at translateXBy:-NSMidX([displayPath bounds])
yBy:-NSMidY([displayPath bounds])];
[displayPath transformUsingAffineTransform:at];
[self setNeedsDisplay:YES];
}

ホーム
前へ