//
//  ECSceneView.m
//  SceneRenderProto
//
//  Created by 二鏡 on 11/11/14.
//  Copyright 2011年 二鏡庵. All rights reserved.
//

#import "ECSceneView.h"
#import "ECLayer_Defaults.h"
#import "ECCompositionLayer.h"
#import "UTIs.h"

extern NSString *udKeepAspect;
extern NSString *udBackgroundAutoLock;
extern NSString *udDrawTextFrame;
extern NSString *udDropCentering;

static const CGFloat cHandleSize = 16.0;

static NSString *oSelectionContext = @"selection";
static NSString *oContentContext = @"content";

static inline CGSize _floorSize(CGSize aSize)
{
    aSize.width = floor(aSize.width);
    aSize.height = floor(aSize.height);
    return aSize;
}

static inline CGPoint _floorPoint(CGPoint aPoint)
{
    aPoint.x = floor(aPoint.x);
    aPoint.y = floor(aPoint.y);
    return aPoint;
}

static inline BOOL _testHandleRectLeft(CGRect bounds, CGPoint point)
{
    CGFloat x = CGRectGetMinX(bounds)+cHandleSize;
    return point.x <= x;
}

static inline BOOL _testHandleRectTop(CGRect bounds, CGPoint point)
{
    CGFloat y = CGRectGetMaxY(bounds)-cHandleSize;
    return point.y >= y;
}

static inline BOOL _testHandleRectRight(CGRect bounds, CGPoint point)
{
    CGFloat x = CGRectGetMaxX(bounds)-cHandleSize;
    return point.x >= x;
}

static inline BOOL _testHandleRectBottom(CGRect bounds, CGPoint point)
{
    CGFloat y = CGRectGetMinY(bounds)+cHandleSize;
    return point.y <= y;
}

static inline BOOL _testHandleRectTopLeft(CGRect bounds, CGPoint point)
{
    CGFloat x = CGRectGetMinX(bounds);
    CGFloat y = CGRectGetMaxY(bounds)-cHandleSize;
    CGRect rect = CGRectMake(x,y,cHandleSize,cHandleSize);
    return CGRectContainsPoint(rect,point);
}

static inline BOOL _testHandleRectTopRight(CGRect bounds, CGPoint point)
{
    CGFloat x = CGRectGetMaxX(bounds)-cHandleSize;
    CGFloat y = CGRectGetMaxY(bounds)-cHandleSize;
    CGRect rect = CGRectMake(x,y,cHandleSize,cHandleSize);
    return CGRectContainsPoint(rect,point);
}

static inline BOOL _testHandleRectBottomRight(CGRect bounds, CGPoint point)
{
    CGFloat x = CGRectGetMaxX(bounds)-cHandleSize;
    CGFloat y = CGRectGetMinY(bounds);
    CGRect rect = CGRectMake(x,y,cHandleSize,cHandleSize);
    return CGRectContainsPoint(rect,point);
}

static inline BOOL _testHandleRectBottomLeft(CGRect bounds, CGPoint point)
{
    CGFloat x = CGRectGetMinX(bounds);
    CGFloat y = CGRectGetMinY(bounds);
    CGRect rect = CGRectMake(x,y,cHandleSize,cHandleSize);
    return CGRectContainsPoint(rect,point);
}

static inline BOOL _floatEQ_p(CGFloat a, CGFloat b)
{
    CGFloat a2 = fabs(a);
    CGFloat b2 = fabs(b);
    
    CGFloat c = MAX(a2,b2);
    CGFloat d = c == 0.0 ? 0.0 : fabs(a-b)/c;
    return d <= __FLT_EPSILON__;
}

@implementation ECSceneView (MouseEdit)

- (NSPoint)convertPointToScene:(NSPoint)aPoint
{
    NSRect bounds = [self bounds];
    NSSize size = composition.scene.size;
    CGFloat dx = floor(NSMidX(bounds)-size.width/2);
    CGFloat dy = floor(NSMidY(bounds)-size.height/2);
    return NSMakePoint(aPoint.x-dx,
                       aPoint.y-dy);
}

- (NSRect)sceneBounds
{
    NSRect bounds = [self bounds];
    NSSize size = composition.scene.size;
    CGFloat dx = floor(NSMidX(bounds)-size.width/2);
    CGFloat dy = floor(NSMidY(bounds)-size.height/2);
    return NSMakeRect(dx,dy, size.width,size.height);
}

- (NSPoint)contentOrigin
{
    NSRect bounds = [self bounds];
    NSSize size = composition.scene.size;
    CGFloat dx = floor(NSMidX(bounds)-size.width/2);
    CGFloat dy = floor(NSMidY(bounds)-size.height/2);
    return NSMakePoint(dx,dy);
}

