//
// SlideEditor.j
// Editor
//
// Created by Francisco Tolmasky on Date.
// Copyright 2005 - 2008, 280 North, Inc. All rights reserved.
//

import <AppKit/CALayer.j>
import <AppKit/CGGeometry.j>
import <AppKit/CPClipView.j>
import <AppKit/CPImage.j>
import <AppKit/CPImageView.j>
import <AppKit/CPScrollView.j>
import <AppKit/CPView.j>

import <SlideKit/SKShape.j>
import <SlideKit/SKMovie.j>

import <SlideKit/SKShapeLayer.j>
import <SlideKit/SKSlideLayer.j>
import <SlideKit/SKSlideLayoutLayer.j>
import <SlideKit/SKSlideView.j>

import "MediaImage.j"
import "TransformControl.j"


SKSetImageClass([MediaImage class]);

@implementation SKSlideLayoutLayer (EditorAdditions)

- (CALayer)hitTest:(CGPoint)aPoint
{
    if ([[self superlayer] isKindOfClass:[SKSlideLayer class]])
        return nil;
        
    return [super hitTest:aPoint];
}

@end

@implementation SlideEditor : SKSlideView
{
    id                      _delegate;
    
    PresentationController  _presentationController;
 
    CPDictionary            _slideLayerCache;
   
    // Managing Selected Shape Layers
    BOOL                    _postponedUnselect;
    ShapeLayer              _candidateShapeLayer;
    CPArray                 _selectedShapeLayers;
    CPIndexSet              _selectionIndexes;
    
    // Drag and Drop
    BOOL                    _isInLiveDrag;
    CGRect                  _draggingFrame;

    // Transforms
    BOOL                    _isInLiveTransform;
    TransformControl        _transformControl;
    
    // 
    BOOL                    _isForwardingEventsToFirstResponder;
    ShapeLayer              _firstResponder;
    
    // Undo Support
    BOOL                    _didUnselectForUndoOrRedo;
}

- (id)initWithFrame:(CPRect)aFrame
{
    self = [super initWithFrame:aFrame];
    
    if (self)
    {
        // FIXME: as awlays, hack.
        window.slideEditor = self;
    
        _slideLayerCache = [CPDictionary dictionary];
        _slideScaleCache = [CPDictionary dictionary];
        
        [self registerForDraggedTypes:[CPArray arrayWithObjects:SKShapesPboardType, CPImagePboardType, nil]];
        
        _selectedShapeLayers = [];
        _selectionIndexes = [CPIndexSet indexSet];
        
        _draggingFrame = CGRectMakeZero();
        
        [self setDisplaysClippedRegions:YES];
        [self setDisplaysMoviesAsThumbnails:YES];
        [self setDisplaysPlaceholderPrompts:YES];
        
        [self setDisplaysShadow:YES];
        [self setShadowWeight:CPHeavyShadow];
        
        // This is a bit tricky, so pay attention:  SlideEditor represents 
        // EVERY slide within a presentation, so we register for slide 
        // notifications regardless, and then set our selected slide accordingly.    
        var defaultCenter = [CPNotificationCenter defaultCenter];
                            
        [defaultCenter addObserver:self
                          selector:@selector(elementDidChangeGeometry:)
                              name:ElementGeometryDidChangeNotification
                            object:nil];
        
        // We listen to all undo managers, since in practice there is only
        // ever one and we don't want to have to re-register when it changes.
        [defaultCenter addObserver:self
                          selector:@selector(undoManagerWillUndoOrRedo:)
                              name:CPUndoManagerWillUndoChangeNotification
                            object:nil];

        [defaultCenter addObserver:self
                          selector:@selector(undoManagerWillUndoOrRedo:)
                              name:CPUndoManagerWillRedoChangeNotification
                            object:nil];
    }
    
    return self;
}

- (void)setFrameSize:(CGSize)aSize
{
    [super setFrameSize:aSize];
    
    // FIXME: Should this be handled by SKShapeLayer?  Tough call.
    var count = _selectedShapeLayers.length;
        
    while (count--)
        [[_selectedShapeLayers[count] auxiliaryLayers] makeObjectsPerformSelector:@selector(updateFromElementLayer)];
}

