MasterMotifCDを対応させる
まずモデルクラスの対応から説明します。MasterMotifCDクラスの変更点は初期化コードの追加とrandomizeメソッドの追加、そしてアクション名を返すクラスメソッドの追加です。
初期化コードを追加する
ランダマイザで発振器を特定するためにorderを利用しているので、発振器を作った時にorderを設定するコードを追加します。
- ミキサーのorderは利用していないので設定する必要はないのですが、ついでなので設定するコードを追加しておきました。
- パラメータの乱数決定とは無関係ですが、原図の初期状態が斜め45度の線になっているのが気に入らないので、ついでに修正しました。円になる様にphaseLagを設定しています。
//
// MasterMotifCD.m
//
#pragma mark -
#pragma mark Initializer
- (void)createMixer
{
int i;
MixerCD *mixer;
for(i=0;i<2;i++)
{
mixer = [NSEntityDescription insertNewObjectForEntityForName:@"Mixer"
inManagedObjectContext:MOC];
[[self mutableSetValueForKey:@"mixers"] addObject:mixer];
if(i==0)
[self setX:mixer];
else
[self setY:mixer];
}
// ランダマイザの為に発振器のorderをセットする。(ミキサーのorderは使っていないが念のため)
[[self x] setOrder:[NSNumber numberWithInt:0]];
[[self y] setOrder:[NSNumber numberWithInt:1]];
[[[self x] osc1] setOrder:[NSNumber numberWithInt:0]];
[[[self x] osc2] setOrder:[NSNumber numberWithInt:1]];
[[[self x] osc3] setOrder:[NSNumber numberWithInt:2]];
[[[self y] osc1] setOrder:[NSNumber numberWithInt:3]];
[[[self y] osc2] setOrder:[NSNumber numberWithInt:4]];
[[[self y] osc3] setOrder:[NSNumber numberWithInt:5]];
// 初期図形を円にする
[[[self y] osc1] setPhaseLag:[NSNumber numberWithInt:90]];
[[[self y] osc2] setPhaseLag:[NSNumber numberWithInt:90]];
[[[self y] osc3] setPhaseLag:[NSNumber numberWithInt:90]];
// addActionNameがセットされる様にクリア
[UNDO_MANAGER setActionName:@""];
}
【失敗例】アンドゥメニューにAdd Master Motifが現れない!
上のコードの[UNDO_MANAGER setActionName:@""];は後で追加したものです。最初はありませんでした。これを追加しないと原図を追加した時のアンドゥメニューが"Undo Add Master Motif"ではなく"Undo Change Phase Lag"になるという不具合が発生します。
データソースを開発している時に、もちろんこのような事が起こらない事は確認していました。その時は初期図形を円にするコードはなかったので、アンドゥマネージャに何もアクション名が設定されておらず、「アクション名が設定されていなければ"Add Master Motif"をセットする」というロジックがうまく働いていたわけです。その事を忘れて初期図形を円にするコードを追加したので、不具合発生となりました。
そう考えると「アクション名が設定されていなければ"Add Master Motif"をセットする」という方法はあまり良い方法ではありませんね。管理対象オブジェクトがコンテキストに挿入されたとき、それが追加されたのか、ペーストされたのか、ドラッグコピーされたのかを見分ける別の方法があれば良いのですが・・・
randomizeメソッドを追加する
MasterMotifCDのrandomizeメソッドは、各ミキサーにrandomizeメッセージを送ってfullUpdateを実行すればOKです。
//
// MasterMotifCD.m
//
#pragma mark -
#pragma mark Randomizing parameters
- (void)randomize
{
[[self mutableSetValueForKey:@"mixers"] makeObjectsPerformSelector:@selector(randomize)];
[self fullUpdate];
}
アクション名を返す
ランダマイズを実行した時にアンドゥメニューに現れるアクション名を返します。
//
// MasterMotifCD.m
//
#pragma mark -
#pragma mark Action names
+ (NSString *)randomizeActionName {return @"randomizeMasterMotif";}
MixerCDを対応させる
MixerCDの対応はrandomizeメッセージを受けて、同じメッセージを各発振器に送る事だけです。
//
// MixerCD.m
//
#pragma mark -
#pragma mark Randomizing parameters
- (void)randomize
{
[[self mutableSetValueForKey:@"oscillators"] makeObjectsPerformSelector:@selector(randomize)];
}
OscillatorCDを対応させる
アクセサメソッドを使ってパラメータをセットすると、波形の更新作業がいちいち発生してしまって無駄なので、setValue:forKey:action:メソッドを直接使ってセットしています。
//
// OscillatorCD.m
//
#import "MasterMotifRandomizer.h"
#pragma mark -
#pragma mark Randomizing parameters
- (void)randomize
{
MasterMotifRandomizer *randomizer = [MasterMotifRandomizer sharedRandomizer];
[self setValue:[randomizer randomAmplitude:[self order]] forKey:@"amplitude" action:nil];
[self setValue:[randomizer randomFrequency:[self order]] forKey:@"frequency" action:nil];
[self setValue:[randomizer randomPhaseLag] forKey:@"phaseLag" action:nil];
}
RMGTableViewを作る
ランダマイズアクションに応答するビューはテーブルビューが適切です。NVKTableViewは汎用的に使える事を目指しているので、ランダマイズアクションに応答させるのは適切ではありません。そこでNVKTableViewのサブクラスとしてRMGTableViewを作ります。
インターフェイスファイルの実装
インターフェイスファイルを以下に示します。インスタンス変数もメソッドの定義も何もありません。
//
// RMGTableView.h
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "NVKTableView.h"
@interface RMGTableView : NVKTableView
{
}
@end
メニューの検証
メニューのアクションがrandomize:であった場合、選択されているオブジェクトがあればYESを、何も選択されていない場合はNOを返す様にします。アクションがrandomize:ではない場合は、スーパークラスに処理を任せます。
ランダマイズアクション
現在選択されているオブジェクト全てにrandomizeメッセージを送って、アクション名をセットすれば完了です。
//
// RMGTableView.m
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import "RMGTableView.h"
#import "MasterMotifCD.h"
@implementation RMGTableView
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
if([menuItem action] == @selector(randomize:))
return [[arrayController selectedObjects] count] > 0;
else
return [super validateMenuItem:menuItem];
}
#pragma mark -
#pragma mark Action methods
- (IBAction)randomize:(id)sender
{
[[arrayController selectedObjects] makeObjectsPerformSelector:@selector(randomize)];
[self setActionName:[[self targetClass] randomizeActionName]];
}
@end
EnclosureViewでランダマイズボタンを配置する
ボタンのアクションを設定する
ランダマイズボタンをウィンドウ上に追加します。このボタンのrandomize:アクションはファーストレスポンダに接続します。