- (eLayerEditMode)testEditRectInLayer:(ECLayer*)aLayer
                                point:(NSPoint)aPoint
{
    CGPoint point = NSPointToCGPoint(aPoint);
    if([aLayer pointInRect: point] == NO)
        return eLayerNone;
    
    // 16x16 角を優先
    CGRect rect = aLayer.bounds;
    
    if(_testHandleRectTopRight(rect, point))
        return eLayerResizeTopRight;
    if(_testHandleRectTopLeft(rect, point))
        return eLayerResizeTopLeft;
    if(_testHandleRectBottomRight(rect, point))
        return eLayerResizeBottomRight;
    if(_testHandleRectBottomLeft(rect, point))
        return eLayerResizeBottomLeft;
    if(_testHandleRectRight(rect, point))
        return eLayerResizeRight;
    if(_testHandleRectLeft(rect, point))
        return eLayerResizeLeft;
    if(_testHandleRectTop(rect, point))
        return eLayerResizeTop;
    if(_testHandleRectBottom(rect, point))
        return eLayerResizeBottom;
    
    return eLayerMove;
}

- (void)_moveSelectionXBy:(CGFloat)dx
                      yBy:(CGFloat)dy
{
    if(draggingOption == eDragOptionHorizontal)
        dy = 0;
    if(draggingOption == eDragOptionVertical)
        dx = 0;
    CGPoint point = CGPointMake(CGRectGetMinX(layerOldBounds) + dx,
                                CGRectGetMinY(layerOldBounds) + dy);
    
    [composition.selectedLayer setValue: [NSValue valueWithPoint: point]
                                 forKey: @"origin"];
}

- (void)_editSelectionTopRightXBy:(CGFloat)dx
                              yBy:(CGFloat)dy
{
    // 左下固定モード
    CGSize size = layerOldBounds.size;
    if(keepAspect)
    {
        CGFloat ratio = size.width/size.height;
        // 比率を計算して小さい方優先
        CGFloat ratioX = (size.width-dx)/size.width;
        CGFloat ratioY = (size.height-dy)/size.height;
        if(ratioX > ratioY)
        {
            size.width += dx;
            size.height = size.width/ratio;
        }
        else
        {
            size.height  += dy;
            size.width = size.height*ratio;
        }
    }
    else
    {
        size.width  += dx;
        size.height += dy;
    }
    [composition.selectedLayer setValue: [NSValue valueWithSize: _floorSize(size)]
                                 forKey: @"size"];
}

- (void)_editSelectionTopLeftXBy:(CGFloat)dx
                             yBy:(CGFloat)dy
{
    // 右下固定モード
    CGFloat mx = CGRectGetMaxX(layerOldBounds);
    CGSize size = layerOldBounds.size;
    CGPoint origin = layerOldBounds.origin;
    if(keepAspect)
    {
        CGFloat ratio = size.width/size.height;
        // 比率を計算して小さい方優先
        CGFloat ratioX = (size.width-dx)/size.width;
        CGFloat ratioY = (size.height+dy)/size.height;
        if(ratioX < ratioY)
        {
            size.width -= dx;
            size.height = size.width/ratio;
            origin.x = mx-size.width;
        }
        else
        {
            size.height += dy;
            size.width = size.height*ratio;
            origin.x = mx-size.width;
        }
    }
    else
    {
        size.width  -= dx;
        size.height -= dy;
        origin.x += dx;
    }
    [composition.selectedLayer setValue: [NSValue valueWithSize: _floorSize(size)]
                                 forKey: @"size"];
    [composition.selectedLayer setValue: [NSValue valueWithPoint: _floorPoint(origin)]
                                 forKey: @"origin"];
}

- (void)_editSelectionBottomLeftXBy:(CGFloat)dx
                                yBy:(CGFloat)dy
{
    // 右上固定。リサイズ&原点移動
    CGSize size = layerOldBounds.size;
    CGFloat mx = CGRectGetMaxX(layerOldBounds);
    CGFloat my = CGRectGetMaxY(layerOldBounds);
    CGPoint origin = layerOldBounds.origin;
    if(keepAspect)
    {
        CGFloat ratio = size.width/size.height;
        CGFloat ratioX = (size.width-dx)/size.width;
        CGFloat ratioY = (size.height-dy)/size.height;
        if(ratioX < ratioY)
        {
            size.width -= dx;
            size.height = size.width/ratio;
            origin.x = mx-size.width;
            origin.y = my-size.height;
        }
        else
        {
            size.height -= dy;
            size.width = size.height*ratio;
            origin.x = mx-size.width;
            origin.y = my-size.height;
        }
    }
    else
    {
        size.width -= dx;
        size.height -= dy;
        origin.x += dx;
        origin.y += dy;
    }
    [composition.selectedLayer setValue: [NSValue valueWithSize: _floorSize(size)]
                                 forKey: @"size"];
    [composition.selectedLayer setValue: [NSValue valueWithPoint: _floorPoint(origin)]
                                 forKey: @"origin"];
}

