/** <title>NSRulerMarker</title>

   <abstract>Displays a symbol in a NSRulerView.</abstract>

   Copyright (C) 2001 Free Software Foundation, Inc.

   Author: Fred Kiefer <FredKiefer@gmx.de>
   Date: Sept 2001
   
   This file is part of the GNUstep GUI Library.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; see the file COPYING.LIB.
   If not, see <http://www.gnu.org/licenses/> or write to the 
   Free Software Foundation, 51 Franklin Street, Fifth Floor, 
   Boston, MA 02110-1301, USA.
*/ 

#import <Foundation/NSException.h>
#import "AppKit/NSRulerMarker.h"
#import "AppKit/NSRulerView.h"
#import "AppKit/NSEvent.h"
#import "AppKit/NSImage.h"
#import "AppKit/NSApplication.h"
#import "AppKit/NSWindow.h"
#import "AppKit/NSCursor.h"

@implementation NSRulerMarker

- (id)initWithRulerView:(NSRulerView *)aRulerView
         markerLocation:(float)location
		  image:(NSImage *)anImage
	    imageOrigin:(NSPoint)imageOrigin
{
  if (aRulerView == nil || anImage == nil)
    [NSException raise: NSInvalidArgumentException
		format: @"No view or image for ruler marker"];

  self = [super init];
  _isMovable = YES;
  _isRemovable = NO;
  _location = location;
  _imageOrigin = imageOrigin;
  _rulerView = aRulerView;
  ASSIGN(_image, anImage);

  return self;
}

- (void) dealloc
{
  RELEASE(_image);
  TEST_RELEASE(_representedObject);
  
  [super dealloc];
}

- (NSRulerView *)ruler
{
  return _rulerView;
}

- (void)setImage:(NSImage *)anImage
{
  ASSIGN(_image, anImage);
}

- (NSImage *)image
{
  return _image;
}

- (void)setImageOrigin:(NSPoint)aPoint
{
  _imageOrigin = aPoint;
}

- (NSPoint)imageOrigin
{
  return _imageOrigin;
}

- (NSRect)imageRectInRuler
{
  BOOL flipped = [_rulerView isFlipped];
  NSSize size = [_image size];
  NSPoint pointInRuler;
  
  pointInRuler = [[_rulerView clientView] 
                             convertPoint: NSMakePoint(_location, _location)
			     toView: _rulerView];

  if ([_rulerView orientation] == NSHorizontalRuler)
    {
      if (flipped)
        {
          return NSMakeRect(pointInRuler.x - _imageOrigin.x,
                            [_rulerView baselineLocation] - (size.height - _imageOrigin.y),
			    size.width, size.height);
        }
      else
        {
          return NSMakeRect(pointInRuler.x - _imageOrigin.x,
                            [_rulerView baselineLocation] - _imageOrigin.y,
			    size.width, size.height);
	}
    }
  else
    {
      if (flipped)
        {
          return NSMakeRect([_rulerView baselineLocation] - _imageOrigin.x,
                            pointInRuler.y - (size.height - _imageOrigin.y),
			    size.width, size.height);
        }
      else
        {
          return NSMakeRect([_rulerView baselineLocation] - _imageOrigin.x,
                            pointInRuler.y - _imageOrigin.y,
			    size.width, size.height);
        }
    }

  return NSZeroRect;
}

- (float)thicknessRequiredInRuler
{
  NSSize size = [_image size];

  if ([_rulerView orientation] == NSHorizontalRuler)
    {
      /* what is below imageOrigin is on the ruler area and is not counted */
      return size.height - _imageOrigin.y;
    }
  else
    {
      /* what is to the right of imageOrigin is on the ruler area */
      return _imageOrigin.x;
    }
}

- (void)setMovable:(BOOL)flag
{
  _isMovable = flag; 
}

- (BOOL)isMovable
{
  return _isMovable;
}

- (void)setRemovable:(BOOL)flag
{
  _isRemovable = flag;
}

