/*
   NSSlider.m

   NSlider and NSSliderCell classes	

   Copyright (C) 1996 Free Software Foundation, Inc.

   Author:  Felipe A. Rodriguez <far@ix.netcom.com>
   Date: 	August 1998
   
   This file is part of the mySTEP Library and is provided
   under the terms of the GNU Library General Public License.
*/

#import <AppKit/NSEvent.h>
#import <AppKit/NSSlider.h>
#import <AppKit/NSSliderCell.h>
#import <AppKit/NSSliderCell.h>
#import <AppKit/NSTextFieldCell.h>
#import <AppKit/NSWindow.h>
#import <AppKit/NSColor.h>
#import <AppKit/NSImage.h>
#import <AppKit/NSApplication.h>
#import <AppKit/NSBezierPath.h>


// Class global variables
static Class _sliderCellClass;


//*****************************************************************************
//
// 		NSSliderCell 
//
//*****************************************************************************

@implementation NSSliderCell

- (id) init
{
	if((self=[self initImageCell:nil]))
		{
		_altIncrementValue = -1.0;
		_isVertical = NO;
		_initializedVertical=!_isVertical;	// force initialization
		_minValue = 0;
		_maxValue = 1;
		_contents = [[NSNumber numberWithFloat:0.0] retain];
		[self setBordered:YES];
		[self setBezeled:YES];	
		_knobCell = [NSCell new];
		}
	return self;
}

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

- (void) drawBarInside:(NSRect)rect flipped:(BOOL)flipped
{
	// FIXME: we can cache the path
	NSBezierPath *p=[NSBezierPath bezierPath];
	int i;
	// note: we must draw our own background or moving the knob would mix up everything
	[[NSColor controlBackgroundColor] set];						
	NSRectFill(rect);									// draw the bar background
	[[NSColor blackColor] set];
	for(i=0; i<_numberOfTickMarks; i++)
		NSRectFill([self rectOfTickMarkAtIndex:i]);		// draw a tick mark by filling
	if(_isVertical)
		{
		float x=rect.origin.x+rect.size.width/2.0-2.0;
		[p moveToPoint:NSMakePoint(x, rect.origin.y+5.0)];
		[p lineToPoint:NSMakePoint(x, rect.origin.y+rect.size.height-5.0)];
		}
	else
		{
		float y=rect.origin.y+rect.size.height/2.0+2.0;
		[p moveToPoint:NSMakePoint(rect.origin.x+5.0, y)];
		[p lineToPoint:NSMakePoint(rect.origin.x+rect.size.width-5.0, y)];
		}
	[p setLineWidth:5.0];
	[p setLineCapStyle:NSRoundLineCapStyle];	// round line cap
	[p stroke];
}

- (NSRect) knobRectFlipped:(BOOL)flipped
{
	NSImage *image = [_knobCell image];
	NSSize size;
	NSPoint origin;
	float floatValue;

	if(!_controlView || fabs(_maxValue - _minValue) < 1e-6)
		return NSZeroRect;	// can't determine properly
	if(_isVertical && flipped)
		floatValue = _maxValue + _minValue - [self floatValue];
	else
		floatValue = [self floatValue];
	floatValue = (floatValue - _minValue) / (_maxValue - _minValue);	// scale from 0.0 to 1.0
	if(floatValue < 0.0) floatValue=0.0;
	if(floatValue > 1.0) floatValue=1.0;
#if 0
	NSLog(@"floatValue=%lf", floatValue);
#endif
	size = [image size];
	if (_isVertical) 
		{
		origin.x = 0;
		origin.y = ((_trackRect.size.height - size.height) * floatValue) + 2;
		}
	else 
		{
		origin.x = ((_trackRect.size.width - size.width) * floatValue) + 0;
		if(flipped)
			origin.y=_trackRect.size.height-2;
		else
			origin.y = 2;
		}
	if(_numberOfTickMarks > 0)
		{
		if(_tickMarkPosition == NSTickMarkAbove)	// == NSTickMarkLeft
			;
		// adjust for tick marks
		}
	return NSMakeRect (origin.x, origin.y, size.width, size.height);  
}

- (void) drawKnob
{
	if(_controlView)
		[self drawKnob:[self knobRectFlipped:[_controlView isFlipped]]];
}

- (void) drawKnob:(NSRect)knobRect
{
#if 0
	NSLog(@"knobRect=%@", NSStringFromRect(knobRect));
#endif
	[_knobCell drawInteriorWithFrame:knobRect inView:_controlView];
}