- (void)_editSelectionBottomRightXBy:(CGFloat)dx
                                 yBy:(CGFloat)dy
{
    // 左上固定モード
    CGFloat my = CGRectGetMaxY(layerOldBounds);
    CGSize size = layerOldBounds.size;
    CGPoint origin = layerOldBounds.origin;
    if(keepAspect)
    {
        CGFloat ratio = size.width/size.height;
        // 比率を計算して小さい方優先
        CGFloat ratioX = (size.width-dx)/size.width;
        CGFloat ratioY = (size.height-dy)/size.height;
        if(ratioX > ratioY)
        {
            size.width += dx;
            size.height = size.width/ratio;
            origin.y = my-size.height;
        }
        else
        {
            size.height  -= dy;
            size.width = size.height*ratio;
            origin.y = my-size.height;
        }
    }
    else
    {
        size.width  += dx;
        size.height -= dy;
        origin.y += dy;
    }
    [composition.selectedLayer setValue: [NSValue valueWithSize: _floorSize(size)]
                                 forKey: @"size"];
    [composition.selectedLayer setValue: [NSValue valueWithPoint: _floorPoint(origin)]
                                 forKey: @"origin"];
}

- (void)_editSelectionRightXBy:(CGFloat)dx
{
    CGFloat refY = CGRectGetMidY(layerOldBounds);
    CGSize size = layerOldBounds.size;
    CGPoint origin = layerOldBounds.origin;
    
    if(keepAspect)
    {
        CGFloat ratio = size.width/size.height;
        size.width += dx;
        size.height = size.width/ratio;
        origin.y = refY-size.height/2.0;
    }
    else
    {
        size.width += dx;
    }
    [composition.selectedLayer setValue: [NSValue valueWithSize: _floorSize(size)]
                                 forKey: @"size"];
    [composition.selectedLayer setValue: [NSValue valueWithPoint: _floorPoint(origin)]
                                 forKey: @"origin"];
}

- (void)_editSelectionLeftXBy:(CGFloat)dx
{
    CGFloat refY = CGRectGetMidY(layerOldBounds);
    CGSize size = layerOldBounds.size;
    CGPoint origin = layerOldBounds.origin;
    
    if(keepAspect)
    {
        CGFloat ratio = size.width/size.height;
        size.width -= dx;
        size.height = size.width/ratio;
        origin.x += dx;
        origin.y = refY-size.height/2.0;
    }
    else
    {
        size.width -= dx;
        origin.x += dx;
    }
    [composition.selectedLayer setValue: [NSValue valueWithSize: _floorSize(size)]
                                 forKey: @"size"];
    [composition.selectedLayer setValue: [NSValue valueWithPoint: _floorPoint(origin)]
                                 forKey: @"origin"];
}

- (void)_editSelectionTopYBy:(CGFloat)dy
{
    CGFloat refX = CGRectGetMidX(layerOldBounds);
    CGSize size = layerOldBounds.size;
    CGPoint origin = layerOldBounds.origin;
    
    if(keepAspect)
    {
        CGFloat ratio = size.width/size.height;
        size.height += dy;
        size.width = size.height*ratio;
        origin.x = refX - size.width/2.0;
    }
    else
    {
        size.height += dy;
    }
    [composition.selectedLayer setValue: [NSValue valueWithSize: _floorSize(size)]
                                 forKey: @"size"];
    [composition.selectedLayer setValue: [NSValue valueWithPoint: _floorPoint(origin)]
                                 forKey: @"origin"];
}

- (void)_editSelectionBottomYBy:(CGFloat)dy
{
    CGFloat refX = CGRectGetMidX(layerOldBounds);
    CGSize size = layerOldBounds.size;
    CGPoint origin = layerOldBounds.origin;
    
    if(keepAspect)
    {
        CGFloat ratio = size.width/size.height;
        size.height -= dy;
        size.width = size.height*ratio;
        origin.x = refX - size.width/2.0;
        origin.y += dy;
    }
    else
    {
        size.height -= dy;
        origin.y += dy;
    }
    [composition.selectedLayer setValue: [NSValue valueWithSize: _floorSize(size)]
                                 forKey: @"size"];
    [composition.selectedLayer setValue: [NSValue valueWithPoint: _floorPoint(origin)]
                                 forKey: @"origin"];
}
@end


#pragma mark -
@implementation ECSceneView
@synthesize editable, composition, scene;

