- Case Study -
Sketch に見るアンドゥ


■ アンドゥの戦略
Ccooa フレームワークは、アンドゥのための標準的な手法を提供している。それは NSUndoManager を使う方法だ。
基本的なアンドゥの戦略はこうだ。ある操作を行ったら、その操作を「取り消すための操作」を NSUndoManager に登録するんだ。例を挙げると、あるドローオブジェクトの色を白から赤に変えるとするでしょ。そうすると、それを「取り消すための操作」は、そのオブジェクトの色を「白に変えること」になるんだ。この操作(実際にはある引数をもったメソッドの呼び出し)を、NSUndoManager に登録するんだ。
登録するためのメソッドには 2 種類ある。
|
AppKit/NSUndoManager.h
|
- (void)registerUndoWithTarget:(id)target
selector:(SEL)selector
object:(id)anObject;
- (id)prepareWithInvocationTarget:(id)target;
|
registerUndoWithTarget:selector:object: はオブジェクトベース、prepareWithInvocaionTarget: は呼び出しベース、って呼ばれている。前者は、「取り消すための操作」をターゲット、セレクタ、その引数、で登録するんだ。後者は、prepareWithInvocationTarget: の返り値に対して、実際に呼んでやることで登録するんだ。Sketch では、後者を使っている。
■ NSUndoManager のインスタンスを取得する
ここでは、どうやって NSUndoManager のインスタンスを取得するか見てみるよ。
まず 1 つ目の方法が、NSDocument の undoManager メソッドを使う方法。SKTDrawDocument はこれを使っている。
|
AppKit/NSDocument.h
|
- (NSUndoManager *)undoManager;
|
次は、NSResponder の undoManager メソッドを使う方法。View のクラスがこれを使ってるよ。
|
AppKit/NSResponder.h
|
@interface NSResponder(NSUndoSupport)
- (NSUndoManager *)undoManager;
@end
|
そして 3 つ目は、SKTGraphic の undoManager メソッドを使う方法。これは中で、NSDoument の undoManager を呼び出している。
|
Sketch/DocumentModesl.subprj/SKTGraphic.m
|
- (NSUndoManager *)undoManager {
return [[self document] undoManager];
}
|
ま、だいたいこの 3 つだ。
■ Sketch に見るアンドゥ
じゃ、実際に使われている様子を調べてみよう。
◆ SKTDrwaDocument に見るアンドゥ
まずは、SKTDrawDocument のアンドゥだ。
- ドローオブジェクトを挿入、削除したとき
ドローオブジェクトを挿入するには、insertGraphic:atIndex: メソッドを使うんだけど、実際に挿入する前に、挿入するオブジェクトのインデックスと、removeGraphicAtIndex: を、NSUndoManager に登録しておくんだ。
|
Sketch/DocumentModesl.subprj/SKTDrawDocument.m
|
- (void)insertGraphic:(SKTGraphic *)graphic atIndex:(unsigned)index {
[[[self undoManager]
prepareWithInvocationTarget:self] removeGraphicAtIndex:index];
[_graphics insertObject:graphic atIndex:index];
...
}
|
逆にオブジェクトを削除するときは、insertGraphic:atAIndex: メソッドと共に、登録しておく。
|
Sketch/DocumentModesl.subprj/SKTDrawDocument.m
|
- (void)removeGraphicAtIndex:(unsigned)index {
...
[_graphics removeObjectAtIndex:index];
...
[[[self undoManager] prepareWithInvocationTarget:self]
insertGraphic:graphic atIndex:index];
...
}
|
- ドローオブジェクトを移動したとき
ドローオブジェクトの前後を入れ替えるときは、moveGraphic:toIndex: を使うんだ。このメソッドの中でも、NSUndoManager を呼んでおく。前の位置にグラフィックを戻せるようにね。
|
Sketch/DocumentModesl.subprj/SKTDrawDocument.m
|
- (void)moveGraphic:(SKTGraphic *)graphic toIndex:(unsigned)newIndex {
unsigned curIndex = [_graphics indexOfObjectIdenticalTo:graphic];
if (curIndex != newIndex) {
[[[self undoManager] prepareWithInvocationTarget:self]
moveGraphic:graphic toIndex:
((curIndex > newIndex) ? curIndex+1 : curIndex)];
...
}
}
|
- プリント情報を変更したとき
setPrintInfo: メソッドで、プリント情報を変更するんだけど、このときに前のプリント情報を NSUndoManager に登録してるんだ。
|
Sketch/DocumentModesl.subprj/SKTDrawDocument.m
|
- (void)setPrintInfo:(NSPrintInfo *)printInfo {
[[[self undoManager]
prepareWithInvocationTarget:self] setPrintInfo:[self printInfo]];
[super setPrintInfo:printInfo];
[[self undoManager]
setActionName:NSLocalizedStringFromTable(
@"Change Print Info",
@"UndoStrings",
@"Action name for changing print info.")];
...
}
|
printInfo メソッドでプリント情報を取り出して、setPrintInfo: と共に、NSUndoManager に登録してるね。
- 書類を開いたとき
書類を開いたときは、いままでのアンドゥの履歴を消しているんだ。NSUndoManager の removeAllActions を呼んでいる。
|
Sketch/DocumentModesl.subprj/SKTDrawDocument.m
|
- (BOOL)loadDataRepresentation:(NSData *)data ofType:(NSString *)type {
...
[[self undoManager] removeAllActions];
...
}
|
◆ SKTGraphic に見るアンドゥ
続いて SKTGraphic のアンドゥを。SKTGraphic の持つ様々な属性を変更したときに、アンドゥを登録するんだ。
- 境界を変更したとき
境界を変更したときは、前の境界のサイズと setBounds: メソッドを登録しておくんだ。
|
Sketch/DocumentModesl.subprj/SKTGraphic.m
|
- (void)setBounds:(NSRect)bounds {
if (!NSEqualRects(bounds, _bounds)) {
if (!_gFlags.manipulatingBounds) {
...
[[[self undoManager]
prepareWithInvocationTarget:self] setBounds:_bounds];
}
_bounds = bounds;
...
}
}
|
他にも、塗りつぶすか否か、塗りつぶしの色、線を描くか、線の色、線の幅、とかを変更したときに、アンドゥを登録しているんだ。登録は、変更前の値と、変更するためのメソッドとの組だよ。
|