home link download back number special issue

HMDT - Special Issue / Sketch BP / Sketch に見るドローオブジェクトの選択


- Case Study -

Sketch に見るドローオブジェクトの選択

ドローオブジェクトを選択する

ドローアプリケーションなら、ドローオブジェクトを選択できるはずだよな!マウスクリックとかで選択して、それに対して様々な操作を行うんだ。

では、どうやって選択するか?まずは、仕様を考えてみよう。

  • ドローオブジェクトは、選択することができる
  • ドローオブジェクトは、複数選択することができる
  • マウスでドローオブジェクトをクリックすると、選択できる
  • あるドローオブジェクトを選択した状態で、他のドローオブジェクトをクリックすると、前の選択が外れて、新しいドローオブジェクトを選択する
  • あるドローオブジェクトを選択した状態で、なにもないところをクリックすると、前の選択が外れる
  • シフトキーを押しながらドローオブジェクトをクリックすると、現在の選択に新しいドローオブジェクトを加える
  • シフトキーを押しながらすでに選択されているドローオブジェクトをクリックすると、その選択が外れる

ま、こんなとこか。これだけの仕様を満たす、ドローオブジェクトの選択法を考えてみよう。


Sketch に見るドローオブジェクトの保持

Sketch で選択されたドローオブジェクトを管理しているのは、SKTGraphicView だ。

SKTGraphicView クラスには、選択されたドローオブジェクトを保持するための変数があるんだ。

Sketch/SKTGraphicView.h
@interface SKTGraphicView : NSView {
    ...
    NSMutableArray *_selectedGraphics;

複数選択することができるから、NSMutableArray 型だぜ。ドローオブジェクトを選択すればこの中に追加して、選択が外れれば消していくんだ。


Sketch に見るクリックされたオブジェクトの調べ方

SKTGraphicView をクリックしたら、その場所にどんなドローオブジェクトがあるか、調べる必要があるよね。それをやるのが graphicUnderPoint: だ。

Sketch/SKTGraphicView.h
- (SKTGraphic *)graphicUnderPoint:(NSPoint)point {
    SKTDrawDocument *document = [self drawDocument];
    NSArray *graphics = [document graphics];
    unsigned i, c = [graphics count];
    SKTGraphic *curGraphic = nil;

    for (i=0; i<c; i++) {
        curGraphic = [graphics objectAtIndex:i];
        if ([self mouse:point 
                inRect:[curGraphic drawingBounds]] && 
            [curGraphic hitTest:point 
                isSelected:[self graphicIsSelected:curGraphic]]) {
            break;
        }
    }
    if (i < c) {
        return curGraphic;
    } else {
        return nil;
    }
}

こんな感じで一個ずつ調べていく。クリックしたポイントが、境界の四角の中にあって、ヒットテストで OK が出れば、それがクリックされたことになる。

しかし、この方法だと、ドローオブジェクトが増えたときに時間がかかり過ぎるよな。


Sketch に見るドローオブジェクトの選択

次は、マウスクリックに始まる、ドローオブジェクトの選択の様子を見てみよう。中心になるのは、もちろん、SKTGraphicView だ。

mouseDown:

処理のエントリは mouseDown: メソッドだ。マウスがクリックされると、ここに入るからね。

Sketch/SKTGraphicView.m
- (void)mouseDown:(NSEvent *)theEvent {
    Class theClass = 
        [[SKTToolPaletteController sharedToolPaletteController] 
            currentGraphicClass];

    ...

    if ([theEvent clickCount] > 1) {
        ...
    }
    if (theClass) {
        ...
    } else {
        [self selectAndTrackMouseWithEvent:theEvent];
    }
}

まず最初に、SKTToolPaletteController に、いまツールを選択してるかどうか問い合わせるんだ。何も選択してないなら(返り値が nil なら)、オブジェクトを選択することになるんだ。

次にクリックカウントを調べる。ダブルクリック以上なら別の処理になる。

そこまで調べて、次は selectAndTrackMouseWithEvent: に処理が移るんだ。


selectAndTrackMouseWithEvent:

何もツールを選択してない状態で、ドローオブジェクトを 1 回クリックすると、ここに入る。

Sketch/SKTGraphicView.m
- (void)selectAndTrackMouseWithEvent:(NSEvent *)theEvent {
    NSPoint curPoint;
    SKTGraphic *graphic = nil;
    BOOL isSelected;
    BOOL extending = (([theEvent modifierFlags] & NSShiftKeyMask) ? YES : NO);

    curPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];
    graphic = [self graphicUnderPoint:curPoint];
    isSelected = (graphic ? [self graphicIsSelected:graphic] : NO);

