変更内容
EnclosureViewはリサイズされた時にサブビューの大きさと位置を変更します。サブビューの変更点を挙げると、以下の様になります。
・発振器の数が二つから六つに増えた
・ミキサーが二つ増えた
・リサージュビューが原図ビューに変わった
・接続線が追加された
ビューとアウトレットの対応関係は右の図の様になっています。
またサブビューではありませんがamplitudeのコントロールが増えています。こちらはサイズ調整ではなく、バインディングの切替えに関わる要素です。また周波数だけステッパがないのも統一感に欠けるので、追加しました。
//
// EnclosureView.h
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class OscillatorView, BezierPathView;
@interface EnclosureView : NSView
{
IBOutlet OscillatorView *x1View,*x2View,*x3View;
IBOutlet OscillatorView *y1View,*y2View,*y3View;
IBOutlet BezierPathView *viewX,*viewY,*viewMaster;
IBOutlet NSView *lineX,*lineY;
IBOutlet NSView *lineX1,*lineX2,*lineX3;
IBOutlet NSView *lineY1,*lineY2,*lineY3;
IBOutlet NSArrayController *controller;
IBOutlet NSTextField *amplitudeTextField, *frequencyTextField, *phaseLagTextField;
IBOutlet NSSlider *amplitudeSlider, *frequencySlider, *phaseLagSlider;
IBOutlet NSStepper *amplitudeStepper, *frequencyStepper, *phaseLagStepper;
int whiteSpace,viewMargin,lineLength;
OscillatorView *selectedView;
}
- (void)selectOscillator:(OscillatorView *)sender;
@end
初期化メソッド
初期化メソッドでは接続線の長さを規定するインスタンス変数lineLengthの設定を追加しています。
awakeFromNibではlineLengthを使って、短い接続線の長さを設定しています。短い接続線は長さが固定なので、はじめに一回設定するだけで済みます。
//
// EnclosureView.m
//
#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;
lineLength = 10;
[nc addObserver:self
selector:@selector(frameDidChange:)
name:NSViewFrameDidChangeNotification
object:self];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)awakeFromNib
{
NSSize sizeLineX = NSMakeSize(1,lineLength);
NSSize sizeLineY = NSMakeSize(lineLength,1);
selectedView = nil;
[self selectOscillator:x1View];
[lineX1 setFrameSize:sizeLineX];
[lineX3 setFrameSize:sizeLineX];
[lineY1 setFrameSize:sizeLineY];
[lineY2 setFrameSize:sizeLineY];
[self frameDidChange:nil];
[viewMaster setKeyPath:@"selection" isXView:NO];
[viewX setKeyPath:@"selection.x" isXView:YES];
[viewY setKeyPath:@"selection.y" isXView:NO];
[x1View setKeyPath:@"selection.x.osc1" isXView:YES];
[x2View setKeyPath:@"selection.x.osc2" isXView:YES];
[x3View setKeyPath:@"selection.x.osc3" isXView:YES];
[y1View setKeyPath:@"selection.y.osc1" isXView:NO];
[y2View setKeyPath:@"selection.y.osc2" isXView:NO];
[y3View setKeyPath:@"selection.y.osc3" isXView:NO];
}
サブビューをリサイズして再配置する
初期化メソッドで通知を受け取る設定を済ませましたので、EnclosureViewがリサイズされるとframeDidChange:メソッドが呼ばれます。ここでサブビューをリサイズして再配置を行ないます。
各ビューを配置する際のパラメータを右の図のように決めます。whiteSpaceは周囲に対する余白(A)、viewMarginはビュー同士の間隔(B)、lineLengthは接続線の長さ(C)、viewSizeは波形を表示するビューの一辺の長さです。
まずこのメソッド内で使う変数を宣言します。
//
// EnclosureView.m
//
@implementation EnclosureView(Private)
#pragma mark -
#pragma mark Event Handler
- (void)frameDidChange:(NSNotification *)aNotification
{
int width,height,viewSize,marginWidth,marginHeight;
NSSize sizeOfView;
NSSize sizeLineX,sizeLineY,longSizeLineX,longSizeLineY;
NSPoint originLineX,originLineY;
NSPoint originViewX,originViewY,originViewMaster;
NSPoint originViewX1,originViewX2,originViewX3;
NSPoint originViewY1,originViewY2,originViewY3;
NSPoint originLineX1,originLineX2,originLineX3;
NSPoint originLineY1,originLineY2,originLineY3;
ビューのサイズを計算する
右の説明図の(D).viewSizeを計算で求めます。widthとheightはそれぞれ許される幅と高さで、どちらか小さい方がviewSizeとなります。
widthとheightの計算方法は右の説明図を見ればわかると思います。幅方向には(A)が二個、(B)が三個、(C)が二個、(D)が四個入っているので、EnclosureViewの横幅からA×2, B×3, C×2をそれぞれ引いて4で割ればDが求まります。ソースコードでは8で割って二倍していますが、これはwidthを偶数にするための工夫です。こうしないと接続線が表示される時にアンチエイリアスがかかってぼやけた表示になってしまいます。
viewSizeが決まったら全体がセンタリングされる様に実際の余白を決定します。marginWidthとmarginHeightがy3Viewの原点座標となります。
// 接続線にアンチエイリアスがかかってぼやけない様に、widthとheightを偶数にする
width = ([self bounds].size.width - whiteSpace*2 - viewMargin*3 - lineLength*2)/8;
width *= 2;
height = ([self bounds].size.height - whiteSpace*2 - viewMargin*2 - lineLength*2)/6;
height *= 2;
viewSize = width < height ? width : height;
marginWidth = ([self bounds].size.width - viewSize*4 - viewMargin*3 - lineLength*2)/2;
marginHeight = ([self bounds].size.height - viewSize*3 - viewMargin*2 - lineLength*2)/2;
各ビューを配置する
あとは計算で求めた座標に各ビューを配置していくだけです。
// Y軸のOscillatorViewを配置する
originViewY1.x = originViewY2.x = originViewY3.x = marginWidth;
originViewY3.y = marginHeight;
originViewY2.y = originViewY3.y + viewSize + viewMargin;
originViewY1.y = originViewY2.y + viewSize + viewMargin;
[y1View setFrameOrigin:originViewY1];
[y2View setFrameOrigin:originViewY2];
[y3View setFrameOrigin:originViewY3];
// Y軸のOscillatorViewにつながる接続線を配置する
originLineY1.x = originLineY2.x = originLineY3.x = originViewY1.x + viewSize + viewMargin;
originLineY1.y = originViewY1.y + viewSize*0.5;
originLineY2.y = originViewY2.y + viewSize*0.5;
originLineY3.y = originViewY3.y + viewSize*0.5;
[lineY1 setFrameOrigin:originLineY1];
[lineY2 setFrameOrigin:originLineY2];
[lineY3 setFrameOrigin:originLineY3];
// 一番下の接続線を長くする
longSizeLineY = NSMakeSize(lineLength*2,1);
[lineY3 setFrameSize:longSizeLineY];
// Y軸の縦の接続線を配置する
originLineY.x = originLineY3.x + lineLength;
originLineY.y = originLineY3.y;
sizeLineY = NSMakeSize(1,viewMargin*2+viewSize*2);
[lineY setFrameOrigin:originLineY];
[lineY setFrameSize:sizeLineY];
// Y軸のMixerViewを配置する
originViewY.x = originLineY3.x + longSizeLineY.width;
originViewY.y = originViewY3.y;
[viewY setFrameOrigin:originViewY];
// MasterMotifViewを配置する
originViewMaster.x = originViewY.x + viewSize + viewMargin;
originViewMaster.y = originViewY.y;
[viewMaster setFrameOrigin:originViewMaster];
// X軸のMixerViewを配置する
originViewX.x = originViewMaster.x;
originViewX.y = originViewMaster.y + viewSize + viewMargin;
[viewX setFrameOrigin:originViewX];
// X軸の横の接続線を配置する
originLineX.x = originViewX.x - viewSize*0.5 - viewMargin;
originLineX.y = originViewX.y + viewSize + lineLength;
[lineX setFrameOrigin:originLineX];
sizeLineX = NSMakeSize(viewMargin*2+viewSize*2,1);
[lineX setFrameSize:sizeLineX];
// X軸の縦の接続線を配置する
originLineX1.x = originLineX.x;
originLineX2.x = originLineX1.x + viewSize + viewMargin;
originLineX3.x = originLineX2.x + viewSize + viewMargin;
originLineX1.y = originLineX3.y = originLineX.y;
originLineX2.y = originLineX.y - lineLength;
[lineX1 setFrameOrigin:originLineX1];
[lineX2 setFrameOrigin:originLineX2];
[lineX3 setFrameOrigin:originLineX3];
// 真ん中の接続線を長くする
longSizeLineX = NSMakeSize(1,lineLength*2);
[lineX2 setFrameSize:longSizeLineX];
// X軸のOscillatorViewを配置する
originViewX1.x = originViewX.x - viewSize - viewMargin;
originViewX2.x = originViewX.x;
originViewX3.x = originViewX.x + viewSize + viewMargin;
originViewX1.y = originViewX2.y = originViewX3.y = originLineX.y + lineLength + viewMargin;
[x1View setFrameOrigin:originViewX1];
[x2View setFrameOrigin:originViewX2];
[x3View setFrameOrigin:originViewX3];
// サイズをセットする
sizeOfView = NSMakeSize(viewSize,viewSize);
[viewMaster setFrameSize:sizeOfView];
[viewX setFrameSize:sizeOfView];
[viewY setFrameSize:sizeOfView];
[x1View setFrameSize:sizeOfView];
[x2View setFrameSize:sizeOfView];
[x3View setFrameSize:sizeOfView];
[y1View setFrameSize:sizeOfView];
[y2View setFrameSize:sizeOfView];
[y3View setFrameSize:sizeOfView];
}
@end
OscillatorViewの選択に伴うバインディングの切替え
まずamplitudeKeyPathメソッドを追加します。第三のプロジェクトでは選択されているビューを見てキーパスを決めていましたが、数が二個から六個に増えたのでそれではコードが長くなりすぎます。そのためにOscillatorViewにoscillatorKeyPathメソッドを用意したので、これを使ってキーパスを生成する様にします。これはfrequencyKeyPathとphaseLagKeyPathでも同じですので、同じ変更を加えます。
細かい話ですが、原図が何も選択されていないとき、あるいは複数選択されている時にテキストフィールドにデフォルトの長いプレイスホルダー文字列が表示されてはみ出しているのが気になります。そこで、はみ出さないような短い文字列をプレイスホルダーとして設定する事にします。これは辞書に指定されたキーで値を書き込み、bind:toObject:withKeyPath:options:メソッドのoptions引数としてその辞書を渡す事で実現できます。
無選択時と複数選択時のプレイスホルダー文字列を設定していますので、ソースコードを参照して下さい。
selectOscillatorメソッドでは振幅を設定するビューと周波数のステッパの設定を追加しています。
//
// EnclosureView.m
//
#pragma mark -
#pragma mark Selection
- (NSString *)amplitudeKeyPath
{
return [[selectedView oscillatorKeyPath] stringByAppendingString:@".amplitude"];
}
- (NSString *)frequencyKeyPath
{
return [[selectedView oscillatorKeyPath] stringByAppendingString:@".frequency"];
}
- (NSString *)phaseLagKeyPath
{
return [[selectedView oscillatorKeyPath] stringByAppendingString:@".phaseLag"];
}
- (void)bind:(id)object withKeyPath:(NSString *)keyPath
{
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
@"-",NSMultipleValuesPlaceholderBindingOption,
@"-",NSNoSelectionPlaceholderBindingOption,
nil];
[object bind:@"value" toObject:controller withKeyPath:keyPath options:options];
}
- (void)selectOscillator:(OscillatorView *)sender
{
if(sender == selectedView) return;
[selectedView setSelected:NO];
[sender setSelected:YES];
selectedView = sender;
[self bind:amplitudeTextField withKeyPath:[self amplitudeKeyPath]];
[self bind:amplitudeSlider withKeyPath:[self amplitudeKeyPath]];
[self bind:amplitudeStepper withKeyPath:[self amplitudeKeyPath]];
[self bind:frequencyTextField withKeyPath:[self frequencyKeyPath]];
[self bind:frequencySlider withKeyPath:[self frequencyKeyPath]];
[self bind:frequencyStepper withKeyPath:[self frequencyKeyPath]];
[self bind:phaseLagTextField withKeyPath:[self phaseLagKeyPath]];
[self bind:phaseLagSlider withKeyPath:[self phaseLagKeyPath]];
[self bind:phaseLagStepper withKeyPath:[self phaseLagKeyPath]];
}

ホーム
前へ