//
// Time-stamp: <95/12/08 22:21:48 stephan>
//
// RulerView.m
// Project: Rulers
//
// Stephan Wacker
// 93-02-01


#import "RulerView.h"
#import "RulerControl.h"
#import "RulerPreferences.h"	// We only need the constants, not the methods

#import "Rubber.h" // psw stuff

#define RUBBERBAND_WINDOW_WIDTH	1
#define CONTINUOUS_UPDATE	1 // YES/NO doesn't work in #ifdef



// Tick mark systems.

TickMark TMpt[] = {			// 1 point is one pixel on the screen
    100.0,	1.0,	100,	// 100 pt
    50.0,	0.7,	0,	//  50 pt
    10.0,	0.3,	0,	//  10 pt
    5.0,	0.1,	0,	//   5 pt
    0.0,	0.0,	0
};

TickMark TMpc[] = {			// 1 pica = 12pt
    48.0,	1.0,	4,	// 4 pc
    24.0,	0.6,	0,	// 2 pc
    12.0,	0.3,	0,	// 1 pc
    4.0,	0.1,	0,	// 4 pt
    0.0,	0.0,	0
};

TickMark TMin[] = {			// 1 inch = 72pt
    72.0,	1.0,	1,	// 1 in
    72.0/2.0,	0.6,	-2,	// 1/2 in
    72.0/4.0,	0.3,	0,	// 1/4 in
    72.0/8.0,	0.1,	0,	// 1/8 in
    0.0,	0.0,	0
};

TickMark TMin_true[] = {		// xx dpi
    1.0,	1.0,	1,	// 1 in
    1.0/2.0,	0.6,	-2,	// 1/2 in
    1.0/4.0,	0.3,	0,	// 1/4 in
    1.0/8.0,	0.1,	0,	// 1/8 in
    0.0,	0.0,	0
};

TickMark TMin_10[] = {			// 1/10 inch marks
    72.0,	1.0,	1,	// 1 in
    72.0*0.5,	0.7,	0,	// 0.5 in
    72.0*0.1,	0.3,	0,	// 0.1 in
    0.0,	0.0,	0
};

TickMark TMin_true_10[] = {		// xx dpi
    75.0,	1.0,	1,	// 1 in
    75.0*0.5,	0.7,	0,	// 0.5 in
    75.0*0.1,	0.3,	0,	// 0.1 in
    0.0,	0.0,	0
};

#define ONE_CM		(72.0 / 2.54)
TickMark TMcm[] = {			// 2.54 centimeter = 1 inch
    ONE_CM,	1.0,	1,	// 1 cm
    ONE_CM*0.5,	0.7,	0,	// 0.5 cm
    ONE_CM*0.1,	0.3,	0,	// 0.1 cm
    0.0,	0.0,	0
};

TickMark TMcm_true[] = {		// xx dpi
    1.0,		1.0,	1,	// 1 cm
    1.0*0.5,		0.7,	0,	// 0.5 cm
    1.0*0.1,		0.3,	0,	// 0.1 cm
    0.0,		0.0,	0
};



@implementation RulerView


- initFrame: (const NXRect *) frameRect
// Initialize yourself after instance has been created.
{
    const char *pbTypes[] = { NXColorPboardType };

    [super initFrame: frameRect];
    
    origin.x = 0.0, origin.y = 0.0;		// left bottom corner
    [self synchScreenOrigin];
    
    markFont
     = [Font newFont: "Helvetica-Bold"
		size: 14
	      matrix: NX_IDENTITYMATRIX
       ];

    [self registerForDraggedTypes: pbTypes count: 1];

    cachedImage = nil; // will be created by first -verifyValidRect:
    imageOrigin.x = imageOrigin.y = 0;
    validRect.origin = imageOrigin;
    validRect.size.width = validRect.size.height = 0;
    
    return self;
}



- drawMarks: (const NXRect *) aRect
{
    return [self subclassResponsibility: _cmd];
}