- (void)slideBaseLayerDidChangeBounds:(SKSlideLayer)aSlideLayer
{
    if (_slideLayer != aSlideLayer)
        return;
    
    [super slideBaseLayerDidChangeBounds:aSlideLayer];
    
    // FIXME: Should this be handled by SKShapeLayer?  Tough call.
    var count = _selectedShapeLayers.length;
        
    while (count--)
        [[_selectedShapeLayers[count] auxiliaryLayers] makeObjectsPerformSelector:@selector(updateFromElementLayer)];
}

// This is only dependable if we are the only editor view.
- (CALayer)layerForSlide:(SKSlide)aSlide
{
    var hash = [aSlide hash];
        layer = [_slideLayerCache objectForKey:hash];
    
    if (!layer)
    {
        layer = [super layerForSlide:aSlide];
        [_slideLayerCache setObject:layer forKey:hash];
    }
    
    return layer;
}

// Assigning the Delegate

- (void)setDelegate:(id)aDelegate
{
    _delegate = aDelegate;
}

- (id)delegate
{
    return _delegate;
}

- (void)setPresentationController:(PresentationController)aPresentationController
{
    if (_presentationController == aPresentationController)
        return;
    
    var defaultCenter = [CPNotificationCenter defaultCenter];
    
    // This check is crucial, if not we delete name:nil object:nil, AKA EVERYTHING!!
    if (_presentationController)
        [defaultCenter removeObserver:self name:nil object:_presentationController];
    
    _presentationController = aPresentationController;

    [defaultCenter
         addObserver:self
            selector:@selector(presentationControllerDidChangeSelection:)
                name:PresentationControllerDidChangeSelectionNotification
              object:_presentationController];

    [defaultCenter postNotificationName:PresentationControllerDidChangeSelectionNotification object:_presentationController];
    
    _presentationController = aPresentationController;
    
    [self setSlide:[_presentationController selectedSlide]];
}

- (PresentationController)presentationController
{
    return _presentationController;
}

- (void)presentationControllerDidChangeSelection:(CPNotification)aNotification
{
    var selectedSlide = [_presentationController selectedSlide];
    
    if (selectedSlide != _slide)
        [self setSlide:selectedSlide];
}

- (void)setSlide:(Slide)aSlide
{
    if (_slide == aSlide)
        return;
    
    [self unselectAllShapeLayers];
    
    [super setSlide:aSlide];
    
    // Since we already listen to this unconditionally, remove ourselves
    // as observers for this individual slide so that we don't get these 
    // notifications twice.
    var defaultCenter = [CPNotificationCenter defaultCenter];

    [defaultCenter removeObserver:self
                             name:SKSlideBaseDidInsertShapesNotification
                           object:aSlide];
          
    [defaultCenter removeObserver:self
                             name:SKSlideBaseDidRemoveShapesNotification
                           object:aSlide];
}

- (CGRect)slideBounds
{
    return [_slideLayer bounds];
}

- (CGPoint)slidePosition
{
    return [_slideLayer position];
}

- (void)slideBaseLayer:(SKSlideLayer)aSlideLayer didInsertShapeLayers:(CPArray)shapeLayers
{
    [_presentationController setSelectionSlide:[aSlideLayer slide]];
    
    [self selectShapeLayers:shapeLayers byExtendingSelection:NO];
}

- (void)slideBaseLayer:(SKSlideLayer)aSlideLayer didRemoveShapeLayers:(CPArray)shapeLayers
{
    [_presentationController setSelectionSlide:[aSlideLayer slide]];

    [self unselectAllShapeLayers];
}

- (void)elementDidChangeGeometry:(CPNotification)aNotification
{
    var shape = [aNotification object],
        slide = [shape slide];
    
    if (_slide != slide)
        [_presentationController setSelectionSlide:slide];
    
    var layer = [self layerForShape:shape];

    if (![_selectedShapeLayers containsObject:layer])
    {
        if (!_didUnselectForUndoOrRedo)
        {
            [self unselectAllShapeLayers];
            _didUnselectForUndoOrRedo = YES;
        }
        
        [self selectShapeLayers:[layer] byExtendingSelection:YES];
    }
    
    [_slideLayer _accomodateClippedRegions];
}

- (void)undoManagerWillUndoOrRedo:(CPNotification)aNotification
{
    _didUnselectForUndoOrRedo = NO;
}

