BezierPathViewにマーカ表示機能を追加する
発振器、ミキサー、原図の全てにマーカを表示するので、マーカ表示機能を追加するのはBezierPathViewです。まずやる事をリストアップしてみましょう。
- マーカを表示するかどうか設定できる様にする
- マーカの位置を設定できる様にする
- マーカを表示する
1と2を実現するためにインスタンス変数を追加します。
//
// BezierPathView.h
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface BezierPathView : NSView
{
IBOutlet NSArrayController *controller;
BOOL isXView, multipleSelection, noSelection, showMarker;
int markerPosition;
NSBezierPath *dataPath, *displayPath;
}
- (NSBezierPath *)dataPath;
- (void)setDataPath:(NSBezierPath *)newPath;
- (void)setKeyPath:(NSString *)keyPath isXView:(BOOL)isX;
@end
初期化メソッド
初期化メソッドでは追加したインスタンス変数の初期値を設定します。
awakeFromNibではshowMarkerのバインディングを設定します。ドキュメントごとに設定する必要はなく、アプリケーションで共通の設定として充分でしょう。そこでユーザデフォルトとバインディングする事にしました。
- ユーザデフォルトについては後で詳しく説明します。
//
// 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;
showMarker = NO;
markerPosition = 1;
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:@"showMarker"
toObject:[NSUserDefaultsController sharedUserDefaultsController]
withKeyPath:@"values.showMarker"
options:nil];
[self bind:@"selectionIndexes"
toObject:controller
withKeyPath:@"selectionIndexes"
options:nil];
}
マーカを表示する
displayMarkerメソッドでは、まずdisplayPathから現在のマーカ位置に対応する座標を読み出します。その座標を中心として円のパスを作り、それを半透明の赤で塗りつぶしてマーカとします。
//
// BezierPathView.m
//
- (void)displayMarker
{
int markerSize = 6;
NSPoint p;
NSRect markerRect;
NSBezierPath *markerPath;
[displayPath elementAtIndex:markerPosition associatedPoints:&p];
markerRect = NSMakeRect(p.x-markerSize/2.0,p.y-markerSize/2.0,markerSize,markerSize);
markerPath = [NSBezierPath bezierPathWithOvalInRect:markerRect];
[[[NSColor redColor] colorWithAlphaComponent:0.5] set];
[markerPath fill];
}
- (void)drawRect:(NSRect)rect
{
[[NSBezierPath bezierPathWithRect:[self bounds]] stroke];
if(![self inLiveResize])
if(multipleSelection || noSelection)
[self showMessage];
else
{
[displayPath stroke];
if(showMarker) [self displayMarker];
}
}
アクセサメソッドを追加する
最後に1と2を実現できる様に、アクセサメソッドを追加してBezierPathViewとしては完了です。値をセットした後に再描画しているだけで、特に変わった事をしているわけではありません。
//
// BezierPathView.m
//
- (BOOL)showMarker {return showMarker;}
- (void)setShowMarker:(BOOL)isShowMarker
{
showMarker = isShowMarker;
[self setNeedsDisplay:YES];
}
- (int)markerPosition {return markerPosition;}
- (void)setMarkerPosition:(int)newPosition
{
markerPosition = newPosition;
[self setNeedsDisplay:YES];
}
RMGDocumentをコントローラとする
markerPositionをファイルに保存したいのならCoreDataを使うべきですが、保存する必要は特にありません。そこでRMGDocumentをコントローラとして使う事にします。RMGDocumentならInterface BuilderでFile's Ownerとしてバインディングを設定できます。
またマーカの最大値は原図のresolutionに依存しますので、スライダーの最大値をresolutionが変わった時に適切に設定する必要があります。RMGDocumentがpositionMaxという値を提供する事で、これを実現します。
インスタンス変数とアウトレットを追加する
前述した機能を実現するために、markerPositionとpositionMaxというインスタンス変数を追加します。また原図のresolutionを監視するためにバインディングを設定するので、配列コントローラのアウトレットを追加します。
//
// RMGDocument.h
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008 . All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class BezierPathView;
@interface RMGDocument : NSPersistentDocument
{
IBOutlet NSArrayController *arrayController;
int markerPosition, positionMax;
}
@end
バインディングを設定し、アクセサメソッドを追加する
原図のresolutionを監視するために、windowControllerDidLoadNibメソッドでバインディングを設定します。resolutionのSetterではpositionMaxをresolution-1に設定します。例えばresolutionが200の場合はmarkerを0から199に設定できる様にするわけです。
resolutionを下げた場合、markerPositionがpositionMaxを超える場合があり得ます。その場合はmarkerPositionをpositionMaxに設定します。
//
// RMGDocument.m
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008 . All rights reserved.
//
#import "RMGDocument.h"
@implementation RMGDocument
- (id)init
{
self = [super init];
if(self != nil)
{
}
return self;
}
- (NSString *)windowNibName
{
return @"RMGDocument";
}
- (void)windowControllerDidLoadNib:(NSWindowController *)windowController
{
[super windowControllerDidLoadNib:windowController];
[self bind:@"resolution" toObject:arrayController withKeyPath:@"selection.resolution" options:nil];
}
#pragma mark -
#pragma mark Accessor methods
- (int)markerPosition {return markerPosition;}
- (void)setMarkerPosition:(int)newPosition {markerPosition = newPosition;}
- (int)positionMax {return positionMax;}
- (void)setPositionMax:(int)newMax {positionMax = newMax;}
- (int)resolution {return nil;}
- (void)setResolution:(int)newValue
{
if(newValue == 0) return; // 起動時ゼロが渡される対策
[self setPositionMax:newValue-1];
if(markerPosition > positionMax)
[self setMarkerPosition:positionMax];
}
@end
BezierPathViewをRMGDocumentにバインディングする
BezierPathViewのバインディングを設定します。設定するのは各BezierPathViewのアウトレットを持つEnclosureViewが適任です。まずRMGDocumentのアウトレットを追加します。
//
// EnclosureView.h
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class OscillatorView, BezierPathView, RMGDocument;
@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;
IBOutlet RMGDocument *document;
int whiteSpace,viewMargin,lineLength;
OscillatorView *selectedView;
}
- (void)selectOscillator:(OscillatorView *)sender;
@end
awakeFromNibでバインディングを設定します。
//
// EnclosureView.m
//
- (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];
NSArray *views = [NSArray arrayWithObjects:
viewMaster, viewX, viewY, x1View, x2View, x3View, y1View, y2View, y3View, nil];
NSEnumerator *enumerator = [views objectEnumerator];
NSView *view;
while(view = [enumerator nextObject])
[view bind:@"markerPosition" toObject:document withKeyPath:@"markerPosition" options:nil];
}
Interface Builder上の設定
以上の様に色々と用意したので、Interface Builderで設定ができる様になります。
まずshow markerチェックボックスのバインディング設定ですが、これはユーザデフォルトコントローラとバインディングする様に設定します。
次はマーカ位置を設定するスライダーの設定です。最大値をFile's Owner (=RMGDocument)のpositionMaxとバインディングします。
スライダーの値はFile's Owner のmarkerPositionとバインディングします。
スライダーのEnabledをshowMarkerとバインディングして、showMarkerがチェックされていない時はスライダーを無効にしておいた方が親切でしょう。

ホーム
前へ