- drawBackground: (const NXRect *) aRect
{
    NXRect	rect = *aRect;
    float	h, s, b;
    float	db;

    NXSetColor( [control color] );
    NXRectFill( aRect );

    NXConvertColorToHSB( [control color], &h, &s, &b );
    db = MIN( b/3, (1-b)/3 );

    if( Q_IsHorizontalRulerView(self) )
    {
	rect.origin.y    += 0.8 * rect.size.height;
	rect.size.height -= 0.8 * rect.size.height;
    }
    else
    {
	rect.size.width -= 0.8 * rect.size.width;
    }
    NXSetColor( NXConvertHSBToColor( h, s, b+db ) );
    NXRectFill( &rect );

    if( Q_IsHorizontalRulerView(self) )
    {
	rect.origin.y = aRect->origin.y;
    }
    else
    {
	rect.origin.x += 4 * rect.size.width;
    }
    NXSetColor( NXConvertHSBToColor( h, s, b-db ) );
    NXRectFill( &rect );
    
    return self;
}



- verifyValidRect: (const NXRect *) aRect
// Make sure that aRect is correctly rendered in the cachedImage.
// aRect is given in Ruler coordinates.
{
    NXRect	rect = *aRect;
    NXSize	size = { 0, 0 };
    NXSize	newSize;
    NXSize	offset = { 0, 0 };
    NXImage	*newImage;
    NXPoint	point;


    // Get the image size.
    [cachedImage getSize: &size];
    newSize = size;

    // Convert aRect to image coordinates.
    rect.origin.x += imageOrigin.x;
    rect.origin.y += imageOrigin.y;

    // Find the required size for an image containing aRect.
    if( NX_MAXX(&rect) > newSize.width )
    {
	newSize.width += (NX_MAXX(&rect) - newSize.width);
    }
    if( NX_X(&rect) < 0 )
    {
	newSize.width += offset.width = -NX_X(&rect);
    }
    if( NX_MAXY(&rect) > newSize.height )
    {
	newSize.height += (NX_MAXY(&rect) - newSize.height);
    }
    if( NX_Y(&rect) < 0 )
    {
	newSize.height += offset.height = -NX_Y(&rect);
    }

    // Check if we have to resize the image.
    if( newSize.width > size.width  ||  newSize.height > size.height )
    {
	// Add some spare room.
	if( Q_IsHorizontalRulerView(self) )
	{
	    newSize.width += 100;
	    if( offset.width > 0 )
	    {
		offset.width += 100;
		rect.origin.x += offset.width;
		imageOrigin.x += offset.width;
	    }
	}
	else
	{
	    newSize.height += 100;
	    if( offset.height > 0 )
	    {
		offset.height += 100;
		rect.origin.y += offset.height;
		imageOrigin.y += offset.height;
	    }
	}

	// Create a new image.
	newImage = [[NXImage alloc] initSize: &newSize];
	[newImage useCacheWithDepth: NX_TwelveBitRGBDepth];
	size = newSize;

	// Paste the currently valid region into the new image.
	[newImage lockFocus];
	point.x = NX_X(&validRect) + offset.width;
	point.y = NX_Y(&validRect) + offset.height;
	[cachedImage composite: NX_COPY
	 fromRect: &validRect toPoint: &point];
	validRect.origin = point;
	[newImage unlockFocus];

	// Replace the cachedImage.
	[cachedImage free];
	cachedImage = newImage;
    }

    // Find the parts of aRect that are not contained in validRect.
    if( NX_X(&rect) < NX_X(&validRect) )
    {
	rect.size.width = NX_X(&validRect) - NX_X(&rect);
	if( NX_X(&rect) - 0 > 20 )
	{
	    rect.size.width += 20;
	    rect.origin.x -= 20;
	}
	else
	{
	    rect.size.width += (NX_X(&rect) - 0);
	    rect.origin.x = 0;
	}
	validRect.size.width += NX_WIDTH(&rect);
	validRect.origin.x = NX_X(&rect);
    }
    else if( NX_MAXX(&rect) > NX_MAXX(&validRect) )
    {
	rect.size.width = NX_MAXX(&rect) - NX_MAXX(&validRect);
	rect.origin.x = NX_MAXX(&validRect);
	if( size.width - NX_MAXX(&rect) > 20 )
	{
	    rect.size.width += 20;
	}
	else
	{
	    rect.size.width += (size.width - NX_MAXX(&rect));
	}
	validRect.size.width += NX_WIDTH(&rect);
    }
    else if( NX_Y(&rect) < NX_Y(&validRect) )
    {
	rect.size.height = NX_Y(&validRect) - NX_Y(&rect);
	if( NX_Y(&rect) - 0 > 20 )
	{
	    rect.size.height += 20;
	    rect.origin.y -= 20;
	}
	else
	{
	    rect.size.height += (NX_Y(&rect) - 0);
	    rect.origin.y = 0;
	}
	validRect.size.height += NX_HEIGHT(&rect);
	validRect.origin.y = NX_Y(&rect);
    }
    else if( NX_MAXY(&rect) > NX_MAXY(&validRect) )
    {
	rect.size.height = NX_MAXY(&rect) - NX_MAXY(&validRect);
	rect.origin.y = NX_MAXY(&validRect);
	if( size.height - NX_MAXY(&rect) > 20 )
	{
	    rect.size.height += 20;
	}
	else
	{
	    rect.size.height += (size.height - NX_MAXY(&rect));
	}
	validRect.size.height += NX_HEIGHT(&rect);
    }
    else
    {
	return self;
    }

    // Draw inside rect.
    [cachedImage lockFocus];
    [self drawBackground: &rect];
    [self drawMarks: &rect];
    [cachedImage unlockFocus];


    return self;
}