- (void)performDragOperation:(id <CPDraggingInfo>)aSender
{
    var location = CGPointApplyAffineTransform([self convertPoint:[aSender draggingLocation] fromView:nil], [_slideLayer transformToLayer]),
        pasteboard = [aSender draggingPasteboard],
        shape = nil;

    if ([[pasteboard types] containsObject:SKShapesPboardType])
        shape = [CPKeyedUnarchiver unarchiveObjectWithData:[pasteboard dataForType:SKShapesPboardType]][0];
    else if (NO /* */)
    {
        // We still need to handle the case of manually dragging and dropping images.
    }
    
    [shape setPosition:CGPointMake(location.x, location.y)];

    [_slide beginEditing];
    [_slide addShape:shape];
    [_slide endEditing];
}

- (CPArray)selectedShapeLayers
{
    return _selectedShapeLayers;
}

- (void)selectShapeLayers:(CPArray)shapeLayers byExtendingSelection:(BOOL)aFlag
{
    if (shapeLayers.length)
    {
        var slide = [[shapeLayers[0] shape] slide];
        
        if (_slide != slide)
            [_presentationController setSelectionSlide:slide];
    }
    
    if (aFlag)
        [_selectedShapeLayers addObjectsFromArray:shapeLayers];

    else
    {        
        _selectionIndexes = [CPIndexSet indexSet];
        
        if (_firstResponder)
        {
            [_firstResponder endTextEditing];
            _firstResponder = nil;
        }
        
        // Remove standard transform controls from selected shape layers.
        [_selectedShapeLayers makeObjectsPerformSelector:@selector(removeAuxiliaryLayersForKeys:) withObject:[BoundsControlAuxiliaryKey, RotationControlAuxiliaryKey]];

        // Add standard transform controls to newly selected shape layers.
        var count = _selectedShapeLayers.length;
        
        while (count--)
        {
            var layer = _selectedShapeLayers[count];
            
            //[layer addAuxiliaryLayer:[BoundsControl transformControlFromPool] forKey:BoundsControlAuxiliaryKey];
            //[layer addAuxiliaryLayer:[RotationControl transformControlFromPool] forKey:RotationControlAuxiliaryKey];

            [layer removeAuxiliaryLayersForKeys:[[layer adjustments] allKeys]];
        }
        
        _selectedShapeLayers = shapeLayers;
    }
    
    // Add standard transform controls to newly selected shape layers.
    var count = shapeLayers.length,
        sublayers = [_slideLayer sublayers],
        shapes = [_slide shapes];
    
    while (count--)
    {       
        var layer = shapeLayers[count],
            shape = [layer shape];
     
        [_selectionIndexes addIndex:[shapes indexOfObjectIdenticalTo:shape]];
        
        if (layer._textLayer)
            layer._textLayer._selectionDelegate = self;

window.the_layer = _rootLayer;
        [layer addAuxiliaryLayer:[BoundsControl transformControlFromPool] forKey:BoundsControlAuxiliaryKey];
        
        if ([layer isKindOfClass:[SKPictureLayer class]] || [layer isMemberOfClass:[SKShapeLayer class]] && [[shape textBody] length] == 0 && ![shape isPlaceholder])
            [layer addAuxiliaryLayer:[RotationControl transformControlFromPool] forKey:RotationControlAuxiliaryKey];
        
        var name,
            adjustments = [layer adjustments],
            names = [adjustments keyEnumerator];
        
        while (name = [names nextObject])
        {
            var adjustment = [AdjustmentControl transformControlFromPool];
            
            [adjustment setAdjustmentName:name];
            [layer addAuxiliaryLayer:adjustment forKey:name];
        }
    }
    
    [self selectedShapeLayersDidChange];
}

- (void)unselectShapeLayers:(CPArray)shapeLayers
{
    [_selectedShapeLayers removeObjectsInArray:shapeLayers];
    [shapeLayers makeObjectsPerformSelector:@selector(removeAuxiliaryLayersForKeys:) withObject:[BoundsControlAuxiliaryKey, RotationControlAuxiliaryKey]];
    
    [self selectedShapeLayersDidChange];
}

- (void)unselectAllShapeLayers
{
    [self selectShapeLayers:[] byExtendingSelection:NO];
}

- (void)selectedShapeLayersDidChange
{
    [_delegate slideEditorDidChangeSelection:self];
}