EnclosureViewにボタンを再配置させる
ウィンドウがリサイズされた時に、このボタンを再配置する必要がありますので、EnclosureViewにコードを追加します。まずアウトレットを追加します。
//
// 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 NSButton *randomizeButton;
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
原図ビューの右側に、原図ビューと同じ大きさでボタンを配置する様にします。これならウィンドウからはみ出す事はありません。
//
// EnclosureView.m
//
- (void)frameDidChange:(NSNotification *)aNotification
{
int width,height,viewSize,marginWidth,marginHeight;
NSSize sizeOfView;
NSSize sizeLineX,sizeLineY,longSizeLineX,longSizeLineY;
NSPoint originLineX,originLineY;
NSPoint originViewX,originViewY,originViewMaster,originRandomize;
NSPoint originViewX1,originViewX2,originViewX3;
NSPoint originViewY1,originViewY2,originViewY3;
NSPoint originLineX1,originLineX2,originLineX3;
NSPoint originLineY1,originLineY2,originLineY3;
// 接続線にアンチエイリアスがかかってぼやけない様に、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];
// Buttonを配置する
originRandomize.x = originViewMaster.x + viewSize + viewMargin;
originRandomize.y = originViewMaster.y;
[randomizeButton setFrameOrigin:originRandomize];
// 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];
[randomizeButton setFrameSize:sizeOfView];
}

ホーム
前へ