- (void)_init
{
    id types = [NSArray arrayWithObjects: (id)kUTTypeURL, nil];
    [self registerForDraggedTypes: types];
    id center = [NSNotificationCenter defaultCenter];
    [center addObserver: self
               selector: @selector(userDefaultsDidChange:)
                   name: NSUserDefaultsDidChangeNotification 
                 object: nil];
} 

- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self _init];
    }
    
    return self;
}

- (void)dealloc
{
    [super dealloc];
}

#pragma mark KVO
- (void)bind:(NSString *)binding 
    toObject:(id)observable
 withKeyPath:(NSString *)keyPath
     options:(NSDictionary *)options
{
    
    if([binding isEqualToString: @"selection"])
    {
        [observable addObserver: self
                     forKeyPath: keyPath
                        options: NSKeyValueObservingOptionNew
                        context: oSelectionContext];
        return;
    }
         
    if([binding isEqualToString: @"content"])
    {
        [observable addObserver: self
                     forKeyPath: keyPath
                        options: NSKeyValueObservingOptionInitial
                        context: oContentContext];
        return;
    }

    [super bind:binding toObject:observable withKeyPath:keyPath options:options];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if(context == oContentContext)
    {
        // 本当はキーパスでちゃんと処理すべきかも
        self.scene = composition.scene;
        [self setNeedsDisplay: YES];
        return;
    }
    

    if(context == oSelectionContext)
    {
        ECLayer *target = composition.selectedLayer.target;
        if([target isKindOfClass: [ECRegularLayer class]])
        {
            id font = [(ECRegularLayer*)target font];
            id fm = [NSFontManager sharedFontManager];
            [fm setSelectedFont: font
                     isMultiple: NO];
        }
        [self setNeedsDisplay: YES];
        return;
    }

    [super observeValueForKeyPath: keyPath
                         ofObject: object
                           change: change
                          context: context];
}

#pragma mark Property
- (BOOL)isOpaqure
{
    return YES;
}

- (BOOL)acceptsFirstResponder
{
    return editable;
}

- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
{
    return YES;
}

- (void)resetCursorRects
{
    [[self window] invalidateCursorRectsForView: self];
    ECLayer *selected = composition.selectedLayer.target;
    if(!selected || selected.lock)
        return;
    
    NSRect rect = [selected bounds];
    NSPoint origin = [self contentOrigin];
    rect.origin.x += origin.x;
    rect.origin.y += origin.y;
    [self addCursorRect: rect
                 cursor: [NSCursor openHandCursor]];
}

- (ECSceneComposition*)composition
{
    return composition;
}

- (void)setComposition:(ECSceneComposition*)aComposition
{
    composition = aComposition;
    self.scene = composition.scene;
}

- (void)setScene:(ECScene*)aScene
{
    if(scene == aScene)
        return;
    
    [scene release];
    scene = [aScene retain];
    [self setNeedsDisplay: YES];
}

#pragma mark Drawing
- (void)drawRect:(NSRect)dirtyRect
{
    CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];

    // BG
    [[NSColor grayColor] set];
    NSRectFill(dirtyRect);
    
    // translate to center
    NSRect bounds = [self bounds];
    NSSize size = scene.size;
    CGFloat dx = floor(NSMidX(bounds)-size.width/2);
    CGFloat dy = floor(NSMidY(bounds)-size.height/2);
    
    id trans = [NSAffineTransform transform];
    [trans translateXBy: dx yBy: dy];
    [trans concat];
    
    // draw content
    [NSGraphicsContext saveGraphicsState];    
    NSRect rect = NSZeroRect;
    rect.size = size;

    [NSBezierPath clipRect: rect];
    [scene drawSceneWithContext: context];
    
    if(dropType == eDropTypeTemplate && insideScene)
    {
        // テンプレートのドロップは編みかけを行う
        [[[NSColor grayColor] colorWithAlphaComponent: 0.6] set];
        id bp = [NSBezierPath bezierPathWithRect: rect];
        [bp fill];
        return;
    }

    // restore clip
    [NSGraphicsContext restoreGraphicsState];

    if(dropping)
    {
        if(dropTarget)
        {
            [dropTarget drawFrameWithColor:[NSColor greenColor]];
        }
    }
    else
    {
        // draw frame
        ECLayer *selectedLayer = composition.selectedLayer.target;
        if(selectedLayer)
        {
            id color = selectedLayer.lock ? [NSColor redColor] : [NSColor yellowColor];
            [selectedLayer drawFrameWithColor: color];
            if([[NSUserDefaults standardUserDefaults] boolForKey: udDrawTextFrame] &&
               [selectedLayer isKindOfClass: [ECRegularLayer class]])
                [(ECRegularLayer*)selectedLayer drawTextFrame];
        }
    }
}