#if 0
#
- drawSelf: (const NXRect *) rects: (int) rectCount
// Draw the ruler image's background.
// Should be refined by subclasses.
{
    NXSetColor( [control color] );
    
    switch( rectCount ) {
      case 1:
	NXRectFill( rects );		// one exposed region
      break;
      case 3:
	NXRectFill( rects+1 );		// two exposed regions
	NXRectFill( rects+2 );
      break;
      default:
	NXRectFill( &bounds );		// complete view
      break;
    }
    
    return self;
}
#else
- drawSelf: (const NXRect *) rects: (int) rectCount
// Draw the ruler.
{
    NXRect	rect = rects[0];

    // Convert to Ruler coordinates.
    rect.origin.x -= origin.x;
    rect.origin.y -= origin.y;

    // Render the region in cachedImage.
    [self verifyValidRect: &rect];

    // Convert to Image coordinates.
    rect.origin.x += imageOrigin.x;
    rect.origin.y += imageOrigin.y;

    // Copy the region from cachedImage to the View.
    [cachedImage composite: NX_COPY fromRect: &rect toPoint: &rects[0].origin];

    return self;
}
#endif



- (BOOL) acceptsFirstMouse
// Accept mouse even if not key window.
{
    return YES;
}



- mouseDown: (NXEvent *) event
// Drag the local origin with the mouse cursor.
// With Cmd-Key: Set Origin to mouse location first.
{
    if( event->flags & NX_COMMANDMASK )
    {
	NXPoint	loc = event->location;			// window coordinates

	[self convertPoint: &loc fromView: nil];	// view coordinates
	[self setOrigin: &loc];
    }

    [self trackMouseFor: self action: @selector( moveOrigin: )];
    [window display];
    
    return self;
}



#if 0
- rightMouseDown: (NXEvent *) event
// Do nothing and wait for rightMouseUp.
{
#if CONTINUOUS_UPDATE
    NXPoint	loc = event->location;		// window coordinates
    
    [self convertPoint: &loc fromView: nil];	// view coordinates
    [self setOrigin: &loc];
    [self trackMouseFor: self action: @selector( moveOrigin: )];
    [window display];
#endif

    return self;
}
#endif



#if 0
- rightMouseUp: (NXEvent *) event
// Set the local origin to the mouse cursor.
{
    NXPoint	loc = event->location;		// window coordinates
    
    [self convertPoint: &loc fromView: nil];	// view coordinates
    [self setOrigin: &loc];
    [window display];
    
    return self;
}
#endif



- getOrigin: (NXPoint *) point
// Get coordinate system's origin (relative to the view).
{
    *point = origin;
    return self;
    
    return self;
}



