// Ruler.m
// A general ruler class designed to be used with the RuledScrollView class 

#import <dpsclient/psops.h>
#import <libc.h>				// for atoi and strrchr
#import <math.h>
#import <appkit/Font.h>
#import <appkit/ScrollView.h>
#import <appkit/Application.h>	// for NXApp method

#import "RuledScrollView.h"		// for retrieving rulers

#import "Ruler.h"
#define DEFAULTSCALESPACING (72*0.39)

@implementation Ruler


/* instance methods */
/* Initializing and freeing an instance */
- initFrame:(NXRect *)frameRect
{
    [super initFrame:frameRect];
    
	scaleSpacing = DEFAULTSCALESPACING;
	orientation = HORIZONTAL;
	inverted = NO;
	mirrored = NO;
	scale = 1.0;
	scaleOffset = 0;
	border = 0.0;
	scaleSpacing = DEFAULTSCALESPACING;
	alignment = NX_CENTERED;
	leftOrBottomMargin = 0;
	rightOrTopMargin = 0;
	backgroundColor = NXConvertRGBToColor(1.0,1.0,1.0);
	backgroundGray = NX_LTGRAY;
	draggableRuler = NO;
	units = malloc(2*sizeof(char));
	strcpy(units,"\0");
	
    font = [Font newFont:"Helvetica" size:12 style:0 matrix:NX_IDENTITYMATRIX];
    NXTextFontInfo(font, &ascender, &descender, &lineHeight);

    return self;
}

- free
{
	if (units) free(units);
	return [super free];
}

/* Drawing the view */
- drawSelf:(NXRect *)rects :(int)rectCount
{
	char buf[25];
	float r,g,b;
	int	j,start,end;
	float	x0,y0,x1,y1,measure, halfWidth;
	float	vtextOffset,htextOffset;

	x0 = rects[0].origin.x;
	y0 = rects[0].origin.y;
	x1 = x0 + rects[0].size.width;
	y1 = y0 + rects[0].size.height;

	NXConvertColorToRGB(backgroundColor, &r, &g, &b);
	if ((r!=1.0) || (g!=1.0) || (b!=1.0)) {
		PSsetrgbcolor(r,g,b);
		}
	else
		PSsetgray(backgroundGray);
    NXRectFill(&(rects[0]));

	[font set];
  // draw a line along the edge of the ruler next to the 'rulered view'
	PSsetgray(NX_BLACK);
	PSsetlinewidth(0.0);
	if (orientation == HORIZONTAL) {
		if (inverted) {
			PSmoveto(x0,1);
			PSlineto(x1,1);
			}
		else {
			PSmoveto(x0,bounds.size.height);
			PSlineto(x1,bounds.size.height);
			}
		}
	else {
		if (inverted) {
			PSmoveto(0,y0);
			PSlineto(0,y1);
			}
		else {
			PSmoveto(bounds.size.width-1,y0);
			PSlineto(bounds.size.width-1,y1);
			}
		}
	PSstroke();

  // draw measures

	if (orientation == HORIZONTAL) {
		start = (int)floor((double)(x0/scaleSpacing))-2;
		end = (int)ceil((double)(x1/scaleSpacing))+1;
		}
	else {
		start = (int)floor((double)(y0/scaleSpacing))-2;
		end = (int)ceil((double)(y1/scaleSpacing))+1;
		}
	for(j=start; j<=end; j++) {
		measure = j*scale+scaleOffset;

		sprintf(buf,"%.4g",measure);
		strcatn(buf,units,23-strlen(buf));
		halfWidth = [font getWidthOf:buf]/2;
		switch (alignment) {
			case NX_LEFTALIGNED: 
				htextOffset = 2;
				break;
			case NX_RIGHTALIGNED: 
				if (orientation == HORIZONTAL) 
					htextOffset = -2-(halfWidth*2);
				else 
					htextOffset =  bounds.size.width-2-(halfWidth*2);
				break;
			default:	// default is centered
				if (orientation == HORIZONTAL) 
					htextOffset = -halfWidth;
				else 
					htextOffset =  bounds.size.width/2-halfWidth;
			}
		if (orientation == HORIZONTAL) {
			if (inverted)
				vtextOffset = TEXTBASELINE;
			else
				vtextOffset = bounds.size.height - lineHeight;
			if (vtextOffset < TEXTBASELINE) 
				vtextOffset = TEXTBASELINE;
			PSmoveto(j*scaleSpacing+leftOrBottomMargin+htextOffset+border, vtextOffset);
			}
		else
			PSmoveto(htextOffset, j*scaleSpacing+leftOrBottomMargin-(lineHeight/2)+border);
		if (j>=0)
			PSshow(buf);
	}	

    return self;
}

/* Event handling */
- (BOOL) acceptsFirstMouse 
{
    return YES;
}

