2009-05-24波形をドラッグして発振器のパラメータを設定できる様にするOscillatorViewを波形ドラッグに対応させる発振器には振幅(波の高さ)、周波数(波の数)、位相のずれ(波の位置)という三つのパラメータがあります。このパラメータを設定するためにステッパとスライダーを使っていました。スライダーが粗調整、ステッパが微調整を行なうという役割分担です。これでも特に問題はないのですが、より直感的に操作できる方法を思いついたので、それを実現してみる事にしました。波形を直接ドラッグして波の高さや位置を変更するという方法です。操作対象に直接働きかけて結果がその場で現れるので、こちらの方が圧倒的にわかりやすいでしょう。波の高さと位置のどちらを変更するのかは、最初にドラッグする向きが縦方向か、横方向かで区別します。これだと波の数を変更できませんが、わかりやすい方法を思いつかないので、従来通りステッパで設定する事にします。Touchだったら二本指で拡大・縮小する動きを使えば、直感的に操作できるんですけどね。OscillatorViewを波形ドラッグに対応させるまずOscillatorViewの対応から説明します。OscillatorViewクラスの変更点はmouseUp:メソッドの変更とmouseDown:メソッドの追加、そしてmouseDragged:メソッドの追加です。それらの下請けのメソッドも追加しています。マウスカーソルを変更する機能も追加しています。OscillatorViewにインスタンス変数を追加する最初にクリックされた時のマウス座標や振幅、位相のずれを覚えておく必要があるのでインスタンス変数を追加します。縦方向か横方向かを判定した結果を保持する必要があるので、そのためのインスタンス変数も追加します。startPoint最初にクリックされた時のマウス座標を保持します。ウィンドウ座標系の位置です。convertedStartPointstartPointを自分の座標系に変換した値です。startAmplitude最初にクリックされた時の振幅を保持します。startPhaseLag最初にクリックされた時の位相のずれを保持します。directionマウスがどちらの方向にドラッグされたのかを判定した結果を保持します。(void)setSelected:(BOOL)isSelected;@endカーソルの形を変えるOscillatorViewをドラッグする事ができますよ、という事をアピールするため、マウスポインタがOscillatorViewの中に入ってきたらマウスカーソルの形を変える事にします。マウスカーソルの形を変えるにはNSViewのresetCursorRectsメソッドを使います。デベロッパドキュメントには以下の様に書いてあります。rectangles.大体こんな事が書いてあります。サブクラスはresetCursorRectsメソッドの実装を、設定したいカーソル矩形それぞれに対してaddCursorRect:cursor:メソッドを使って設定するようにしないといけない。NSViewではresetCursorRectsメソッドで何もしない。アプリケーションはresetCursorRectsメソッドを直接呼び出してはいけない。カーソル矩形を再設定したければ、代わりにinvalidateCursorRectsForView:メソッドを使う。ここではビュー全体の矩形を指定して、カーソルが開いた手の形になる様に指定しています。以前はマウスボタンが放された時に発振器を切替えていましたが、波形をドラッグできる機能をつけ加えるとなると、マウスボタンが放されてから切替えるのは不自然で、マウスボタンが押された時に切替える方が自然です。そこでEnclosureViewに切替えメッセージを送るコードをmouseUp:メソッドからmouseDown:メソッドに移します。アンドゥのグループ化ドラッグ中に連続的に振幅や位相のずれを変更するわけですが、イベントループをまたぐためアンドゥがグループ化されない問題が発生しました。一回のドラッグで振幅を変更したのに、その途中経過が全部記録されて何度も何度もアンドゥしないと元に戻らないという困った事になります。これは明示的にアンドゥをグルーピングすればよいので、mouseDown:でグループ化開始、mouseUp:でグループ化終了とします。配列コントローラで何も選択されていない時はグループ化開始もしないので、その場合は何もしません。方向判定結果のリセットドラッグ方向が縦か横かを判定した結果をリセットするコードを追加します。endUndoGrouping];}}mouseDown:メソッド発振器を切替えるコードをmouseUp:メソッドからこちらに持ってきました。配列コントローラで何も選択されていない時でもOscillatorViewがクリックされる事はあり得ますが、その場合はパラメータを変更すべき発振器がないわけですから、波形のドラッグに関するコードを実行できません。よってその場合は何もしない様にします。mouseUp:メソッドで説明した様にアンドゥのグルーピングを開始します。それ以降にやっている事はマウス座標を読み取ってインスタンス変数に保存している事と、発振器の振幅、位相のずれを読み取ってインスタンス変数に保存している事です。OscillatorViewは発振器に対するキーパスを保持しているので、それとvalueForKeyPath:メソッドを使って発振器のパラメータを読み取る事ができます。intValue];}}振幅の変動を適用するドラッグの方向を判定して、振幅の変更であると判定されるとapplyAmplitudeChange:direction:メソッドが呼ばれます。引数はマウス座標の変化量(移動量)とドラッグの方向です。振幅はビューの中心から離れる方向にドラッグされたときに値を増やすビューの中心に向かう方向にドラッグされた時に値を減らすとするのが自然であろうと考えました。すると正弦波のピークをドラッグした時に波形がマウスカーソルについてくる感じになります。OscillatorViewはX軸データ用のものとY軸データ用のものがあって、X軸データ用のものは90度回転させて表示しています。よってdirectionが横方向の場合はビューの右半分がクリックされたのか、左半分がクリックされたのかで増減を判定するdirectionが縦方向の場合はビューの上半分がクリックされたのか、下半分がクリックされたのかで増減を判定するとしています。ドラッグされた量と振幅の増減を合わせるために、変化量を操作しています。横方向の場合、ビューの幅で割って振幅の最大値の二倍をかけています。マウス座標の変化量をビューの幅で割ると、マウスカーソルをビューの端から端まで動かした時に1になります。ビューに表示されている波形の端から端までは振幅の最大値の二倍なので、これをかけると一致する事になります。最後に振幅がゼロから振幅の最大値の間に収まる様にクリッピングしてから値を発振器に書き込んで終了です。stringByAppendingString:@".amplitude"]];}位相ずれの変動を適用するドラッグの方向を判定して、位相ずれの変更であると判定されるとapplyPhaseLagChange:メソッドが呼ばれます。引数はマウス座標の変化量(移動量)です。ドラッグされた量と位相ずれの増減を合わせるために、変化量を操作しています。変化量に360をビューの高さで割って周波数をかけた値をかけています。マウス座標の変化量をビューの高さで割ると、マウスカーソルをビューの端から端まで動かした時に1になります。ビューに表示されている波形の端から端までの位相は360の周波数倍なので、これをかけると一致する事になります。最後に位相ずれがゼロから359の間に収まる様にクリッピングしてから値を発振器に書き込んで終了です。stringByAppendingString:@".phaseLag"]];}mouseDragged:メソッドmouseDragged:メソッドではマウス座標を読み取ってその変化量を割り出し、縦方向のドラッグなのか横方向のドラッグなのかを判定して、それに応じて上に用意したメソッドを呼び出しています。縦横の判断をする際、敏感すぎると誤判断するので多少の遊びを持たせています。縦方向にドラッグしようとして最初にわずかに横にぶれてしまった場合に「横」と判断してしまう事を避けようという事です。これは3ピクセル動くまでは判断を保留する事で実現しています。