- (void) drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView
{
	_controlView=controlView;	// remember
	if(_sliderType == NSCircularSlider)
		;
	if(_initializedVertical != _isVertical)
		{ // needs to adjust
		NSImage *image;
		float size;
		if((_initializedVertical = _isVertical))
			{
			image = [NSImage imageNamed:@"GSSliderVert"];
			size = cellFrame.size.width-2.0;
			}
		else 
			{
			image = [NSImage imageNamed:@"GSSliderHoriz"];
			size = cellFrame.size.height-2.0;
			}
		if(_numberOfTickMarks > 0)
			{
			if(_tickMarkPosition == NSTickMarkAbove)	// == NSTickMarkLeft
				;
			}
		image=[image copy];	// make an independent copy
		[image setSize:NSMakeSize(size, size)];	// make it square according to orientation
		[_knobCell setImage:image];
#if 0
		NSLog(@"knob cell %@", _knobCell);
		NSLog(@"knob image %@", image);
#endif
		[image release];
		}

	_trackRect = cellFrame;
	
//	if (_titleCell)
//		[_titleCell drawInteriorWithFrame:cellFrame inView:controlView];
	
	[self drawBarInside:cellFrame flipped:[controlView isFlipped]];
	[self drawKnob];
}

- (float) knobThickness
{
	NSSize size = [[_knobCell image] size];
	return _isVertical ? size.height : size.width;
}

- (void) setKnobThickness:(float)thickness
{
NSImage* image = [_knobCell image];
NSSize size = [image size];

	if (_isVertical)
		size.height = thickness;
	else
		size.width = thickness;
	
	[image setSize:size];
}

- (void) setAltIncrementValue:(double)increment
{
	_altIncrementValue = increment;
}

- (void) setMinValue:(double)aDouble
{
	_minValue = aDouble;
	// check if we should modify the value
}

- (void) setMaxValue:(double)aDouble
{
	_maxValue = aDouble;
	// check if we should modify the value
}

- (int) isVertical						{ return _isVertical; }
- (double) altIncrementValue			{ return _altIncrementValue; }
+ (BOOL) prefersTrackingUntilMouseUp	{ return YES; }
- (NSRect) trackRect					{ return _trackRect; }
- (double) minValue						{ return _minValue; }
- (double) maxValue						{ return _maxValue; }

- (void) setTickMarkPosition:(NSTickMarkPosition) pos; { _tickMarkPosition=pos; }
- (NSTickMarkPosition) tickMarkPosition; { return _tickMarkPosition; }

- (double) tickMarkValueAtIndex:(int) index;
{
	if(_numberOfTickMarks == 0)
		return _minValue;
	return _minValue+(index*(_maxValue-_minValue))/_numberOfTickMarks;
}

- (NSRect) rectOfTickMarkAtIndex:(int) index;
{
	NSRect r=NSZeroRect;
	if(_isVertical)
		{
		r.origin.x=5.0;	// make dependent on left/right
		r.origin.y=index*(_trackRect.size.height/(_numberOfTickMarks-1));
		r.size=NSMakeSize(5.0, 1.0);
		}
	else
		{
		r.origin.x=index*(_trackRect.size.width/(_numberOfTickMarks-1));
		r.origin.y=5.0;	// make dependent on above/below
		r.size=NSMakeSize(1.0, 5.0);
		}
	return r;
}

- (id) initWithCoder:(NSCoder*)decoder
{
	self = [super initWithCoder:decoder];
	if([decoder allowsKeyedCoding])
		{
		_minValue=[decoder decodeDoubleForKey:@"NSMinValue"];
		_maxValue=[decoder decodeDoubleForKey:@"NSMaxValue"];
		_isVertical=[decoder decodeBoolForKey:@"NSVertical"];
		_initializedVertical=!_isVertical;	// force initialization of image
		_altIncrementValue=[decoder decodeDoubleForKey:@"NSAltIncValue"];
		[self setFloatValue:[decoder decodeFloatForKey:@"NSValue"]];
//		[self setDoubleValue:[decoder decodeDoubleForKey:@"NSValue"]];
		_sliderType=[decoder decodeIntForKey:@"NSSliderType"];
		_numberOfTickMarks=[decoder decodeIntForKey:@"NSNumberOfTickMarks"];
		_allowTickMarkValuesOnly=[decoder decodeBoolForKey:@"NSAllowsTickMarkValuesOnly"];
		_tickMarkPosition=[decoder decodeIntForKey:@"NSTickMarkPosition"];
		return self;
		}
	[decoder decodeValuesOfObjCTypes:"ff@f", &_minValue, &_maxValue, 
										&_contents, &_altIncrementValue];
	return self;
}

- (void) encodeWithCoder:(NSCoder*)coder
{
	[coder encodeValuesOfObjCTypes:"ff@f", _minValue, _maxValue, 
									_contents, _altIncrementValue];
}

// deprecated

