//
// Time-stamp: <94/12/30 19:09:11 stephan>
//
// VRulerView.m
// Project: Rulers
//
// Stephan Wacker
// 93-02-11


#import "VRulerView.h"
#import "VRulerControl.h"
#import "RulerPreferencesControl.h"
#import "math.h"			// ceil()


@implementation VRulerView


- initFrame: (const NXRect *) frameRect
// Initialize yourself after instance has been created.
{
    NXSize	offset;
    
    [super initFrame: frameRect];
    
    // Move origin (from left bottom corner) up
    // by 80% of frame height (rounded up to an integer).
    offset.height = ceil( 0.8 * NX_HEIGHT( frameRect ) );
    offset.width = 0.0;
    [self moveOrigin: &offset];
    
    // Initially, the ``valid'' cached image is a rectangle of zero width
    // at the very bottom edge of the ruler view.
    validRect.size.width = NX_WIDTH( frameRect );
    imageOrigin.y = origin.y;
    
    return self;
}



#if 0
- drawSelf: (const NXRect *) rects : (int) rectCount
// Draw the marks in the ruler image.
// Background is drawn by super.
{
    NXCoord		y;
    int			k;
    char		str[40];
    NXCoord		tick_baselength, tick_length;
    const TickMark	*mark;
    BOOL		displayLabels = [control labelsMode];
    int			marksPosition = [control vMarksMode];
    int			posNumDirection = [control vPosNumMode];
    NXFontMetrics	*metrics = [markFont metrics];	// font dimensions
    float		capHeight = metrics->capHeight * [markFont pointSize];

#if 0
    [super drawSelf: rects : rectCount];
#else
    NXSetColor( [control color] );
    NXRectFill( rects );
#endif
    
    tick_baselength = 18.0;			// main mark length
    PSsetlinewidth( 0.25 );			// thin lines
    [markFont set];
    
    // Process every set of marks.
    //
    for( mark = [self tickMarkSystem]; mark->distance > 0.0; mark++ )
    {
	tick_length = mark->length * tick_baselength;
	
	// start with
	//  y = k * mark->distance + origin.y,
	//  y >= NX_Y(&bounds)
	for(
	  k = (int) ceil( (NX_Y(&bounds)-origin.y)/mark->distance ),
	   y = k * mark->distance + origin.y,
	   k *= mark->value;
	  y <= NX_MAXY(&bounds);
	  k += mark->value, y += mark->distance )
	{
	    PSnewpath();
	    if( marksPosition & VMARKS_LEFT ) {
		PSmoveto( NX_X(&bounds), y );
		PSrlineto( tick_length, 0.0 );	// a tick mark
	    }
	    if( marksPosition & VMARKS_RIGHT ) {
		PSmoveto( NX_MAXX(&bounds), y );
		PSrlineto( -tick_length, 0.0 );	// a tick mark
	    }
	    NXSetColor( [control marksColor] );
	    PSstroke();				// draw one tick mark
	    
	    if( displayLabels ) {
		if( mark->value > 0 )
		{
		    NXRect markFrame;
		    
		    sprintf( str, "%d",
		      k < 0 ? ((posNumDirection & POSNUM_DOWN) ? -k : k)
		            : ((posNumDirection & POSNUM_UP  ) ? k : -k)
		    );
		    
		    NX_WIDTH(&markFrame) = [markFont getWidthOf: str] + 4.0;
		    NX_HEIGHT(&markFrame) = capHeight + 4.0;
		    NX_X(&markFrame) = 0.5 * (NX_WIDTH(&bounds) - NX_WIDTH(&markFrame));
		    NX_Y(&markFrame) = ceil( y - 0.5 * NX_HEIGHT(&markFrame) );
		    
		    NXSetColor( [control color] );	// background of labels
		    NXRectFill( &markFrame );
		    
		    PSmoveto( NX_X(&markFrame) + 2.0, NX_Y(&markFrame) + 2.0 );
		    NXSetColor( [control marksColor] );
		    PSshow( str );
		}
		else if( mark->value < 0 )
		{
		    // display a fraction like `3/8'
		    // !toDo!
		}
	    }
	}
    }
    
    return self;
}
#endif