- mouseDown:(NXEvent *)theEvent
{
    id		rulerWindow;
	int		oldMask, looping = YES;
    NXRect	windowRect,tempRect,visibleRect;
    NXPoint	offset, mouseLocation;
	BOOL	shiftHeld = NO;
	
	if (!draggableRuler)
		return self;
	if (theEvent->flags & NX_SHIFTMASK) shiftHeld = YES;
        
  //	if(theEvent->data.mouse.click == 2)		
	/* action for a double-click event */
    
    /* convert the visible rect to screen-based coordinates */
	[self getVisibleRect:(NXRect *)&windowRect];
    [self convertRect:&windowRect toView:nil];
    [window convertBaseToScreen:&windowRect.origin];

  /* adjust for width of outline */
	windowRect.size.width += 2;
	windowRect.size.height += 2;
	windowRect.origin.x -= 1;
	windowRect.origin.y -= 1;
	tempRect.size = windowRect.size;
	tempRect.origin = bounds.origin;
    
    rulerWindow = [[Window alloc] initContent:(const NXRect *)&windowRect
		style:(int)NX_PLAINSTYLE	// no title bar and frame
		backing:(int)NX_BUFFERED
		buttonMask:(int)0	// no controls in title bar and frame 
		defer:(BOOL)NO];
  
  /* so we can draw the currently visible region */
	[self getVisibleRect:&visibleRect];

  /* draw the ruler into the newly created window's content view */ 
	[[rulerWindow contentView] lockFocus];
		// account for frame and translate to currently visible region
		PStranslate(1-visibleRect.origin.x,1-visibleRect.origin.y);
		[self drawSelf:&bounds:0];
		PStranslate(-1+visibleRect.origin.x,-1+visibleRect.origin.y);
		NXFrameRect((const NXRect *)&tempRect);
	[[rulerWindow contentView] unlockFocus];

	[rulerWindow orderFront:NULL];
    
    /* compute the offset from the image's origin to the mouse location */
    offset.x = theEvent->location.x - NX_X(&windowRect)+2;
    offset.y = theEvent->location.y - NX_Y(&windowRect)-2;
    
    /* convert the mousedown location to screen coordinates */
    mouseLocation = theEvent->location;
    [window convertBaseToScreen:&mouseLocation];
    
    /* go into the dragging loop */
	oldMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK];
	while(looping) {
		theEvent = [NXApp getNextEvent:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
		if(theEvent->type == NX_MOUSEDRAGGED){
			if (shiftHeld == YES) { 	// constrain drag
				[rulerWindow getFrame:&tempRect];
				if (orientation == HORIZONTAL)
					[rulerWindow moveTo:tempRect.origin.x 
						:theEvent->location.y-offset.y];
				else
					[rulerWindow moveTo:theEvent->location.x-offset.x 
						:tempRect.origin.y];
				}
			else
				[rulerWindow moveTo:theEvent->location.x-offset.x 
					:theEvent->location.y-offset.y];
			}
		else {	 /* NX_MOUSEUP */
			[rulerWindow free];
			looping = NO;
			}
		}

    return self;
}

/* Sizing the rulers */
- setSize
  // Sets the size of the view.  Invoked by the setSize method of
  // RuledScrollView, which must be invoked whenever the size of
  // the mainView is changed.  
  // In addition to sizing the ruler, this method also sets the instance
  // variables leftOrBottomMargin and rightOrTopMargin, which determine 
  // the location of the origin of the ruler.  If the ruler extends over 
  // the end of an adjacent ruler, the corresponding margin is set to 
  // the width or height of that ruler.  (In order to extend over the 
  // end of another ruler, the ruler must be a 'primary' ruler and 
  // there must be no corresponding stubView.)  The intention of the 
  // margin variables is to locate the origin of the ruler at the
  // edge of the view that it 'rules', automatically accounting  
  // for whatever stub views exist and which rulers are primary.
  //
  // For example, sizing a Right Ruler is done as follows:
  // 1) if Vertical Rulers are primary, and there is a bottom ruler
  //    and there isn't a bottom right stubView, set
  //    leftOrBottomMargin to the height of the bottom ruler
  // 2) if Vertical Rulers are primary, and there is a top ruler
  //    and there isn't a top right stubView, set
  //    rightOrTopMargin to the height of the top ruler
  // 4) the height is set to the height of the main View +
  //	leftOrBottomMargin + rightOrTopMargin.  The width is 
  //	unchanged.
   