- (id) titleCell						{ return nil; }
- (NSColor*) titleColor					{ return nil; }
- (NSFont*) titleFont					{ return nil; }
- (void) setTitle:(NSString*)title		{ [super setTitle:title]; }	// default implementation
- (NSString*) title						{ return nil; }
- (void) setTitleCell:(NSCell*)aCell	{ NIMP; return; }
- (void) setTitleColor:(NSColor*)color	{ NIMP; return; }
- (void) setTitleFont:(NSFont*)font		{ NIMP; return; }

- (double) closestTickMarkValueToValue:(double) value;
{
	if(_numberOfTickMarks > 1 && _allowTickMarkValuesOnly)
		{ // round to nearest tick mark value
		double dist=(_maxValue-_minValue)/(_numberOfTickMarks-1);	// distance between tick marks
		double n=(value-_minValue)/dist;
		n=floor(n+0.5);	// round to nearest integer
		value=n*dist+_minValue;
		}
	return value;
}

- (int) indexOfTickMarkAtPoint:(NSPoint) point;
{
	return NSNotFound;
}

- (BOOL) allowsTickMarkValuesOnly;					{ return _allowTickMarkValuesOnly; }
- (int) numberOfTickMarks;							{ return _numberOfTickMarks; }
- (void) setAllowsTickMarkValuesOnly:(BOOL) flag;	{ _allowTickMarkValuesOnly=flag; }
- (void) setNumberOfTickMarks:(int) num;			{ _numberOfTickMarks=num > 0?num:0; }
- (void) setSliderType:(NSSliderType) sliderType;	{ _sliderType=sliderType; }
- (NSSliderType) sliderType;						{ return _sliderType; }

@end /* NSSliderCell */

//*****************************************************************************
//
// 		NSSlider 
//
//*****************************************************************************

@implementation NSSlider

+ (void) initialize
{
	if (self == [NSSlider class])
		_sliderCellClass = [NSSliderCell class];		// Set our cell class
}

+ (void) setCellClass:(Class)class			{ _sliderCellClass = class; }
+ (Class) cellClass							{ return _sliderCellClass; }

- (id) initWithFrame:(NSRect)frameRect
{
	[super initWithFrame:frameRect];
	
	[self setCell:[[_sliderCellClass new] autorelease]];		// set our cell
	[_cell setState:1];

	return self;
}

- (NSImage *) image							{ return [_cell image]; }
- (int) isVertical							{ return [_cell isVertical]; }
- (float) knobThickness						{ return [_cell knobThickness]; }
- (void) setImage:(NSImage*)backgroundImg	{ [_cell setImage:backgroundImg]; }
- (void) setKnobThickness:(float)aFloat		{ [_cell setKnobThickness:aFloat];}
- (void) setTitle:(NSString *)aString		{ [_cell setTitle:aString]; }
- (void) setTitleCell:(NSCell *)aCell		{ [_cell setTitleCell:aCell]; }
- (void) setTitleColor:(NSColor *)aColor	{ [_cell setTitleColor:aColor]; }
- (void) setTitleFont:(NSFont *)fontObj		{ [_cell setTitleFont:fontObj]; }
- (NSString *) title						{ return [_cell title]; }
- (id) titleCell							{ return [_cell titleCell]; }
- (NSColor *) titleColor					{ return [_cell titleColor]; }
- (NSFont *) titleFont						{ return [_cell titleFont]; }
- (BOOL) allowsTickMarkValuesOnly;			{ return [_cell allowsTickMarkValuesOnly]; }
- (double) altIncrementValue				{ return [_cell altIncrementValue]; }
- (double) closestTickMarkValueToValue:(double) value;	{ return [_cell closestTickMarkValueToValue:value]; }
- (int) indexOfTickMarkAtPoint:(NSPoint) point;	{ return [_cell indexOfTickMarkAtPoint:point]; }
- (double) maxValue							{ return [_cell maxValue]; }
- (double) minValue							{ return [_cell minValue]; }
- (int) numberOfTickMarks;					{ return [_cell numberOfTickMarks]; }
- (NSRect) rectOfTickMarkAtIndex:(int) index;	{ return [_cell rectOfTickMarkAtIndex:index]; }
- (void) setAllowsTickMarkValuesOnly:(BOOL)f	{ [_cell setAllowsTickMarkValuesOnly:f]; }
- (void) setAltIncrementValue:(double)aDouble	{ [_cell setAltIncrementValue:aDouble]; }
- (void) setMaxValue:(double)aDouble		{ [_cell setMaxValue:aDouble]; }
- (void) setMinValue:(double)aDouble		{ [_cell setMinValue:aDouble]; }
- (void) setNumberOfTickMarks:(int) num;	{ [_cell setNumberOfTickMarks:num]; }
- (void) setTickMarkPosition:(NSTickMarkPosition) pos;	{ [_cell setTickMarkPosition:pos]; }
- (NSTickMarkPosition) tickMarkPosition;	{ return [_cell tickMarkPosition]; }
- (double) tickMarkValueAtIndex:(int) index;	{ return [_cell tickMarkValueAtIndex:index]; }
- (NSRect) trackRect						{ return [_cell trackRect]; }
- (BOOL) acceptsFirstMouse:(NSEvent*)event	{ return YES; }