- (void)mouseUp:(CPEvent)anEvent
{
    if (_isForwardingEventsToFirstResponder)
    {
        [_firstResponder mouseUp:anEvent];
        _isForwardingEventsToFirstResponder = NO;
    }
    
    else if (_isInLiveDrag)
        [self endDragWithEvent:anEvent];
    
    else if (_isInLiveTransform)
    {
        _isInLiveTransform = NO;
        [_transformControl endTransformWithEvent:anEvent];
    }

    else if (_postponedUnselect)
    {
        if ([anEvent modifierFlags] & CPShiftKeyMask)
            [self unselectShapeLayers:[_candidateShapeLayer]];
        else if ([_selectedShapeLayers count] > 1)
            [self selectShapeLayers:[_candidateShapeLayer] byExtendingSelection:NO];
    }
}

- (BOOL)acceptsFirstResponder
{
    return YES;
}

- (void)mouseDown:(CPEvent)anEvent
{
    var location = [self convertPoint:[anEvent locationInWindow] fromView:nil],
        hitLayer = [_layer hitTest:location];

    // Forward event to "first responder" if we have one.
    if (hitLayer == _firstResponder)
    {
        _isForwardingEventsToFirstResponder = YES;
        
        return [_firstResponder mouseDown:anEvent];
    }
    
    // If it's a transform control, then start sending events to it.
    // FIXME: could we coallesce this with firstresponder forwarding?
    if ([hitLayer isKindOfClass:[TransformControl class]])
    {
        _isInLiveTransform = YES;
        _transformControl = hitLayer;
        
        [_transformControl setSlideWorkspace:self];
        [_transformControl startTransformWithEvent:anEvent];
    
        return;
    }
    
    // If it's not a shape layer, then it's just some other floating 
    // layer at this point, and we don't send events to those.
    if (![hitLayer isKindOfClass:[SKShapeLayer class]])
        return [self unselectAllShapeLayers];
    
    // At this point there should be just one selected shape layer.
    if ([anEvent clickCount] == 2)
        return [self attemptToEditTextInSelectedShapeLayerWithEvent:anEvent];
    
    // Basically, although we may mouse down on an shape, we may not 
    // be constraining to it, because we can still initiate a drag after
    // all.  For now assume that we are going to constrain it though.
    _postponedUnselect = NO;
    
    var selected = [_selectedShapeLayers containsObject:hitLayer],
        modifierFlags = [anEvent modifierFlags];
    
    // All of these are equivilant to us.
    if (modifierFlags & CPShiftKeyMask || modifierFlags & CPCommandKeyMask || modifierFlags & CPControlKeyMask)
    {   
        // If it's already highlighted, unhighlight.  Else, select.
        if (selected)
            [self unselectShapeLayers:[hitLayer]];
        // Simply extend the selection
        else
            [self selectShapeLayers:[hitLayer] byExtendingSelection:YES];
    }
    else if (!selected)
        [self selectShapeLayers:[hitLayer] byExtendingSelection:NO];
    else
    {
        _postponedUnselect = YES;
        _candidateShapeLayer = hitLayer;
    }
}

- (void)mouseDragged:(CPEvent)anEvent
{
    if (_isForwardingEventsToFirstResponder)
        return [_firstResponder mouseDragged:anEvent];
        
    else if (_isInLiveDrag)
        return [self updateDragWithEvent:anEvent];
    
    else if (_isInLiveTransform)
        return [_transformControl updateTransformWithEvent:anEvent];
    
    var hitLayer = [_layer hitTest:[self convertPoint:[anEvent locationInWindow] fromView:nil]];
    
    if (hitLayer == _slideLayer || [hitLayer isKindOfClass:[BoundsControl class]])
        return;
    
    if (![_selectedShapeLayers containsObject:hitLayer])
        return;
        
    if (_selectedShapeLayers.length)
    {
        _postponedUnselect = NO;
        [self startDragWithEvent:anEvent];
    }
    else
        {} // live select
}

- (void)endDragWithEvent:(CPEvent)anEvent
{
    _isInLiveDrag = NO;
    
    var index = _selectedShapeLayers.length;
    
    while (index--)
    {
        var shapeLayer = _selectedShapeLayers[index],
            shape = [shapeLayer shape];
        
        [shape beginEditing];
        [shape setPosition:CGPointMakeCopy([shapeLayer position])];
        [shape endEditing];
        
    }window.nodisplay = false;
}

- (void)startDragWithEvent:(CPEvent)anEvent
{window.nodisplay = true;
    _isInLiveDrag = YES;

    _draggingFrame.size = CGSizeMakeZero();
    _draggingFrame.origin = CGPointMakeCopy([anEvent locationInWindow]);
    
    _draggingStartFrame = [self frame];
}