- setOrigin: (const NXPoint *) point
// Set local coordinate system's origin.
// Caller should send a display message to the view or its window.
{
    origin = *point;

    if( Q_IsHorizontalRulerView(self) )
    {
	origin.y = 0;
    }
    else
    {
	origin.x = 0;
    }

    [self synchScreenOrigin];
#if CONTINUOUS_UPDATE
    [self update];
#else
    [self setNeedsDisplay: YES];
#endif
    
    return self;
}



- moveOrigin: (const NXSize *) delta
// Move local coordinate system's origin.
// Caller should send a display message to the view or its window.
{
    if( Q_IsHorizontalRulerView( self ) )
    {
	origin.x += delta->width;
    }
    else
    {
	origin.y +=delta->height;
    }

    [self synchScreenOrigin];
#if CONTINUOUS_UPDATE
    [self update];
#else
    [self setNeedsDisplay: YES];
#endif
    
    return self;
}



- synchOrigin
// Take origin from screenOrigin.
{
    origin = screenOrigin;
    [window convertScreenToBase: &origin];

    if( Q_IsHorizontalRulerView(self) )
    {
	origin.y = 0;
    }
    else
    {
	origin.x = 0;
    }

    [self setNeedsDisplay: YES];
    
    return self;
}



- synchScreenOrigin
// Take screenOrigin from origin.
{
    screenOrigin = origin;
    [window convertBaseToScreen: &screenOrigin];
    
    return self;
}



- setCacheInvalid
// Force the cachedImage to be redrawn.
{
    // Put the validRect origin at the bottom left corner of
    // the currently visible view.
    validRect.origin.x = imageOrigin.x - origin.x;
    validRect.origin.y = imageOrigin.y - origin.y;

    if( Q_IsHorizontalRulerView(self) )
    {
	validRect.size.width = 0;
    }
    else
    {
	validRect.size.height = 0;
    }
    [self setNeedsDisplay: YES];

    return self;
}



- (const TickMark *) tickMarkSystem
// Answer current tick mark system, as selected by the RulerControl.
{
    TickMark	*result;

    
    switch( [control unitsMode] ) {
    
      case UNIT_PT:
	return TMpt;
	break;
      
      case UNIT_PC:
      default:
	return TMpc;
	break;
      
      case UNIT_IN:
	switch( [control inchMode] ) {
	  case INCH_DIV_8:
	  default:
	    return TMin;
	  case INCH_DIV_10:
	    return TMin_10;
	}
	break;
      
      case UNIT_CM:
	return TMcm;
	break;
      
      case UNIT_TRUE_IN:
	switch( [control inchMode] ) {
	  case INCH_DIV_8:
	  default:
	    result = TMin_true;
	    result[0].distance = [control screenDpi];
	    result[1].distance = result[0].distance / 2.0;
	    result[2].distance = result[0].distance / 4.0;
	    result[3].distance = result[0].distance / 8.0;
	    return result;
	  case INCH_DIV_10:
	    result = TMin_true_10;
	    result[0].distance = [control screenDpi];
	    result[1].distance = result[0].distance * 0.5;
	    result[2].distance = result[0].distance * 0.1;
	    return result;
	}
	break;
      
      case UNIT_TRUE_CM:
	result = TMcm_true;
	result[0].distance = [control screenDpi] / 2.54;
	result[1].distance = result[0].distance * 0.5;
	result[2].distance = result[0].distance * 0.1;
	return result;
	break;
    }
    
    return NULL;
}