#pragma mark Support Operation
- (void)_selectLayer:(ECLayer*)aLayer
{
    [composition select: aLayer];
}

- (void)_deselect
{
    [composition deselect];
}

#pragma mark Mouse Event
- (void)mouseDown:(NSEvent*)theEvent
{
    if(editable == NO)
        return;
    
    ECLayer *topLayer;
    ECLayer *selectedLayer = composition.selectedLayer.target;
    clickPoint = [theEvent locationInWindow];
    keepAspect = [[NSUserDefaults standardUserDefaults] boolForKey: udKeepAspect];
    [composition.undo disableUndoRegistration]; // ドラッグ中はundoをカット
    dragged = NO;
    
    // 基本ポイント
    NSPoint basePoint = [self convertPoint: clickPoint
                                  fromView: nil];
        
    // content相対ポイント
    NSPoint localPoint = [self convertPointToScene: basePoint];
    
    // ** 以下においてCompositionLayerはlayerAtPoint:を常にNOとするので
    // ** マウスイベントの対象外となる

    topLayer = [composition.scene layerAtPoint: localPoint];
    // 現在選択中のレイヤーを優先的に処理
    if(selectedLayer && selectedLayer.lock == NO)
    {
        // 選択レイヤーはマウス領域内か？
        if([selectedLayer pointInRect: localPoint] &&
           topLayer.lock != NO)
        {
            draggingMode = [self testEditRectInLayer: selectedLayer
                                               point: localPoint];
            layerOldBounds = selectedLayer.bounds;
            // レギュラーの場合はtextFrameも退避
            if([selectedLayer isKindOfClass: [ECRegularLayer class]])
                layerOldTextFrame = ((ECRegularLayer*)selectedLayer).textFrame;
            [self resetCursorRects];
            return;
        }
    }
    
    // トップレイヤーを選択して改めて再評価
    [self _selectLayer: topLayer];
    selectedLayer = topLayer;
    
    if(selectedLayer && selectedLayer.lock == NO)
    {
        draggingMode = [self testEditRectInLayer: selectedLayer
                                           point: localPoint];
        layerOldBounds = selectedLayer.bounds;
        if([selectedLayer isKindOfClass: [ECRegularLayer class]])
            layerOldTextFrame = ((ECRegularLayer*)selectedLayer).textFrame;
    }
    else
    {
        draggingMode = eLayerNone;
    }
    [self resetCursorRects];
}

- (void)mouseDragged:(NSEvent*)theEvent
{
    if(editable == NO)
        return ;
    if(draggingMode == eLayerNone || composition.selectedLayer.target == nil)
        return;
    
    dragged = YES;
    NSPoint point = [theEvent locationInWindow];
    CGFloat dx = floor(point.x-clickPoint.x);
    CGFloat dy = floor(point.y-clickPoint.y);
    
    // 初動時にオプションを定める
    if(draggingOption == eDragOptionUndefined)
    {
        if(([theEvent modifierFlags] & NSShiftKeyMask) == 0)
        {
            draggingOption = eDragOptionNone;
        }
        else
        {
            // シフトマスクが付く時は、移動に対して制約を行う
            if(fabs(dx) > fabs(dy))
                draggingOption = eDragOptionHorizontal;
            else
                draggingOption = eDragOptionVertical;
        }
    }
    
    switch(draggingMode)
    {
        case eLayerMove:
            [self _moveSelectionXBy: dx yBy: dy];
            break;
        case eLayerResizeTopRight:
            [self _editSelectionTopRightXBy: dx yBy: dy];
            break;
        case eLayerResizeTopLeft:
            [self _editSelectionTopLeftXBy: dx yBy: dy];
            break;
        case eLayerResizeBottomLeft:
            [self _editSelectionBottomLeftXBy: dx yBy: dy];
            break;
        case eLayerResizeBottomRight:
            [self _editSelectionBottomRightXBy: dx yBy: dy];
            break;
        case eLayerResizeRight:
            [self _editSelectionRightXBy: dx];
            break;
        case eLayerResizeLeft:
            [self _editSelectionLeftXBy: dx];
            break;
        case eLayerResizeTop:
            [self _editSelectionTopYBy: dy];
            break;
        case eLayerResizeBottom:
            [self _editSelectionBottomYBy: dy];
            break;
        default:
            ;
    }
    [self setNeedsDisplay: YES];
    [self resetCursorRects];
}

