Fixed some memory leaks, and took the address-related properties out of Player.
     1 /*  This code is based on Apple's "GeekGameBoard" sample code, version 1.0.
 
     2     http://developer.apple.com/samplecode/GeekGameBoard/
 
     3     Copyright © 2007 Apple Inc. Copyright © 2008 Jens Alfke. All Rights Reserved.
 
     5     Redistribution and use in source and binary forms, with or without modification, are permitted
 
     6     provided that the following conditions are met:
 
     8     * Redistributions of source code must retain the above copyright notice, this list of conditions
 
     9       and the following disclaimer.
 
    10     * Redistributions in binary form must reproduce the above copyright notice, this list of
 
    11       conditions and the following disclaimer in the documentation and/or other materials provided
 
    12       with the distribution.
 
    14     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 
    15     IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
 
    16     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
 
    17     BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 
    18     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 
    19     PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 
    20     CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
 
    21     THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
    29 #import "QuartzUtils.h"
 
    33 @interface BoardView ()
 
    34 - (void) _findDropTarget: (NSPoint)pos;
 
    38 @implementation BoardView
 
    41 @synthesize gameboard=_gameboard;
 
    51 - (void) _removeGameBoard
 
    54         RemoveImmediately(_gameboard);
 
    59 - (void) createGameBoard
 
    61     [self _removeGameBoard];
 
    62     _gameboard = [[CALayer alloc] init];
 
    63     _gameboard.frame = [self gameBoardFrame];
 
    64     _gameboard.autoresizingMask = kCALayerMinXMargin | kCALayerMaxXMargin | kCALayerMinYMargin | kCALayerMaxYMargin;
 
    66     // Tell the game to set up the board:
 
    67     _game.board = _gameboard;
 
    69     [self.layer addSublayer: _gameboard];
 
    76     [_gameboard setValue: [NSNumber numberWithDouble: M_PI]
 
    77               forKeyPath: @"transform.rotation"];
 
    86 - (void) setGame: (Game*)game
 
    90         [self createGameBoard];
 
    94 - (void) startGameNamed: (NSString*)gameClassName
 
    96     Class gameClass = NSClassFromString(gameClassName);
 
    97     Game *game = [[gameClass alloc] init];
 
   108         && _game.currentPlayer.local
 
   109         && _game.currentTurn.status < kTurnComplete;
 
   113 - (CGRect) gameBoardFrame
 
   115     return self.layer.bounds;
 
   119 - (void)resetCursorRects
 
   121     [super resetCursorRects];
 
   122     if( self.canMakeMove )
 
   123         [self addCursorRect: self.bounds cursor: [NSCursor openHandCursor]];
 
   127 - (IBAction) enterFullScreen: (id)sender
 
   129     [self _removeGameBoard];
 
   130     if( self.isInFullScreenMode ) {
 
   131         [self exitFullScreenModeWithOptions: nil];
 
   133         [self enterFullScreenMode: self.window.screen 
 
   136     [self createGameBoard];
 
   140 - (void)viewWillStartLiveResize
 
   142     [super viewWillStartLiveResize];
 
   143     _oldSize = self.frame.size;
 
   146 - (void)setFrameSize:(NSSize)newSize
 
   148     [super setFrameSize: newSize];
 
   149     if( _oldSize.width > 0.0f ) {
 
   150         CGAffineTransform xform = _gameboard.affineTransform;
 
   151         xform.a = xform.d = MIN(newSize.width,newSize.height)/MIN(_oldSize.width,_oldSize.height);
 
   152         _gameboard.affineTransform = xform;
 
   154         [self createGameBoard];
 
   157 - (void)viewDidEndLiveResize
 
   159     [super viewDidEndLiveResize];
 
   160     _oldSize.width = _oldSize.height = 0.0f;
 
   161     [self createGameBoard];
 
   166 #pragma mark KEY EVENTS:
 
   169 - (void) keyDown: (NSEvent*)ev
 
   171     if( self.isInFullScreenMode ) {
 
   172         if( [ev.charactersIgnoringModifiers hasPrefix: @"\033"] )       // Esc key
 
   173             [self enterFullScreen: self];
 
   179 #pragma mark HIT-TESTING:
 
   182 /** Converts a point from window coords, to this view's root layer's coords. */
 
   183 - (CGPoint) _convertPointFromWindowToLayer: (NSPoint)locationInWindow
 
   185     NSPoint where = [self convertPoint: locationInWindow fromView: nil];    // convert to view coords
 
   186     return NSPointToCGPoint( [self convertPointToBase: where] );            // then to layer coords
 
   190 // Hit-testing callbacks (to identify which layers caller is interested in):
 
   191 typedef BOOL (*LayerMatchCallback)(CALayer*);
 
   193 static BOOL layerIsBit( CALayer* layer )        {return [layer isKindOfClass: [Bit class]];}
 
   194 static BOOL layerIsBitHolder( CALayer* layer )  {return [layer conformsToProtocol: @protocol(BitHolder)];}
 
   195 static BOOL layerIsDropTarget( CALayer* layer ) {return [layer respondsToSelector: @selector(draggingEntered:)];}
 
   198 /** Locates the layer at a given point in window coords.
 
   199     If the leaf layer doesn't pass the layer-match callback, the nearest ancestor that does is returned.
 
   200     If outOffset is provided, the point's position relative to the layer is stored into it. */
 
   201 - (CALayer*) hitTestPoint: (NSPoint)locationInWindow
 
   202          forLayerMatching: (LayerMatchCallback)match
 
   203                    offset: (CGPoint*)outOffset
 
   205     CGPoint where = [self _convertPointFromWindowToLayer: locationInWindow ];
 
   206     CALayer *layer = [_gameboard hitTest: where];
 
   209             CGPoint bitPos = [self.layer convertPoint: layer.position 
 
   210                               fromLayer: layer.superlayer];
 
   212                 *outOffset = CGPointMake( bitPos.x-where.x, bitPos.y-where.y);
 
   215             layer = layer.superlayer;
 
   222 #pragma mark MOUSE CLICKS & DRAGS:
 
   225 - (void) mouseDown: (NSEvent*)ev
 
   227     if( ! self.canMakeMove ) {
 
   233     _dragStartPos = ev.locationInWindow;
 
   234     _dragBit = (Bit*) [self hitTestPoint: _dragStartPos
 
   235                         forLayerMatching: layerIsBit 
 
   236                                   offset: &_dragOffset];
 
   239         // If no bit was clicked, see if it's a BitHolder the game will let the user add a Bit to:
 
   240         id<BitHolder> holder = (id<BitHolder>) [self hitTestPoint: _dragStartPos
 
   241                                                  forLayerMatching: layerIsBitHolder
 
   244             _dragBit = [_game bitToPlaceInHolder: holder];
 
   246                 _dragOffset.x = _dragOffset.y = 0;
 
   247                 if( _dragBit.superlayer==nil )
 
   248                     _dragBit.position = [self _convertPointFromWindowToLayer: _dragStartPos];
 
   262     _oldHolder = _dragBit.holder;
 
   263     // Ask holder's and game's permission before dragging:
 
   265         _dragBit = [_oldHolder canDragBit: _dragBit];
 
   266         if( _dragBit && ! [_game canBit: _dragBit moveFrom: _oldHolder] ) {
 
   267             [_oldHolder cancelDragBit: _dragBit];
 
   278     _oldSuperlayer = _dragBit.superlayer;
 
   279     _oldLayerIndex = [_oldSuperlayer.sublayers indexOfObjectIdenticalTo: _dragBit];
 
   280     _oldPos = _dragBit.position;
 
   281     ChangeSuperlayer(_dragBit, self.layer, self.layer.sublayers.count);
 
   282     _dragBit.pickedUp = YES;
 
   283     [[NSCursor closedHandCursor] push];
 
   287             _dragBit.position = [self _convertPointFromWindowToLayer: _dragStartPos];
 
   289         [self _findDropTarget: _dragStartPos];
 
   294 - (void) mouseDragged: (NSEvent*)ev
 
   297         // Get the mouse position, and see if we've moved 3 pixels since the mouseDown:
 
   298         NSPoint pos = ev.locationInWindow;
 
   299         if( fabs(pos.x-_dragStartPos.x)>=3 || fabs(pos.y-_dragStartPos.y)>=3 )
 
   302         // Move the _dragBit (without animation -- it's unnecessary and slows down responsiveness):
 
   303         CGPoint where = [self _convertPointFromWindowToLayer: pos];
 
   304         where.x += _dragOffset.x;
 
   305         where.y += _dragOffset.y;
 
   307         CGPoint newPos = [_dragBit.superlayer convertPoint: where fromLayer: self.layer];
 
   309         [CATransaction flush];
 
   310         [CATransaction begin];
 
   311         [CATransaction setValue:(id)kCFBooleanTrue
 
   312                          forKey:kCATransactionDisableActions];
 
   313         _dragBit.position = newPos;
 
   314         [CATransaction commit];
 
   316         // Find what it's over:
 
   317         [self _findDropTarget: pos];
 
   322 - (void) _findDropTarget: (NSPoint)locationInWindow
 
   324     locationInWindow.x += _dragOffset.x;
 
   325     locationInWindow.y += _dragOffset.y;
 
   326     id<BitHolder> target = (id<BitHolder>) [self hitTestPoint: locationInWindow
 
   327                                              forLayerMatching: layerIsBitHolder
 
   329     if( target == _oldHolder )
 
   331     if( target != _dropTarget ) {
 
   332         [_dropTarget willNotDropBit: _dragBit];
 
   333         _dropTarget.highlighted = NO;
 
   337         CGPoint targetPos = [(CALayer*)target convertPoint: _dragBit.position
 
   338                                                  fromLayer: _dragBit.superlayer];
 
   339         if( [target canDropBit: _dragBit atPoint: targetPos]
 
   340            && [_game canBit: _dragBit moveFrom: _oldHolder to: target] ) {
 
   341             _dropTarget = target;
 
   342             _dropTarget.highlighted = YES;
 
   348 - (void) mouseUp: (NSEvent*)ev
 
   352             // Update the drag tracking to the final mouse position:
 
   353             [self mouseDragged: ev];
 
   354             _dropTarget.highlighted = NO;
 
   355             _dragBit.pickedUp = NO;
 
   357             // Is the move legal?
 
   358             if( _dropTarget && [_dropTarget dropBit: _dragBit
 
   359                                             atPoint: [(CALayer*)_dropTarget convertPoint: _dragBit.position 
 
   360                                                                             fromLayer: _dragBit.superlayer]] ) {
 
   361                 // Yes, notify the interested parties:
 
   362                 [_oldHolder draggedBit: _dragBit to: _dropTarget];
 
   363                 [_game bit: _dragBit movedFrom: _oldHolder to: _dropTarget];
 
   366                 [_dropTarget willNotDropBit: _dragBit];
 
   367                 if( _oldSuperlayer ) {
 
   368                     ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
 
   369                     _dragBit.position = _oldPos;
 
   370                     [_oldHolder cancelDragBit: _dragBit];
 
   372                     [_dragBit removeFromSuperlayer];
 
   376             // Just a click, without a drag:
 
   377             _dropTarget.highlighted = NO;
 
   378             _dragBit.pickedUp = NO;
 
   379             ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
 
   380             [_oldHolder cancelDragBit: _dragBit];
 
   381             if( ! [_game clickedBit: _dragBit] )
 
   393 #pragma mark INCOMING DRAGS:
 
   396 // subroutine to call the target
 
   397 static int tell( id target, SEL selector, id arg, int defaultValue )
 
   399     if( target && [target respondsToSelector: selector] )
 
   400         return (ssize_t) [target performSelector: selector withObject: arg];
 
   406 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
 
   408     _viewDropTarget = [self hitTestPoint: [sender draggingLocation]
 
   409                         forLayerMatching: layerIsDropTarget
 
   411     _viewDropOp = _viewDropTarget ?[_viewDropTarget draggingEntered: sender] :NSDragOperationNone;
 
   415 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
 
   417     CALayer *target = [self hitTestPoint: [sender draggingLocation]
 
   418                         forLayerMatching: layerIsDropTarget 
 
   420     if( target == _viewDropTarget ) {
 
   421         if( _viewDropTarget )
 
   422             _viewDropOp = tell(_viewDropTarget,@selector(draggingUpdated:),sender,_viewDropOp);
 
   424         tell(_viewDropTarget,@selector(draggingExited:),sender,0);
 
   425         _viewDropTarget = target;
 
   426         if( _viewDropTarget )
 
   427             _viewDropOp = [_viewDropTarget draggingEntered: sender];
 
   429             _viewDropOp = NSDragOperationNone;
 
   434 - (BOOL)wantsPeriodicDraggingUpdates
 
   436     return (_viewDropTarget!=nil);
 
   439 - (void)draggingExited:(id <NSDraggingInfo>)sender
 
   441     tell(_viewDropTarget,@selector(draggingExited:),sender,0);
 
   442     _viewDropTarget = nil;
 
   445 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
 
   447     return tell(_viewDropTarget,@selector(prepareForDragOperation:),sender,YES);
 
   450 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
 
   452     return [_viewDropTarget performDragOperation: sender];
 
   455 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
 
   457     tell(_viewDropTarget,@selector(concludeDragOperation:),sender,0);
 
   460 - (void)draggingEnded:(id <NSDraggingInfo>)sender
 
   462     tell(_viewDropTarget,@selector(draggingEnded:),sender,0);