{
    NXRect theRect, mainViewFrame, rulerFrame;
	id leftOrBottomRuler, rightOrTopRuler, ruledScrollView, stub;

  /* Ruler's super is a clipview; it's super is RuledScrollView,
   * it's docView is the main View  */
	ruledScrollView = [superview superview];
	if (![ruledScrollView isKindOf:[RuledScrollView class]])
	  // this ruler is not currently in a RuledScrollView
	  // (probably because ruler is hidden)
		return self;
	[[ruledScrollView docView] getFrame:&mainViewFrame];
	[self getFrame:(NXRect *)&theRect];
	leftOrBottomMargin = 0.0;
	rightOrTopMargin = 0.0;
	if (orientation == HORIZONTAL) {
		leftOrBottomRuler = [ruledScrollView leftRuler];
		if ([ruledScrollView bottomRuler] == self)
			stub = [ruledScrollView bottomLeftStub];
		else
			stub = [ruledScrollView topLeftStub];
		if (leftOrBottomRuler && (!stub) && ([ruledScrollView primaryRulers] == TOPBOTTOM)) {
			[leftOrBottomRuler getFrame:&rulerFrame];
			leftOrBottomMargin = rulerFrame.size.width;
			}
			
		rightOrTopRuler = [ruledScrollView rightRuler];
		if ([ruledScrollView bottomRuler] == self)
			stub = [ruledScrollView bottomRightStub]; 
		else
			stub = [ruledScrollView topRightStub]; 
		if (rightOrTopRuler && (!stub) && ([ruledScrollView primaryRulers] == TOPBOTTOM)) {
			[rightOrTopRuler getFrame:&rulerFrame];
			rightOrTopMargin = rulerFrame.size.width;
			}
		theRect.size.width = mainViewFrame.size.width 
			+ leftOrBottomMargin + rightOrTopMargin;
		}
	else {
		leftOrBottomRuler = [ruledScrollView bottomRuler];
		if ([ruledScrollView leftRuler] == self)
			stub = [ruledScrollView bottomLeftStub];
		else
			stub = [ruledScrollView bottomRightStub];
		if ((leftOrBottomRuler) && (!stub) && ([ruledScrollView primaryRulers] == LEFTRIGHT)) {
			[leftOrBottomRuler getFrame:&rulerFrame];
			leftOrBottomMargin = rulerFrame.size.height;
			}

		rightOrTopRuler = [ruledScrollView topRuler];
		if ([ruledScrollView leftRuler] == self)
			stub = [ruledScrollView topLeftStub];
		else
			stub = [ruledScrollView topRightStub];
		if ((rightOrTopRuler) && (!stub) && ([ruledScrollView primaryRulers] == LEFTRIGHT)) {
			[rightOrTopRuler getFrame:&rulerFrame];
			rightOrTopMargin = rulerFrame.size.height;
			}
		theRect.size.height = mainViewFrame.size.height
			+ leftOrBottomMargin + rightOrTopMargin;
		}
	[self setFrame:(NXRect *)&theRect];
	return self;
}

/* Setting/returning Ruler attributes */
- setScaleSpacing:(NXCoord)points
{
	scaleSpacing = points;
	return self;
}

- (NXCoord)scaleSpacing { return scaleSpacing; }

- setScale:(float)scaleIncrement
  // the increment of the scale reading between major marks
{
	scale = scaleIncrement;
	return self;
}

- (float)scale { return scale; }

- setScaleOffset:(float)origin
{
	scaleOffset = origin;
	return self;
}

- (float)scaleOffset { return scaleOffset; }

- setBorder:(NXCoord)theBorder
{
	border = theBorder;
	return self;
}

- (NXCoord)border { return border; }

- setOrientation:(float)angle
  // set to the manifest HORIZONTAL or VERTICAL
  // must be set before sending setSize:
{
	orientation = angle;
	return self;
}

- (float)orientation { return orientation; }

- setInverted:(BOOL)invertFlag
  // a flag that may be checked in drawself: to draw a 
  // ruler reflected vertically top or right side
{
	inverted = invertFlag;
	return self;
}

- (BOOL)inverted { return inverted; }

- setMirrored:(BOOL)mirrorFlag
  // a flag that may be checked in drawself: to draw a 
  // the ruler reflected horizontally
{
	mirrored = mirrorFlag;
	return self;
}

- (BOOL)mirrored { return mirrored; }

- setAlignment:(int)alignType
  // set to one of NX_LEFTALIGNED, NX_CENTERED, NX_RIGHTALIGNED
  // manifests defined in appkit/Text.h
{
	alignment = alignType;
	return self;
}

- (int)alignment { return alignment; }

- setUnits:(char *)name
{
	if (name == NULL) return self;
	if (strlen(units) < strlen(name)) {
		free(units);
		units = malloc(strlen(name)*sizeof(char));
		}
	strcpy(units,name);
	return self;
}	

- (const char *)units {return (const char *)units;}

- setFont:aFont
{

    font = aFont;
    NXTextFontInfo(font, &ascender, &descender, &lineHeight);
    if (descender < 0.0) descender = -1.0 * descender;

    return self;
}

- (Font *)font { return font; }

- setBackgroundColor:(NXColor)bColor
{
	backgroundColor = bColor;
	return self;
}

- (NXColor)backgroundColor {return backgroundColor;}

- setBackgroundGray:(float)value;
{
	backgroundGray = value;
	return self;
}

- (float)backgroundGray{return backgroundGray;}

- setImage:anImage
{
    /* an image object for rulers that tile an image to the background */
    image = anImage;
    return self;
}

- image
{
	return image;
}

- setDraggableRuler:(BOOL)yesOrNo;
{
	draggableRuler = yesOrNo;
	return self;
}

- (BOOL)draggableRuler {return draggableRuler; }

@end