- (void)updateDragWithEvent:(CPEvent)anEvent
{
    var location = [anEvent locationInWindow],
        scale = [self scale],
        deltaX = (location.x - CGRectGetMinX(_draggingFrame)) / scale,
        deltaY = (location.y - CGRectGetMinY(_draggingFrame)) / scale;

    if ([anEvent modifierFlags] & CPShiftKeyMask)
    {
        if (ABS(deltaX) > ABS(deltaY))
            deltaY = 0;
        else
            deltaX = 0;
    }
    
    deltaX -= CGRectGetWidth(_draggingFrame);
    deltaY -= CGRectGetHeight(_draggingFrame);
    
    var index = _selectedShapeLayers.length;
        
    if(index == 1 && !([anEvent modifierFlags] & (CPShiftKeyMask | CPDeviceIndependentModifierFlagsMask)))
    {
        var shapeLayer = _selectedShapeLayers[0],
            origin = [shapeLayer position],
            
            // This is absolutely terrible, we should cache this.
            size = [[_presentationController presentation] size];
            
        if(ABS(origin.x + deltaX - (size.width / 2.0)) < 10.0)
            deltaX = size.width / 2.0 - origin.x;
            
        if(ABS(origin.y + deltaY- (size.height / 2.0)) < 10.0)
            deltaY = size.height / 2.0 - origin.y;
    }

    while (index--)
    {
        var shapeLayer = _selectedShapeLayers[index],
            origin = [shapeLayer position];

        [shapeLayer setPosition:CGPointMake(origin.x + deltaX, origin.y + deltaY)];
    }

    _draggingFrame.size.width += deltaX;
    _draggingFrame.size.height += deltaY;
}

- (void)attemptToEditTextInSelectedShapeLayerWithEvent:(CPEvent)anEvent
{
    if (_selectedShapeLayers.length != 1)
        return;
    
    var shapeLayer = _selectedShapeLayers[0];
    
    // ONLY shapes that haven't been rotated yet can edit text.  
    // NOT pictures, movies, or groups.
    if ([shapeLayer allowsTextEditing])
    {
        [self selectShapeLayers:[shapeLayer] byExtendingSelection:NO];
        
        // We don't want to show our rotator while editing.
        [shapeLayer removeAuxiliaryLayerForKey:RotationControlAuxiliaryKey];
        
        _firstResponder = shapeLayer;
        [_firstResponder beginTextEditing];
        
        _firstResponder._textLayer._selectionDelegate = self;
        
        _isForwardingEventsToFirstResponder = YES;
        [_firstResponder mouseDown:anEvent];
        
        [_delegate slideEditorDidChangeTextAttributes:self];
    }
}

@end

// FIXME: This will one day be replaced by nib's handling of undo + redo
@implementation SlideEditor (UndoAndRedo)

/*- (void)keyUp:(CPEvent)anEvent
{
    if (!window.ppp) window.ppp ="";
    window.ppp += "keyup " + [anEvent characters] + " " + [anEvent keyCode] + " " + [anEvent isARepeat] + "\n";
return;
}*/

- (void)keyDown:(CPEvent)anEvent
{
    var charactersIgnoringModifiers = [anEvent charactersIgnoringModifiers],
        modifierFlags = [anEvent modifierFlags];
        
    if (!_firstResponder)
    {
        switch([anEvent keyCode])
        {
            case  8:    //backspace && delete
            case 46: return [self deleteBackwards:self];
        }
    }
    
    if (!_firstResponder && _selectedShapeLayers.length == 1)
    {
        var shapeLayer = _selectedShapeLayers[0];
        
        // ONLY shapes that haven't been rotated yet can edit text.  
        // NOT pictures, movies, or groups.
        if ([shapeLayer rotationRadians] == 0.0 && [shapeLayer isMemberOfClass:[SKShapeLayer class]])
        {
            // We don't want to show our rotator while editing.
            [shapeLayer removeAuxiliaryLayerForKey:RotationControlAuxiliaryKey];
            
            _firstResponder = shapeLayer;
            [_firstResponder beginTextEditing];
                    
            _firstResponder._textLayer._selectionDelegate = self;
        }
    }
    
    [_firstResponder keyDown:anEvent];
}

//