- (void)mouseUp:(NSEvent*)theEvent
{
    ECLayerProxy *proxy = composition.selectedLayer;
    if(dragged && 
       proxy.target && 
       proxy.target.lock == NO)
    {
        // 編集過程をキングクリムゾンするための処理
        id newSize = [proxy valueForKey: @"size"];
        id newOrigin = [proxy valueForKey: @"origin"];
        id newTextFrame = [proxy valueForKey: @"textFrame"]; // NotApplicableかもしれない！！！
        
        // 一端旧値に戻す
        [proxy setValue: [NSValue valueWithSize: layerOldBounds.size]
                 forKey: @"size"];
        [proxy setValue: [NSValue valueWithPoint: layerOldBounds.origin] 
                 forKey: @"origin"];
        // ** おかしな値が代入されたとしても、undefinedで無視される
        [proxy setValue: [NSValue valueWithRect: layerOldTextFrame]
                 forKey: @"textFrame"];
        // undoを回復
        [composition.undo enableUndoRegistration];
        // 新値を再代入
        [proxy setValue: newSize
                 forKey: @"size"];
        [proxy setValue: newOrigin
                 forKey: @"origin"];
        // ** おかしな値が代入されたとしても、undefinedで無視される
        [proxy setValue: newTextFrame
                 forKey: @"textFrame"];
    }
    else
    {
        // 外れクリックなら単にマウス処理を終了
        [composition.undo enableUndoRegistration];
    }
    draggingOption = eDragOptionUndefined;
}