- (void) drawRect:(NSRect)rect	
{ 
	[_cell drawWithFrame:rect inView:self];
}

- (float) _floatValueForMousePoint:(NSPoint)point knobRect:(NSRect)knobRect
{
NSRect slotRect = [_cell trackRect];
BOOL isVertical = [_cell isVertical];
float minValue = [_cell minValue];
float maxValue = [_cell maxValue];
float floatValue = 0;
float position;
										// Adjust the point to lie inside the 
	if (isVertical) 					// knob slot. We don't have to worry
		{								// whether the view is flipped or not.
		if (point.y < slotRect.origin.y + knobRect.size.height / 2)
			position = slotRect.origin.y + knobRect.size.height / 2;
    	else 
			if (point.y <= (position = NSMaxY(slotRect) -NSHeight(knobRect)/2))
      			position = point.y;
													// Compute the float value 
    	floatValue = (position - (slotRect.origin.y + knobRect.size.height/2))
		  				/ (slotRect.size.height - knobRect.size.height);
   		if ([self isFlipped])
      		floatValue = 1 - floatValue;
  		}
	else 											// Adjust the point to lie
		{						 					// inside the knob slot 
		if (point.x < slotRect.origin.x + knobRect.size.width / 2)
			position = slotRect.origin.x + knobRect.size.width / 2;
		else 
			if (point.x <= (position = NSMaxX(slotRect) - NSWidth(knobRect)/2))
      			position = point.x;
													// Compute the float value
						 							// given the knob size
    	floatValue = (position - (slotRect.origin.x + knobRect.size.width / 2))
		  				/ (slotRect.size.width - knobRect.size.width);
  		}

	return floatValue * (maxValue - minValue) + minValue;
}

- (void) trackKnob:(NSEvent*)event knobRect:(NSRect)knobRect
{
NSEventType eventType = NSLeftMouseDown;
NSPoint current, previous;
BOOL isContinuous = [self isContinuous];
float oldFloatValue = [_cell floatValue];
NSDate *distantFuture = [NSDate distantFuture];
id target = [_cell target];
SEL action = [_cell action];

// Why do we need periodic events at all? Maybe to handle drawing events triggered by the action?
	[NSEvent startPeriodicEventsAfterDelay:0.05 withPeriod:0.05];	// autorepeat

	while((eventType = [event type]) != NSLeftMouseUp) 
		{
		if(eventType != NSPeriodic)
			current = [event locationInWindow];
		else
			{
			if(current.x != previous.x || current.y != previous.y) 
				{
				NSPoint p = [self convertPoint:current fromView:nil];
				float v = [self _floatValueForMousePoint:p knobRect:knobRect];

				previous = current;
				if (v != oldFloatValue) 
					{ // has really changed
					oldFloatValue = v;
					if([_cell allowsTickMarkValuesOnly])
						v=[_cell closestTickMarkValueToValue:v]; // round to nearest tick mark
					[_cell setFloatValue:v];
					[_cell drawWithFrame:bounds inView:self];
					[window flushWindow];
					if(isContinuous)
	  					[target performSelector:action withObject:self];
      				}
      			knobRect.origin = p;
				}
			}

		event = [NSApp nextEventMatchingMask:GSTrackingLoopMask
								   untilDate:distantFuture 
									  inMode:NSEventTrackingRunLoopMode
									 dequeue:YES];
		}
 
	if(!isContinuous)	// If the control is not continuous send the action at the end of the drag
		[target performSelector:action withObject:self];
	[NSEvent stopPeriodicEvents];
}

- (void) mouseDown:(NSEvent *)event
{
NSPoint location = [self convertPoint:[event locationInWindow]fromView:nil];
NSRect rect = [_cell knobRectFlipped:[self isFlipped]];

	[self lockFocus];
	NSRectClip(bounds);
														// Mouse is not on the 
	if (![self mouse:location inRect:rect]) 			// knob, move knob to
		{												// the mouse position 
		float value = [self _floatValueForMousePoint:location knobRect:rect];
	
		[_cell setFloatValue:value];
		if([self isContinuous])
			[[_cell target] performSelector:[_cell action] withObject:self];
		[_cell drawWithFrame:[self bounds] inView:self];
		[window flushWindow];
		}
	
	[self trackKnob:event knobRect:rect];
	[self unlockFocus];
}

@end /* NSSlider */