- (void)changeFontFamily:(id)aSender
{
    [_selectedShapeLayers makeObjectsPerformSelector:@selector(setFontFamily:) withObject:[[aSender selectedItem] name]];
}

- (void)changeFontSize:(id)aSender
{
    [_selectedShapeLayers makeObjectsPerformSelector:@selector(setFontSize:) withObject:[[aSender selectedItem] value]];
}

- (void)changeTextColor:(id)aSender
{
    [_selectedShapeLayers makeObjectsPerformSelector:@selector(setTextColor:) withObject:[SKColor colorWithColor:[aSender color] adjustments:nil]];
}

- (void)changeTextStyle:(id)aSender
{
    var tag = [aSender selectedTag],
        selector = nil;

    if (tag ==  0)
        selector = @selector(bold:);
    else if (tag == 1)
        selector = @selector(unbold:);
    else if (tag == 2)
        selector = @selector(italicize:);
    else if (tag == 3)
        selector = @selector(unitalicize:);
    else if (tag == 4)
        selector = @selector(underline:);
    else if (tag == 5)
        selector = @selector(ununderline:);
        
    [_selectedShapeLayers makeObjectsPerformSelector:selector withObject:aSender];
}

- (void)changeTextAlignment:(id)aSender
{
    [_selectedShapeLayers makeObjectsPerformSelector:@selector(align:) withObject:[aSender selectedTag]];
}

- (void)changeBulletIndentationLevel:(id)aSender
{
    [_selectedShapeLayers makeObjectsPerformSelector:([aSender selectedTag] == -1 ? @selector(decreaseBulletLevel) : @selector(increaseBulletLevel))];
}

- (void)changeBulletStyle:(id)aSender
{
    [_selectedShapeLayers makeObjectsPerformSelector:@selector(bulletStyle:) withObject:[aSender selectedTag]];
}

- (void)changeFillColor:(id)aSender
{
    var layer = nil,
        layers = [_selectedShapeLayers objectEnumerator],
        color = [SKConcreteColor colorWithColor:[aSender color] adjustments:nil];
        
    while (layer = [layers nextObject])
    {
        var shape = [layer shape];
        
        [shape beginEditing];
        [shape setFillColor:color];
        [shape endEditing];
    }
}

- (void)changeOpacity:(id)aSender
{
    var alpha = [aSender floatValue],
        count = _selectedShapeLayers.length;
    
    while (count--)
    {
        var shape = [_selectedShapeLayers[count] shape];
        
        [shape beginEditing];
        [shape setOpacity:alpha];
        [shape endEditing];
    }
}

- (void)deleteBackwards:(id)aSender
{
    var shapes = [],
        index = 0,
        count = _selectedShapeLayers.length;
    
    for (; index < count; ++index)
        shapes.push([_selectedShapeLayers[index] shape]);    

    [_slide beginEditing];
    [_slide removeShapes:shapes];
    [_slide endEditing];
}

//

//

- (void)cut:(id)aSender
{
    if (_firstResponder)
    {
        [_firstResponder cut:self];
        
        return;
    }
    
    [self copy:self];
    [self deleteBackwards:self];
}

- (void)copy:(id)aSender
{
    if (_firstResponder)
    {
        [_firstResponder copy:self];
        
        return;
    }
    
    var pasteboard = [CPPasteboard generalPasteboard];
    
    [pasteboard declareTypes:[SKShapesPboardType] owner:self];
    
    // We don't do lazy copy paste in case the selection changes.
    var shapes = [],
        index = 0,
        count = _selectedShapeLayers.length;
    
    for (; index < count; ++index)
        shapes.push([_selectedShapeLayers[index] shape]);
    
    [pasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:shapes] forType:SKShapesPboardType];
}

- (void)paste:(id)aSender
{
    if (_firstResponder)
    {
        [_firstResponder paste:self];
        
        return;
    }
    
    var pasteboard = [CPPasteboard generalPasteboard],
        types = [pasteboard types];

    if (![types containsObject:SKShapesPboardType])
        return;
    
    var shapes = [CPKeyedUnarchiver unarchiveObjectWithData:[pasteboard dataForType:SKShapesPboardType]];   
    /*
    var index = 0,
        count = shapes.length;
    
    for (; index < count; ++index)
        
    */
    [_slide beginEditing];
    [_slide addShapes:shapes];
    [_slide endEditing];
}