#pragma mark Key Event
- (void)keyDown:(NSEvent *)theEvent
{
    if(editable == NO)
        return ;

    [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
}

- (void)keyUp:(NSEvent*)theEvent
{
    // リピートのグループ化はkeyUpで判定する
    // これいらないかも -> 必要だった
    if(key_state.moved)
    {
        ECLayerProxy *proxy = composition.selectedLayer;
        id newPoint = [proxy valueForKey: @"origin"];
        [proxy setValue: [NSValue valueWithPoint:key_state.original] 
                 forKey: @"origin"];
        [composition.undo enableUndoRegistration];
        [proxy setValue: newPoint
                 forKey: @"origin"];
        key_state.moved = NO;
    }
}

- (void)moveUp:(id)sender
{
    ECLayer *selectedLayer = composition.selectedLayer.target;

    if(selectedLayer == nil || selectedLayer.lock)
        return;
    
    CGPoint origin = selectedLayer.origin;
    if(key_state.moved == NO)
    {
        [composition.undo disableUndoRegistration];
        key_state.original = origin;
        key_state.moved = YES;
    }
    origin.y++;
    selectedLayer.origin = origin;
    [self setNeedsDisplay: YES];
}

- (void)moveDown:(id)sender
{
    ECLayer *selectedLayer = composition.selectedLayer.target;
    if(selectedLayer == nil || selectedLayer.lock)
        return;
    
    CGPoint origin = selectedLayer.origin;
    if(key_state.moved == NO)
    {
        [composition.undo disableUndoRegistration];
        key_state.original = origin;
        key_state.moved = YES;
    }
    origin.y--;
    selectedLayer.origin = origin;
    [self setNeedsDisplay: YES];
}

- (void)moveLeft:(id)sender
{
    ECLayer *selectedLayer = composition.selectedLayer.target;
    if(selectedLayer == nil || selectedLayer.lock)
        return;
    
    CGPoint origin = selectedLayer.origin;
    if(key_state.moved == NO)
    {
        [composition.undo disableUndoRegistration];
        key_state.original = origin;
        key_state.moved = YES;
    }
    origin.x--;
    selectedLayer.origin = origin;
    [self setNeedsDisplay: YES];
}

- (void)moveRight:(id)sender
{
    ECLayer *selectedLayer = composition.selectedLayer.target;
    if(selectedLayer == nil || selectedLayer.lock)
        return;
    
    CGPoint origin = selectedLayer.origin;
    if(key_state.moved == NO)
    {
        [composition.undo disableUndoRegistration];
        key_state.original = origin;
        key_state.moved = YES;
    }
    origin.x++;
    selectedLayer.origin = origin;
    [self setNeedsDisplay: YES];
}

- (void)deleteBackward:(id)sender
{
    if([composition canDelete])
        [composition remove];
}

#pragma mark Drop
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{
    if(editable == NO)
        return NSDragOperationNone;

    dropType = eDropTypeNone;
    isAcceptableDropType = NO;
    dropping = YES;
    insideScene = NO;
    id pboard = [sender draggingPasteboard];
    id types = [NSArray arrayWithObject: (id)kUTTypeURL];
    if(![pboard canReadItemWithDataConformingToTypes: types])
        return NSDragOperationNone;
    
    // test content
    id url = [NSURL URLFromPasteboard: pboard];
    if([url isFileURL] == NO)
        return NSDragOperationNone;
    
    id ws = [NSWorkspace sharedWorkspace];
    id UTI = [ws typeOfFile: [url path] error: nil];
    if(UTI == nil)
        return NSDragOperationNone;
    
    if(UTTypeConformsTo((CFStringRef)UTI, utiSceneFile))
    {
        isAcceptableDropType = YES;
        dropType = eDropTypeTemplate;
        return NSDragOperationMove;
    }
    
    if(!UTTypeConformsTo((CFStringRef)UTI, kUTTypeImage))
        return NSDragOperationNone;
    
    // all test passed.
    isAcceptableDropType = YES;
    dropType = eDropTypeImage;
    return NSDragOperationCopy;
}

- (NSDragOperation)draggingUpdated:(id < NSDraggingInfo >)sender
{
    if(editable == NO)
        return NSDragOperationNone;

    if(isAcceptableDropType == NO)
        return NSDragOperationNone;
    
    [self setNeedsDisplay: YES];
    dropTarget = nil;
    insideScene = NO;
    
    // 座標テスト
    NSPoint point = [self convertPoint: [sender draggingLocation]
                              fromView: nil];
    NSRect sceneBounds = [self sceneBounds];
    if(NSPointInRect(point, sceneBounds) == NO)
        return NSDragOperationNone;
    
    insideScene = YES;
    
    if(dropType == eDropTypeTemplate)
    {
        return NSDragOperationMove;
    }
    
    // optドロップでcopyに制約された場合、常に新規レイヤー
    if([sender draggingSourceOperationMask] == NSDragOperationCopy)
        return NSDragOperationCopy;
    
    
    NSPoint scenePoint = [self convertPointToScene: point];
    // この点の直下を調べて適切なレイヤーにドロップ候補をかける
    // ロック中にドロップを受けるかどうかは設定で変えた方が良いか？
    ECLayer *layer = [composition.scene layerAtPoint: scenePoint];
    if(layer.lock)
        return NSDragOperationCopy;
    
    if([layer isKindOfClass: [ECRegularLayer class]])
        dropTarget = (ECRegularLayer*)layer;
    return NSDragOperationCopy;
}

- (BOOL)prepareForDragOperation:(id < NSDraggingInfo >)sender
{
    return YES;
}

- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender
{
    id ud = [NSUserDefaults standardUserDefaults];
    id pboard = [sender draggingPasteboard];    
    
    id url = [NSURL URLFromPasteboard: pboard];
    
    if(dropType == eDropTypeTemplate)
    {
        // テンプレートをロードしてsceneを差し替える
        NSSize size = [ECScene sizeOfContentsOfURL: url];
        if(!NSEqualSizes(composition.scene.size,size))
        {
            NSRunAlertPanel(NSLocalizedString(@"Template size mismatch.",@""),
                            NSLocalizedString(@"You cannot drop template of different size scene.",@""),
                            @"OK",nil,nil);
            return NO;
        }
        
        id newScene = [[ECScene alloc] initWithContentsOfURL: url];
        if(newScene == nil)
            return NO;
        [composition setScene: newScene];
        [newScene release];
        return YES;
    }
    
    id imageRef = [[[ECImageRef alloc] initWithURL: url] autorelease];
    if(imageRef == nil)
        return NO;
    
    if(dropTarget)
    {
        // レイヤーイメージの入れ替え
        CGSize newSize = [imageRef imageSize];
        CGSize oldSize = [dropTarget.image imageSize];
        CGFloat newRatio = newSize.width/newSize.height;
        CGFloat oldRatio = oldSize.width/oldSize.height;

        // アスペクト比を考慮して同じと判断したらフレームは維持する
        if(!_floatEQ_p(newRatio,oldRatio))
        {
            // アスペクト比が違う場合
            CGPoint refPoint = dropTarget.center;
            CGSize recSize = [composition.scene recommendSizeForImageSize: newSize];
            CGFloat x = floor(refPoint.x-recSize.width/2.0);
            CGFloat y = floor(refPoint.y-recSize.height/2.0);
            CGPoint newOrigin = CGPointMake(x,y);
            dropTarget.origin = newOrigin;
            dropTarget.size = recSize;
        }
        dropTarget.image = imageRef;
        
        // 背面画像をオートロック
        if([ud boolForKey: udBackgroundAutoLock])
        {
            if(dropTarget == [[composition.scene layers] lastObject])
                dropTarget.lock = YES;
        }
    }
    else
    {
        // ドロップされたイメージから新規でレイヤーを起こして追加する
        NSPoint refPoint;
        if([ud boolForKey: udDropCentering])
        {
            // 常に中心化を行う場合
            CGSize size = composition.scene.size;
            refPoint = NSMakePoint(size.width/2.0,size.height/2.0);
        }
        else
        {
            // ドロップ点を基準とする場合
            NSPoint point = [self convertPoint: [sender draggingLocation]
                              fromView: nil];
            refPoint = [self convertPointToScene: point];
        }
        CGSize recSize = [scene recommendSizeForImageSize: [imageRef imageSize]];
        CGFloat x = floor(refPoint.x-recSize.width/2.0);
        CGFloat y = floor(refPoint.y-recSize.height/2.0);
        CGPoint newOrigin = CGPointMake(x,y);
        ECRegularLayer *newLayer = [[[ECRegularLayer alloc] initWithImage: imageRef
                                                                     size: recSize] autorelease];
        newLayer.font = [ECRegularLayer defaultFont];
        newLayer.origin = newOrigin;
        newLayer.name = [scene nextLayerName];

        [composition add: newLayer];
        
        if([ud boolForKey: udBackgroundAutoLock] && [[scene layers] count] == 1)
        {
            // 背面画像をオートロック
            newLayer.lock = YES;
        }
    }
    
    return YES;
}

- (void)concludeDragOperation:(id<NSDraggingInfo>)sender
{
    [[self window] makeKeyAndOrderFront: self];
}

- (void)draggingEnded:(id < NSDraggingInfo >)sender
{
    dropTarget = nil;
    dropping = NO;
    insideScene = NO;
    dropType = eDropTypeNone;
    [self setNeedsDisplay: YES];
}

- (BOOL)wantsPeriodicDraggingUpdates
{
    return NO;
}

#pragma mark Notification
- (void)userDefaultsDidChange:(id)notif
{
    [self setNeedsDisplay: YES];
}

#pragma mark Action
- (IBAction)deselect:(id)sender
{
    [self _deselect];
}

- (IBAction)deleteImage:(id)sender
{
    [composition.selectedLayer setValue: nil
                                 forKey: @"image"];
}

- (IBAction)hideSelection:(id)sender
{
    // あったっけ？
    [self _deselect];
}

- (IBAction)toggleLock:(id)sender
{
    BOOL lock = composition.selectedLayer.target.lock;
    composition.selectedLayer.target.lock = !lock;
}

- (IBAction)showAllLayers:(id)sender
{
    id layers = [composition.scene layers];
    for(ECLayer *layer in layers)
    {
        layer.hide = NO;
    }
    [self setNeedsDisplay: YES];
}

- (IBAction)changeLayerFont:(id)sender
{
    id fm = [NSFontManager sharedFontManager];
    id font = [fm convertFont:[fm selectedFont]];
    [composition.selectedLayer setValue: font
                                 forKey: @"font"];
}

- (IBAction)cut:(id)sender
{
    ECLayer *layer = composition.selectedLayer.target;
    if(layer)
    {
        // copy
        id pboard = [NSPasteboard generalPasteboard];
        [pboard clearContents];
        id obj = [NSArray arrayWithObject: layer];
        [pboard writeObjects: obj];
        
        // delete
        [composition remove];
    }    
}

- (IBAction)copy:(id)sender
{
    ECLayer *layer = composition.selectedLayer.target;
    if(layer)
    {
        id pboard = [NSPasteboard generalPasteboard];
        [pboard clearContents];
        id obj = [NSArray arrayWithObject: layer];
        [pboard writeObjects: obj];
    }
}

- (IBAction)paste:(id)sender
{
    id pboard = [NSPasteboard generalPasteboard];
    id classArray = [NSArray arrayWithObjects: [ECRegularLayer class],
                     [ECCompositionLayer class], 
                     nil];
    id opts = [NSDictionary dictionary];
    
    BOOL ok = [pboard canReadObjectForClasses: classArray options: opts];
    if(ok)
    {
        id objs = [pboard readObjectsForClasses: classArray options: opts];
        ECLayer *layer = [objs objectAtIndex: 0];
        CGPoint origin = layer.origin;
        origin.x += 1;
        origin.y += 1;
        layer.origin = origin;
        id name = layer.name;
        layer.lock = NO;
        layer.hide = NO;
        id fmt = NSLocalizedString(@"%@ copy", @"");
        layer.name = [NSString stringWithFormat: fmt, name];
        [composition add: layer];     
    }
}

- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
    SEL action = [menuItem action];
    if(action == @selector(cut:) ||
       action == @selector(copy:))
        return composition.selectedLayer.target != nil;
    
    if(action == @selector(paste:))
    {
        id pboard = [NSPasteboard generalPasteboard];
        id classArray = [NSArray arrayWithObjects: [ECRegularLayer class],
                         [ECCompositionLayer class], 
                         nil];
        id opts = [NSDictionary dictionary];
        
        BOOL ok = [pboard canReadObjectForClasses: classArray options: opts];
        return ok;
    }
    return YES;
}
@end
