home link download back number special issue

HMDT - Special Issue / Sketch BP / Sketch に見るアンドゥ


- 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;
        ...
    }
}

他にも、塗りつぶすか否か、塗りつぶしの色、線を描くか、線の色、線の幅、とかを変更したときに、アンドゥを登録しているんだ。登録は、変更前の値と、変更するためのメソッドとの組だよ。


Home | Link | Download | Back Number | Speciall Issue

Sketch BP

mailto: mkino@xd5.so-net.ne.jp

HMDT