- (void)textLayerDidChangeSelection:(TextLayer)aTextLayer
{
    [_delegate slideEditorDidChangeTextSelection:self];
}

- (void)textLayerDidChangeTextAttributes:(TextLayer)aTextLayer
{
    [_delegate slideEditorDidChangeTextAttributes:self];
}

- (void)moveShapesForward:(id)aSender
{
    var count = [_selectionIndexes count],
        allShapes = [_slide shapes],
        allShapesCount = [allShapes count];
    
    if (!count || (count == allShapesCount))
        return;
        
    [_slide beginEditing];
    
    [_slide moveShapesAtIndexes:_selectionIndexes toIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(MIN(allShapesCount - count, [_selectionIndexes lastIndex] + 2 - count), count)]];

    [_slide endEditing];
}

- (void)moveShapesBackward:(id)aSender
{
    var count = [_selectionIndexes count],
        allShapes = [_slide shapes],
        allShapesCount = [allShapes count];
    
    if (!count || (count == allShapesCount))
        return;

    [_slide beginEditing];
    
    [_slide moveShapesAtIndexes:_selectionIndexes toIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(MAX(0, [_selectionIndexes firstIndex] - 1), count)]];
    
    [_slide endEditing];
}

- (void)moveShapesFront:(id)aSender
{
    var count = [_selectionIndexes count],
        allShapes = [_slide shapes],
        allShapesCount = [allShapes count];
    
    if (!count || (count == allShapesCount))
        return;
    
    [_slide beginEditing];
    
    [_slide moveShapesAtIndexes:_selectionIndexes toIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(allShapesCount - count, count)]];
    
    [_slide endEditing];
}

- (void)moveShapesBack:(id)aSender
{
    var count = [_selectionIndexes count],
        allShapes = [_slide shapes],
        allShapesCount = [allShapes count];
    
    if (!count || (count == allShapesCount))
        return;
        
    [_slide beginEditing];
    
    [_slide moveShapesAtIndexes:_selectionIndexes toIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, count)]];
    
    [_slide endEditing];
}

@end

var SlideEditorBackgroundColor          = nil,
    SlideEditorBackgroundGradientImage  = nil;

@implementation SlideEditorClipView : CPClipView
{
    CPImageView _backgroundGradientView;
}

+ (CPColor)backgroundColor
{
    if (!SlideEditorBackgroundColor)
        SlideEditorBackgroundColor = [CPColor colorWithCalibratedRed:215.0 / 255.0 green:215.0 / 255.0 blue:215.0 / 255.0 alpha:1.0];

    return SlideEditorBackgroundColor;
}

+ (CPImage)backgroundGradientImage
{
    if (!SlideEditorBackgroundGradientImage)
        [SlideEditorBackgroundGradientImage = [CPImage alloc] initWithContentsOfFile:@"Resources/SlideEditorBackgroundGradient.png" size:CGSizeMake(1.0, 312.0)];
    
    return SlideEditorBackgroundGradientImage;
}

- (id)initWithFrame:(CGRect)aFrame
{
    self = [super initWithFrame:aFrame];
    
    if (self)
    {
        var theClass = [self class];
    
        // Set up the background.
        [self setBackgroundColor:[theClass backgroundColor]];
        
        _backgroundGradientView = [[CPImageView alloc] initWithFrame:CGRectMake(1.0, 1.0, 0.0, 0.0)];
        
        [_backgroundGradientView setImageScaling:CPScaleToFit];
        [_backgroundGradientView setImage:[theClass backgroundGradientImage]];
        [_backgroundGradientView setFrameSize:CGSizeMake(CGRectGetWidth([self bounds]) - 2.0, 312.0)];
        [_backgroundGradientView setAutoresizingMask:CPViewWidthSizable];

        [self addSubview:_backgroundGradientView];
    }
    
    return self;
}

@end

@implementation SlideEditorScrollView : CPScrollView
{
}

- (id)initWithFrame:(CGRect)aFrame
{
    self = [super initWithFrame:aFrame];
    
    if (self)
        [self setContentView:[[SlideEditorClipView alloc] initWithFrame:CGRectMakeZero()]];
    
    return self;
}

@end

@implementation SKShapeLayer (SlideEditorAdditions)

- (BOOL)allowsTextEditing
{
    return [self rotationRadians] == 0.0 && [self isMemberOfClass:[SKShapeLayer class]];
}

@end