- (BOOL)isRemovable
{
  return _isRemovable;
}

- (void)setMarkerLocation:(float)location
{
  _location = location;
}
 
- (float)markerLocation
{
  return _location;
}

- (void)setRepresentedObject:(id <NSCopying>)anObject
{
  ASSIGN(_representedObject, (id)anObject);
}

- (id <NSCopying>)representedObject
{
  return _representedObject;
}

- (void)drawRect:(NSRect)aRect
{
  NSPoint aPoint;
  NSRect rect;
  
  /* do not draw when dragging, all drawing is done in -trackMouse... */
  if (_isDragging)
    {
      return;
    }
    
  rect = [self imageRectInRuler];
  aPoint = rect.origin;
  if ([_rulerView isFlipped])
    {
      aPoint.y += rect.size.height;
    }
  rect = NSIntersectionRect(aRect, rect);
  if (NSIsEmptyRect(rect))
    return;

  [_image compositeToPoint: aPoint
	  operation: NSCompositeSourceOver];
}

- (BOOL)isDragging
{
  return _isDragging;
}

- (BOOL)trackMouse:(NSEvent *)theEvent adding:(BOOL)adding
{
  NSView *client = [_rulerView clientView];
  NSEvent *newEvent = nil;
  int eventMask = NSLeftMouseDraggedMask | NSLeftMouseUpMask;
  BOOL isFar = NO;
  BOOL askedCanRemove = NO;
  BOOL canRemove = NO;
  BOOL flipped;
  NSPoint mousePositionInRuler;
  NSPoint mousePositionInClient;
  NSPoint mousePositionInWindow;
  NSPoint previousMousePositionInWindow;
  NSPoint mouseOffset;
  float location;
  NSRect drawRect;
  NSRect bounds = [_rulerView bounds];
  NSPoint drawPoint;
  BOOL returnValue = NO;
  NSWindow *window;

  if (adding)
    {
      if ([client respondsToSelector: @selector(rulerView:shouldAddMarker:)]
          && [client rulerView: _rulerView shouldAddMarker: self] == NO)
	return NO;
    }
  else if (!_isMovable && !_isRemovable)
    {
      return NO;
    }
  else if (_isMovable
           && [client respondsToSelector: @selector(rulerView:shouldMoveMarker:)]
           && [client rulerView: _rulerView shouldMoveMarker: self] == NO)
    {
      return NO;
    }

  // I'm being dragged!
  _isDragging = YES;
  RETAIN(self); // make sure we're not dealloc'd if removed from ruler
  [NSCursor hide];
  [_rulerView lockFocus];

  /* cache some values */
  flipped = [_rulerView isFlipped];
  window = [_rulerView window];
  
  mousePositionInWindow = [theEvent locationInWindow];
  previousMousePositionInWindow = mousePositionInWindow;
  mousePositionInRuler = [_rulerView convertPoint: mousePositionInWindow
                                         fromView: nil];

  /* calculate offset of mouse click relative to marker's location
     (to avoid marker bumping when dragging starts) */
  if (adding)
    {
      mouseOffset = NSMakePoint(0, 0);
    }
  else
    {
      NSPoint locationInRuler;
      locationInRuler = [client convertPoint: NSMakePoint(_location, _location)
			              toView: _rulerView];
      if ([_rulerView orientation] == NSHorizontalRuler)
        {
	  mouseOffset = NSMakePoint(locationInRuler.x - mousePositionInRuler.x, 0);
	}
      else
        {
	  mouseOffset = NSMakePoint(0, locationInRuler.y - mousePositionInRuler.y);
	  if (flipped)
	    {
	      mouseOffset.y *= -1;
	    }
	}
    }

  /* cache image without marker and draw marker in position */
  if (adding)
    {
      /* marker is not drawn yet; mouse is in marker's imageOrigin */
      drawRect.size = [_image size];
      drawPoint.x = mousePositionInRuler.x - _imageOrigin.x;
      if (!flipped)
        {
          drawPoint.y = mousePositionInRuler.y - _imageOrigin.y;
          drawRect.origin = drawPoint;
        }
      else
        {
          drawPoint.y = mousePositionInRuler.y + _imageOrigin.y;
          drawRect.origin.x = drawPoint.x;
          drawRect.origin.y = drawPoint.y - drawRect.size.height;
        }
    }
  else
    {
      drawRect = [self imageRectInRuler];
      drawPoint = drawRect.origin;
      if (flipped)
        {
          drawPoint.y += drawRect.size.height;
        }
      /* make marker disappear (-drawRect: does not draw marker when dragged) */  
      [_rulerView drawRect: drawRect];
    }
  [window cacheImageInRect: [_rulerView convertRect: drawRect toView: nil]];
  [_image compositeToPoint: drawPoint
                 operation: NSCompositeSourceOver];
  [window flushWindow];

  /* loop processing events until mouse up */
  while (_isDragging)
    {
      if(newEvent == nil) 
	{
	  newEvent = theEvent;
	  previousMousePositionInWindow = NSMakePoint(0,0); 
	}
      else
	{
	  newEvent = [NSApp nextEventMatchingMask: eventMask
			    untilDate: [NSDate distantFuture]
			    inMode: NSEventTrackingRunLoopMode
			    dequeue: YES];
	}

      switch ([newEvent type]) 
	{
	  case NSLeftMouseDown:
	  case NSLeftMouseDragged:
	      /* take mouse position from outside of event stream
	         and ignore event if in same position as previous event, 
		 to reduce the number of events to process */
              mousePositionInWindow = [window mouseLocationOutsideOfEventStream];
	      if (NSEqualPoints(mousePositionInWindow, previousMousePositionInWindow))
	        {
		  break;
		}
	      previousMousePositionInWindow = mousePositionInWindow;
	      
	      /* offset mouse position to marker's location */
	      mousePositionInWindow.x += mouseOffset.x;
	      mousePositionInWindow.y += mouseOffset.y;
	      
	      /* see if mouse is far from ruler area (to remove marker) */
              mousePositionInRuler = [_rulerView convertPoint: mousePositionInWindow
                                                     fromView: nil];
	      isFar = !NSMouseInRect(mousePositionInRuler, bounds, flipped);
	      
	      /* if it is the first time it's far from the ruler area,
	         see if it can be removed */
	      if (isFar && !askedCanRemove)
	        {
		  if (adding)
		    {
		      canRemove = YES;
		    }
		  else if (!_isRemovable)
		    {
		      canRemove = NO;
		    }
		  else if ([client respondsToSelector: @selector(rulerView:shouldRemoveMarker:)])
                    {
                      canRemove = [client rulerView: _rulerView
		                 shouldRemoveMarker: self];
		    }
		  else
		    {
		      canRemove = YES;
		    }
		  askedCanRemove = YES;
		}

              /* calculate new marker location */
	      mousePositionInClient = [client convertPoint: mousePositionInWindow
                			          fromView: nil];
	      if ([_rulerView orientation] == NSHorizontalRuler)
	        {
	          location = mousePositionInClient.x;
	        }
	      else
	        {
	          location = mousePositionInClient.y;
	        }
		
	      /* give client a chance to change location */
	      if (adding && !isFar)
	        {
	          if ([client respondsToSelector: @selector(rulerView:willAddMarker:atLocation:)])
                    {
                      location = [client rulerView: _rulerView
                                     willAddMarker: self
                                        atLocation: location];
	            }
	        }
	      else if (_isMovable && !(isFar && canRemove))
	        {
	          if ([client respondsToSelector: @selector(rulerView:willMoveMarker:toLocation:)])
                    {
                      location = [client rulerView: _rulerView
                                    willMoveMarker: self
                                        toLocation: location];
	            }
	        }
	      _location = location;
	      
	      /* calculate position to draw marker */
	      drawRect = [self imageRectInRuler];
	      drawPoint = drawRect.origin;
	      if (flipped)
	        {
                  drawPoint.y += drawRect.size.height;
	        }
	      if (isFar && canRemove)
	        {
		  if ([_rulerView orientation] == NSHorizontalRuler)
		    {
		      float offset;
		      offset = mousePositionInRuler.y - [_rulerView baselineLocation];
		      drawPoint.y += offset;
		      drawRect.origin.y += offset;
		    }
                  else
		    {
		      float offset;
		      offset = mousePositionInRuler.x - [_rulerView baselineLocation];
		      drawPoint.x += offset;
		      drawRect.origin.x += offset;
		    }
	        }
		
	      /* undraw marker and redraw in new position */
	      [window restoreCachedImage];
              [window cacheImageInRect: [_rulerView convertRect: drawRect
                                                         toView: nil]];
	      [_image compositeToPoint: drawPoint
                             operation: NSCompositeSourceOver];
	      [window flushWindow];
	      break;

	  case NSLeftMouseUp:
	      if (adding)
	        {
	          if (isFar)
	            {
                      /* do nothing, it won't be added */
		      [window restoreCachedImage];
	              returnValue = NO;
	            }
	          else
	            {
                      /* marker is added to ruler view; inform client */
		      [window discardCachedImage];
	              [_rulerView addMarker:self];
	              if ([client respondsToSelector: @selector(rulerView:didAddMarker:)])
                	{
                	  [client rulerView: _rulerView didAddMarker: self];
	        	}
	              returnValue = YES;
	            }
	        }
	      else
	        {
	          if (isFar && _isRemovable && canRemove)
	            {
                      /* remove marker from ruler and inform client */
                      /* this could result in marker being dealloc'd; that's
                         why there is a retain before the loop */
		      [window restoreCachedImage];
	              [_rulerView removeMarker:self];
	              if ([client respondsToSelector: @selector(rulerView:didRemoveMarker:)])
                	{
                	  [client rulerView: _rulerView didRemoveMarker: self];
	        	}
	              returnValue = YES;
	            }
	          else if (_isMovable)
	            {
                      /* inform client that marker has been moved */
		      [window discardCachedImage];
	              if ([client respondsToSelector: @selector(rulerView:didMoveMarker:)])
                	{
                	  [client rulerView: _rulerView didMoveMarker: self];
	        	}
	              returnValue = YES;
	            }
	          else
	            {
		      [window restoreCachedImage];
	              returnValue = NO;
	            }
	        }
	      _isDragging = NO;
	      break;
	  default:
	      break;
        }
    }
  [_rulerView unlockFocus];
  [NSCursor unhide];
  [window flushWindow];
  RELEASE(self); // was retained when dragging started
  return returnValue;
}

// NSCopying protocol
- (id) copyWithZone: (NSZone*)zone
{
  NSRulerMarker *new = (NSRulerMarker*)NSCopyObject (self, 0, zone);

  new->_image = [_image copyWithZone: zone];
  new->_isDragging = NO;
  return new;
}

// NSCoding protocol
- (void) encodeWithCoder: (NSCoder*)aCoder
{
  [aCoder encodeObject: _rulerView];
  [aCoder encodeObject: _image];
  [aCoder encodeConditionalObject: _representedObject];
  [aCoder encodePoint: _imageOrigin];
  [aCoder encodeValueOfObjCType: @encode(float) at: &_location];
  [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_isMovable];
  [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_isRemovable];
}

- (id) initWithCoder: (NSCoder*)aDecoder
{
  _rulerView = [aDecoder decodeObject];
  _image = [aDecoder decodeObject];
  _representedObject = [aDecoder decodeObject];
  _imageOrigin = [aDecoder decodePoint];
  [aDecoder decodeValueOfObjCType: @encode(float) at: &_location];
  [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_isMovable];
  [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_isRemovable];

  return self;
}

// NSObject protocol

@end





