// BWPageControl.m -- the bottom-of-the-window UI thingie.

// Copyright (C) 2003 Borkware
// We use the BSD License.  Check out http://borkware.com/license

#import "BWPageControl.h"


// constants for getting the look "just right", so that it emulates pretty
// closely the Mac OS 9 NotePad desk accessory

// the size of the dog-ear
#define PAGE_CORNER_SIZE 23

// how far to scoot up the dog-ear control.
// The horizontal lines implying multiple sheets are drawn in the lower
// 8 pixels of the window

#define PAGE_CORNER_FUDGE_FACTOR 8


@implementation BWPageControl


// get things initialized.  default to showing page number 1, and

- (id) initWithFrame: (NSRect) frame
{
    if (self = [super initWithFrame: frame]) {

	[self setPageNumber: 1];

	// no mouse events yet, so no tracking has happened
	currentPart = BWPageNoPart;
	hitPart = BWPageNoPart;
    }

    return (self);

} // initWithFrame



// clean up our mess

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



// set the paage number area to show the current page number

- (void) setPageNumber: (int) number
{
    [pageNumber release];

    // if we wanted to, we could get fancy and use another kind
    // of string, like - # - for numbers
    pageNumber = [[NSString stringWithFormat: @"%d", number] retain];

    [self setNeedsDisplay: YES];

} // setPageNumber



// figure out the rectangle where the page control dog-ear lives
// used both when drawing and hit-testing

- (NSRect) calculatePageControlRect
{
    NSRect rect;

    rect = [self bounds];

    // set the size of the page control to be a square of
    // page_corner_size
    rect.size.height = rect.size.width = PAGE_CORNER_SIZE;

    // scoot up above the bottom-of-control eye candy
    rect.origin.y += PAGE_CORNER_FUDGE_FACTOR;

    // offset by 0.5 so that lines drawn will only affect single
    // pixels.  Otherwise the line drawn at integral coordinates will
    // cause double-wide lines to the drawn. 

    rect.origin.x += 0.5;
    rect.origin.y += 0.5;

    return (rect);

} // calculatePageControlRect



// draw the dog-ear control.  it's a little more complicated than it
// originally looks since there is a gradient going from dark to light
// gray in the 'page forward' part

- (void) drawPageControl
{
    NSRect rect = [self calculatePageControlRect];

    // we're going to be drawing triangular stuff, so calculate these
    // coordinates for convenience
    NSPoint topLeft, topRight, bottomRight;
    
    topLeft = NSMakePoint (rect.origin.x, rect.origin.y + rect.size.height);
    topRight = NSMakePoint (rect.origin.x + rect.size.width,
			    rect.origin.y + rect.size.height);
    bottomRight = NSMakePoint (rect.origin.x + rect.size.width, rect.origin.y);


    // clear out the upper triangle to white
    {
	NSBezierPath *path;
	path = [NSBezierPath bezierPath];
	[path moveToPoint: topLeft];
	[path lineToPoint: topRight];
	[path lineToPoint: bottomRight];
	[path closePath];
	[[NSColor whiteColor] set];
	[path fill];
	// path is autoreleased
    }

    // now do the diagonal

    [[NSColor darkGrayColor] set];
    [NSBezierPath strokeLineFromPoint: topLeft  toPoint: bottomRight];

    // fill in the gradient
    {
	// the gradient is 12 pixels wide, canted at an angle.
	// the start/end values are intensities of white
#define GRADIENT_SIZE 12.0
#define GRADIENT_START 0.6
#define GRADIENT_END 1.00
	float gradientIncrement = (GRADIENT_END - GRADIENT_START) / GRADIENT_SIZE;
	float gradient = GRADIENT_START;

	// starting points of the gradient lines
	NSPoint pt1, pt2;
	pt1 = topLeft;
	pt2 = bottomRight;

	int i;
	for (i = 0; i < 12; i++) {
	    pt1.x++;
	    pt2.y++;
	    [[NSColor colorWithDeviceWhite: gradient  alpha: 1.0] set];
	    [NSBezierPath strokeLineFromPoint: pt1  toPoint: pt2];
	    gradient += gradientIncrement;
	}
    }


    // then stroke the top and right sides of the rectangle

    [[NSColor blackColor] set];

    [NSBezierPath strokeLineFromPoint: topLeft  toPoint: topRight];
    [NSBezierPath strokeLineFromPoint: topRight  toPoint: bottomRight];

} // drawPageControl



// calculate where to put the page number string, and draw it

- (void) drawPageNumber
{

    // the outer rect is the size of the control, minus the dog-ear.
    // center the page number in this rectangle
    NSRect outerRect;
    outerRect = [self bounds];
    outerRect.origin.y += PAGE_CORNER_FUDGE_FACTOR;
    outerRect.size.height -= PAGE_CORNER_FUDGE_FACTOR;

    // this is the rectangle that snuggles around the string
    NSRect innerRect;
    innerRect.size = [pageNumber sizeWithAttributes: nil];
    innerRect.origin = NSZeroPoint;

    // center the inner in the outer

    innerRect.origin.x = (outerRect.origin.x 
			  + floor((outerRect.size.width - innerRect.size.width) / 2));
    innerRect.origin.y = (outerRect.origin.y 
			  + floor((outerRect.size.height - innerRect.size.height) / 2));

    [pageNumber drawInRect: innerRect  withAttributes: nil];

} // drawPageNumber