    if (!extending && !isSelected) {
        [self clearSelection];
    }

    if (graphic) {
        // Add or remove this graphic from selection.
        if (extending) {
            if (isSelected) {
                [self deselectGraphic:graphic];
                isSelected = NO;
            } else {
                [self selectGraphic:graphic];
                isSelected = YES;
            }
        } else {
            if (isSelected) {
                int knobHit = [graphic knobUnderPoint:curPoint];
                if (knobHit != NoKnob) {
                    [self trackKnob:knobHit 
                                ofGraphic:graphic withEvent:theEvent];
                    return;
                }
            }
            [self selectGraphic:graphic];
            isSelected = YES;
        }
    } else {
        [self rubberbandSelectWithEvent:theEvent];
        return;
    }

    if (isSelected) {
        [self moveSelectedGraphicsWithEvent:theEvent];
        return;
    }

    // If we got here then there must be nothing else to do.  
    // Just track until mouseUp:.
    while (1) {
        theEvent = [[self window] nextEventMatchingMask:
                        (NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
        if ([theEvent type] == NSLeftMouseUp) {
            break;
        }
    }
}

- シフトキーが押されているかどうか調べる

まず、シフトキーがいっしょに押されているかどうか調べるんだ。

Sketch/SKTGraphicView.m
    ...

    BOOL extending = 
        (([theEvent modifierFlags] & NSShiftKeyMask) ? YES : NO);

    ...

NSEvent の modifierFlags を呼び出して、NSShiftKeyMask と論理和を取ることで分かるぜ。その結果は、extending っていう変数に入れておく。

- クリックされたドローオブジェクトを調べる

次に、クリックした先にドローオブジェクトがあるかどうか調べるよ。

Sketch/SKTGraphicView.m
    ...

    curPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];
    graphic = [self graphicUnderPoint:curPoint];
    isSelected = (graphic ? [self graphicIsSelected:graphic] : NO);

    ...

まず、クリックした場所を調べる。そして、そこにドローオブジェクトがあるかどうかを、graphicUnderPoint: を使って調べるんだ。その後、そのドローオブジェクトがすでに選択されたものかどうかを調べておくんだ。

- ドローオブジェクトを選択する、または選択を外す

いよいよ選択するぞ!

Sketch/SKTGraphicView.m
    ...

    if (!extending && !isSelected) {
        [self clearSelection];
    }

    if (graphic) {
        // Add or remove this graphic from selection.
        if (extending) {
            if (isSelected) {
                [self deselectGraphic:graphic];
                isSelected = NO;
            } else {
                [self selectGraphic:graphic];
                isSelected = YES;
            }
        } else {
            if (isSelected) {
                int knobHit = [graphic knobUnderPoint:curPoint];
                if (knobHit != NoKnob) {
                    [self trackKnob:knobHit 
                                ofGraphic:graphic withEvent:theEvent];
                    return;
                }
            }
            [self selectGraphic:graphic];
            isSelected = YES;
        }
    } else {
        [self rubberbandSelectWithEvent:theEvent];
        return;
    }

    ...

まず、シフトキーを押さずに、いま選択しているドローオブジェクト「以外」のところをクリックした場合。すべての選択を解除するために clearSelection を呼ぶんだ。

でいよいよ次が、なにかドローオブジェクトをクリックした場合。

  • シフトキーを押していて、そのドローオブジェクトがすでに選択されていたら、選択を外す
  • シフトキーを押していて、そのドローオブジェクトが選択されていなかったら、選択する
  • シフトキーは押されてなかったら、選択する

ってとこだ。

- 後始末

最後に後始末をする、と。

Sketch/SKTGraphicView.m
    ...

    if (isSelected) {
        [self moveSelectedGraphicsWithEvent:theEvent];
        return;
    }

    // If we got here then there must be nothing else to do.  
    // Just track until mouseUp:.
    while (1) {
        theEvent = [[self window] nextEventMatchingMask:
                (NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
        if ([theEvent type] == NSLeftMouseUp) {
            break;
        }
    }
}

まだドローオブジェクトが選択されてるなら、今度はドラッグの処理をする。それはまた別のところで解説するよ。

その後は、マウスが放されるまで待つ。nextEventMatchingMask: を呼びながら、ループを回すんだ。これで完了。


Home | Link | Download | Back Number | Speciall Issue

Sketch BP

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

HMDT