- trackMouseFor: target action: (SEL) action
//
// Track the mouse while it drags some control object.
// Initiated by a mouseDown: event; tracks mouse until mouseUp:.
//
// The action is repeatedly sent to the target with
// the partial moving distance as its argument.
// The method definition should be
//    action: (NXSize *) delta
//
{
    NXEvent	*event = [NXApp currentEvent];		// MouseDown
    NXPoint	oldLoc = event->location;		// window coordinates
    NXPoint	newLoc;
    NXSize	delta;
    int		oldMask = [window eventMask];
    int		trackMask;
    int		stopEvent;

    // Reset local variable for resizing correction.
    off = 0;

    switch( event->type )
    {
      default:
      case NX_LMOUSEDOWN:
	trackMask = NX_LMOUSEUPMASK | NX_LMOUSEDRAGGEDMASK;
	stopEvent = NX_LMOUSEUP;
	break;
#if 0
      case NX_RMOUSEDOWN:
	trackMask = NX_RMOUSEUPMASK | NX_RMOUSEDRAGGEDMASK;
	stopEvent = NX_RMOUSEUP;
	break;
#endif
    }
    
    [window setEventMask: (oldMask | trackMask)];
    
    [window convertBaseToScreen: &oldLoc];		// screen coordinates
    
    while( 1 ) {
	newLoc = event->location;			// window coordinates
	[window convertBaseToScreen: &newLoc];		// screen coordinates
	
#if 0
 This was an ugly mistake: If the cursor was moved in both x and y direction, the first test signaled that delta.width was not zero and the second test -- including the assignment to delta.height -- was skipped.
	if( (delta.width = newLoc.x - oldLoc.x) != 0.0
	 || (delta.height = newLoc.y - oldLoc.y) != 0.0
	) {
	    [target perform: action with: (id) &delta];
	}
#else
	delta.width = newLoc.x - oldLoc.x;
	delta.height = newLoc.y - oldLoc.y;
	if( delta.width || delta.height ) {
	    [target perform: action with: (id) &delta];
	}
#endif
	
	if( event->type == stopEvent ) break;		// from while()
	
	oldLoc = newLoc;
	event = [NXApp getNextEvent: trackMask];
    }
    
    [window setEventMask: oldMask];		// restore old settings
    
    return self;
}


- showRubberband
{
    PSWInitRubberband();
    [window getFrame: &rubberRect];
    [self drawRubberband];
    PSWMakeVisible(TRUE);
    return self;
}


- hideRubberband
{
    PSWMakeVisible(FALSE);
    [window placeWindowAndDisplay: &rubberRect];
    return self;
}


// The code for this method is copied from
//	/BGCD/BGCDDeveloper/Sources/Classes/Rubberview/Rubberband.m
//	by Slugg Jello, 
//	Mouthing Flowers
//	152 20th Ave. #1
//	Seattle, WA. 98112
//	slugg@mouthers.nwnexus.wa.com
//
- drawRubberband
{
    NXRect	tRect, screenRect = rubberRect;
    
    struct
    {
	NXRect	top, left, bottom, right;
    } points;
    
    
    /**** set size of each rubberband side ****/
    
    /* left */
    tRect = screenRect;
    tRect.size.width = RUBBERBAND_WINDOW_WIDTH;
    tRect.size.height = MAX(RUBBERBAND_WINDOW_WIDTH,screenRect.size.height);
    points.left = tRect;
    
    /* right */
    tRect.origin.x += screenRect.size.width - 1.0;
    points.right = tRect;
    
    /* bottom */
    tRect = screenRect;
    tRect.origin.x += 1.0;
    tRect.size.width = MAX(RUBBERBAND_WINDOW_WIDTH,screenRect.size.width-1.0);
    tRect.size.height = RUBBERBAND_WINDOW_WIDTH;
    points.bottom = tRect;
    
    /* top */
    tRect.origin.y += screenRect.size.height - 1.0;
    points.top = tRect;
    
    /* this'll erase old one and draw new one in new position */
    PSWPositionRect( (float *)&points );

    return self;
}


@end // RulerView



@implementation RulerView ( DraggingDestination )


- (NXDragOperation) draggingEntered: sender
// Invoked when the dragged image enters the destination.
// Answer what we will do when the object is released.
{
    return ([sender draggingSourceOperationMask] & NX_DragOperationGeneric);
}


- (BOOL) prepareForDragOperation: sender
// Invoked when the image is released.
// Answer whether we will perform the drag operation.
{
    return YES;
}


- (BOOL) performDragOperation: sender
// Invoked after the released image has been removed from the screen.
// Answer whether the data has been accepted.
{
    Pasteboard	*pb = [Pasteboard newName: NXDragPboard];

    [control setColor: NXReadColorFromPasteboard(pb)];
    
    return YES;
}


@end //RulerView ( DraggingDestination )