// draw the whole control

- (void) drawRect: (NSRect) rect
{
    NSPoint pt1, pt2;
    int i;
    NSColor *veryLightGray;

    // be sloppy and just draw the whole thing
    rect = [self bounds];

    // fill in the dog-ear area with a light gray wash

    veryLightGray = [NSColor colorWithDeviceWhite: 0.95  alpha: 1.0];
    [veryLightGray set];
    NSRectFill (rect);

    // fill in the page number area with white, so it matches
    // the textview background
    rect.origin.x += PAGE_CORNER_SIZE;
    rect.size.width -= PAGE_CORNER_SIZE;
    rect.origin.y += PAGE_CORNER_FUDGE_FACTOR;
    rect.size.height -= PAGE_CORNER_FUDGE_FACTOR;

    [[NSColor whiteColor] set];
    [NSBezierPath fillRect: rect];
  
    // now do the fake 'sheets of paper' effect

    // reset the rectangle since we clobbered it above
    rect = [self bounds];

    // black line at the very bottom
    [[NSColor blackColor] set];
    
    pt1 = pt2 = rect.origin;
    pt2.x += rect.size.width;

    // get the line out of the pixel-gutter and only do one-pixel-wide lines
    pt1.y += 0.5; 
    pt2.y += 0.5;

    [NSBezierPath strokeLineFromPoint: pt1  toPoint: pt2];

    // then skip a line, and make a gray one.  repeat a couple of times

    [[NSColor darkGrayColor] set];

    for (i = 0; i < 3; i++) {

	pt1.y += 2;
	pt2.y += 2;
	[NSBezierPath strokeLineFromPoint: pt1  toPoint: pt2];
    }

    // then a black one to connect to the page corner
    [[NSColor blackColor] set];

    pt1.y += 2;
    pt2.y += 2;
    pt1.x += PAGE_CORNER_SIZE;

    [NSBezierPath strokeLineFromPoint: pt1  toPoint: pt2];

    // draw the other bits and pieces too
    [self drawPageControl];
    [self drawPageNumber];

} // drawRect


// given a point in local coordinates, see what part of the dog-ear
// page flipper thingie the point is in

- (BWPageControlPart) hitTestPageControl: (NSPoint) localPoint
{
    // assume noPart until proven otherwise
    BWPageControlPart part = BWPageNoPart;

    NSRect rect;
    rect = [self calculatePageControlRect];

    if (NSPointInRect(localPoint, rect)) {

	// bias rect and point to zero,zero.  makes the if test a little
	// more concise
	localPoint.x -= rect.origin.x;
	localPoint.y -= rect.origin.y;

	// assume the control rect is a square

	if (localPoint.x + localPoint.y > rect.size.width) {
	    part = BWPageForward;
	} else {
	    part = BWPageBackward;
	}
    }

    return (part);

} // hitTestPageControl



// woo hoo!  we've got a hit in the controller!  Let the delegate know so
// they can do some work

- (void) dispatchHit: (BWPageControlPart) part
{
    if (hitPart == BWPageForward) {
	[delegate pageForward];
    } else if (hitPart == BWPageBackward) {
	[delegate pageBackward];
    }
    
} // dispatchHit



// if the user clicks and holds in the page flappy, it'll fire
// repeatedly.  This handles the timer fire action

- (void) handlePagerTimer: (NSTimer *) timer
{
    // make sure they're still in the part they clicked in
    if (hitPart == currentPart) {
	[self dispatchHit: hitPart];
    }
} // handlePagerTimer



// handle a mouse click.

- (void) mouseDown: (NSEvent *) event
{
    NSPoint point = [self convertPoint: [event locationInWindow]  fromView: nil];

    // see if the click was in the page control region
    hitPart = [self hitTestPageControl: point];
    currentPart = hitPart;

    if (hitPart != BWPageNoPart) {
	
	// go ahead and dispatch the action.  then 
	[self dispatchHit: hitPart];

	// start the timer
	pagerTimer = [[NSTimer scheduledTimerWithTimeInterval: 1.0
			      target: self
			      selector: @selector(handlePagerTimer:)
			      userInfo: nil
			      repeats: YES] retain];
    }
    
} // mouseDown



// handle a mouse drag.  Just see if we've moved to a new part or not

- (void) mouseDragged: (NSEvent *) event
{
    NSPoint point = [self convertPoint: [event locationInWindow]  fromView: nil];

    // if currentPart is different than our starting out part, the
    // timer action won't dispatch a message to the delegate.

    currentPart = [self hitTestPageControl: point];

} // mouseDragged



// done with the mouse handling.  Ditch the timer

- (void) mouseUp: (NSEvent *) theEvent
{
    [pagerTimer invalidate];
    [pagerTimer release];
    pagerTimer = nil;

    currentPart = BWPageNoPart;

} // mouseUp



// someone wants to see notifications on control area clickings.
// Using a delegate is kinda weird.  It would be better to just use
// a notification.

- (void) setDelegate: (id) newDelegate
{
    delegate = newDelegate;
} // setDelegate


@end // BWPageControl


/* $Id: BWPageControl.m,v 1.4 2003/01/02 21:29:20 markd Exp $ */