- drawMarks: (const NXRect *) aRect
// Draw the marks in the ruler image.
// aRect is given in Ruler coordinates.
// Background is drawn by super.
// Drawing is lockFocused in cachedImage.
{
    NXCoord		y;
    int			k;
    char		str[40];
    NXCoord		tick_baselength, tick_length;
    const TickMark	*mark;
    BOOL		displayLabels = [control labelsMode];
    int			marksPosition = [control vMarksMode];
    int			posNumDirection = [control vPosNumMode];
    NXFontMetrics	*metrics = [markFont metrics];	// font dimensions
    float		capHeight = metrics->capHeight * [markFont pointSize];

    
    tick_baselength = 18.0;			// main mark length
    PSsetlinewidth( 0.25 );			// thin lines
    [markFont set];
    
    // Process every set of marks.
    //
    for( mark = [self tickMarkSystem]; mark->distance > 0.0; mark++ )
    {
	tick_length = mark->length * tick_baselength;
	
	// start with
	//  y = k * mark->distance + imageOrigin.y,
	//  y < NX_Y(&bounds)
	for(
	  k = (int) ceil( (NX_Y(aRect)-imageOrigin.y)/mark->distance ) - 1,
	   y = k * mark->distance + imageOrigin.y + 0.25,
	   k *= mark->value;
	  y < NX_MAXY(aRect) + mark->distance;
	  k += mark->value, y += mark->distance )
	{
	    PSnewpath();
	    if( marksPosition & VMARKS_LEFT ) {
		PSmoveto( NX_X(&bounds), y );
		PSrlineto( tick_length, 0.0 );	// a tick mark
	    }
	    if( marksPosition & VMARKS_RIGHT ) {
		PSmoveto( NX_MAXX(&bounds), y );
		PSrlineto( -tick_length, 0.0 );	// a tick mark
	    }
	    NXSetColor( [control marksColor] );
	    PSstroke();				// draw one tick mark
	    
	    if( displayLabels ) {
		if( mark->value > 0 )
		{
		    NXRect markFrame;
		    
		    sprintf( str, "%d",
		      k < 0 ? ((posNumDirection & POSNUM_DOWN) ? -k : k)
		            : ((posNumDirection & POSNUM_UP  ) ? k : -k)
		    );
		    
		    NX_WIDTH(&markFrame) = [markFont getWidthOf: str] + 4.0;
		    NX_HEIGHT(&markFrame) = capHeight + 4.0;
		    NX_X(&markFrame) = 0.5 * (NX_WIDTH(&bounds) - NX_WIDTH(&markFrame));
		    NX_Y(&markFrame) = ceil( y - 0.5 * NX_HEIGHT(&markFrame) );
		    
		    NXSetColor( [control color] );	// background of labels
		    NXRectFill( &markFrame );
		    
		    PSmoveto( NX_X(&markFrame) + 2.0, NX_Y(&markFrame) + 2.0 );
		    NXSetColor( [control marksColor] );
		    PSshow( str );
		}
		else if( mark->value < 0 )
		{
		    // display a fraction like `3/8'
		    // !toDo!
		}
	    }
	}
    }
    
    return self;
}



- (NXCoord) constrainDistance: (NXCoord) distance
                        frame: (NXRect *) frameRect
// PRIVATE
// Change the frameRect's height by distance.
// Observe minimum and maximum sizes.
// Answer actual height change.
{
    NXSize	minSize, maxSize;
    NXCoord	minDist, maxDist;
    NXCoord	result = distance + off;
    
    [window getMinSize: &minSize];
    [window getMaxSize: &maxSize];
    
    maxDist = maxSize.height - NX_HEIGHT( frameRect );
    minDist = minSize.height - NX_HEIGHT( frameRect );
    off = 0;
    
    if( result < minDist )
    {
	off = result - minDist;
	result = minDist;
    }
    if( result > maxDist )
    {
	off = result - maxDist;
	result = maxDist;
    }
    
    if( result * distance < 0.0 )	// cancel opposite direction
    {
	off += result;
	result = 0.0;
    }
    
    NX_HEIGHT( frameRect ) += result;
    
    return result;
}



- sizeTop: (const NXSize *) delta
// Change the window's height by delta (at the top).
// Positive distance increases height.
{
    NXCoord	distance = delta->height;
    
    if( !(distance = [self constrainDistance: distance frame: &rubberRect]) )
	return nil;				// size is not changed
    
    [self drawRubberband];
    
    return self;
}



- sizeBottom: (const NXSize *) delta
// Change the window's height by delta (at the bottom).
// Positive distance decreases height.
{
    NXCoord	distance = -delta->height;
    
    if( !(distance = [self constrainDistance: distance frame: &rubberRect]) )
	return nil;				// size is not changed
    
    NX_Y( &rubberRect ) -= distance;		// move bottom edge
    origin.y += distance;			// adjust local origin
    
    [self drawRubberband];
    
    return self;
}


@end
