//
// DigitalClockView.m
// rev 1.3
// Now supports BackSpace inspector panels
//
// Matt Pharr- pharr@cs.yale.edu
// NeXTMail welcome
//

#import "DigitalClockView.h"
#import <appkit/appkit.h>
#import <defaults/defaults.h>
#import <dpsclient/wraps.h>
#import <libc.h>
#import <stdlib.h>
#import <strings.h>
#import <time.h>

#define AM_PM		0
#define MILITARY	1

@implementation DigitalClockView

/* Fixes the NXImage so that if there isn't room to display it on the
 * screen (i.e. it's too big, or the screen/window space is too small)
 * it still behaves generally nicely.
 */
- fixPosition
{
    /* If we're doing AM/PM time and a leading 0 was taken off the time
     * string that strftime returned, let some of the NXImage go off the
     * edge of the screen before we force it back on. When the leading 0
     * is taken off, we have extra room in currentImage, which is a black
     * area at the right side, so we can let that part go off the screen...
     */
	
    if (militaryTime == NO && didShorten == YES) {
        if (currentLocation.x + .9 * currentSize.width >= bounds.size.width)
	    currentLocation.x = bounds.size.width - .9 * currentSize.width;
    }
    else {
        if (currentLocation.x + currentSize.width >= bounds.size.width)
            currentLocation.x = bounds.size.width - currentSize.width;
    }


    if (currentLocation.x <= bounds.origin.x)
   	currentLocation.x = 0;

    if (currentLocation.y + currentSize.height >= bounds.size.height)
        currentLocation.y = bounds.size.height - currentSize.height;
		
    if (currentLocation.y <= bounds.origin.y)
        currentLocation.y = 0;

    return self;
}


/* This handles bouncing the image- i.e. changing the move vector if needed
 * when currentImage hits the edge of the screen/window. Note a similar kludge
 * as in fixPosition that allows for the truncated AM/PM times.
 */
- bounceIfNeeded
{
    if (currentLocation.x <= bounds.origin.x)
        moveVector.x= bounceMultiplier * randBetween(0.5, 1.5);
		
    if (militaryTime == NO && didShorten == YES) {
        if (currentLocation.x + .9 * currentSize.width >= bounds.size.width)
            moveVector.x= bounceMultiplier * (-1) * randBetween(0.5, 1.5);
    }
    else {
        if (currentLocation.x + currentSize.width >= bounds.size.width)
            moveVector.x= bounceMultiplier * (-1) * randBetween(0.5, 1.5);
    }
	
    if (currentLocation.y <= bounds.origin.y)
        moveVector.y= bounceMultiplier * randBetween(0.5, 1.5);
		
    if (currentLocation.y + currentSize.height >= bounds.size.height)
        moveVector.y= bounceMultiplier * (-1) * randBetween(0.5, 1.5);

    return self;
}


- chooseColor
{
    float dr, dg, db;

    if ([Window defaultDepthLimit] == NX_TwoBitGrayDepth) {
        PSsetgray(1.0);         /* yipee! */
    }

    else {
        dr= randBetween(-.1, .1); /* randomly move the red, green, and blue */
        dg= randBetween(-.1, .1); /* components of the color around between */
        db= randBetween(-.1, .1); /* 0 and 1. There is a minor bummer in that
                                   * you only see the effect of the color
                                   * change once a second, because it only
                                   * draws the time into the bitmap when the
                                   * time changes. Would be an easy modification
                                   * to get it to redraw the bitmap more
                                   * frequently, but its probably not worth
                                   * the processor cycles. Depending on how this
                                   * ends up looking in color (wish I knew!), it
                                   * might be better to replace everything
                                   * between the else and the PSsetrgbcolor
                                   * with just a PSsetrgbcolor(), with constant
                                   * arguments...
                                   */

        currR += dr;    currG += dg;     currB += db;

        if (currR < 0.0) currR= 0.0;
        if (currR > 1.0) currR= 1.0;
        if (currG < 0.0) currG= 0.0;
        if (currG > 1.0) currG= 1.0;
        if (currB < 0.0) currB= 0.0;
        if (currB > 1.0) currB= 1.0;
        PSsetrgbcolor(currR, currG, currB);
    }

    return self;
}



- oneStep
{
    time_t tTime;
    struct tm *currentTime;
	
	
    /* if a new currentImage has been allocated, most likely the size of the
     * clock or whether military time is being used or not has changed. Thus,
     * it's a good idea to clear out the drawing area, so that no little
     * remnants are left hanging around.
     */
    if (isNew == YES) {
        PSsetgray(0.0);
        NXRectFill(&bounds);
        isNew= NO;
    }

    currentLocation.x += moveVector.x;
    currentLocation.y += moveVector.y;
    [self bounceIfNeeded];
    [self fixPosition];

    time(&tTime);
    currentTime= localtime(&tTime);
	
    if (militaryTime == YES) 
        strftime(theTime, 14, "%H:%M:%S", currentTime);
    else {
        strftime(theTime, 14, "%I:%M:%S %p", currentTime);
        if (theTime[0] == '0') {
            strcpy(theTime, theTime+1); /* take off the leading 0 */
            didShorten= YES;
        }
        else
            didShorten= NO;
    }

    if (strcmp(theTime, lastTime) != 0) { /* if the time has changed... */
        strcpy(lastTime, theTime);

        currentLocation.x += moveVector.x; /* move it one more time so it's not so jerky */
        currentLocation.y += moveVector.y; /* when the time changes... */
        [self bounceIfNeeded];
        [self fixPosition];

        if ([currentImage lockFocus] == YES) { /* draw a new bitmap */
            PSsetgray(0.0);
            PSrectfill(0, 0, currentSize.width, currentSize.height);
            [theFont set];

            PSsetgray(.333);    /* give it a little shadow */
            PSmoveto(14, clockSize);
            PSshow(theTime);

            PSmoveto(10, clockSize + 2);
            [self chooseColor];
            PSshow(theTime);
			
            [currentImage unlockFocus];
        }
    }

    [currentImage composite:NX_COPY toPoint:&currentLocation];

    usleep((1*1000000)/68);

    return self;
}


- (const char *)windowTitle
{
    return "Digital Clock";
}


- drawSelf:(const NXRect *)rects :(int)rectCount
{
    if (!rects || !rectCount) {
        return self;
    }

    PSsetgray(0);
    NXRectFill(rects);

    return self;
}


- newClock
{
    *theTime= '\0';
    *lastTime= '\0';

    theFont=  [Font newFont:"Times-Roman" size:clockSize];

    currR= currG= currB= .5;

    if (militaryTime == YES) {
        currentSize.width= [theFont getWidthOf:"88:88:88"] + 20;
    }
    else {
        currentSize.width= [theFont getWidthOf:"88:88:88 MM"] + 20;
    }
	
    currentSize.height= clockSize + 20;
	
    [currentImage free];
    currentImage= [[NXImage alloc] initSize:&currentSize];
    [currentImage setFlipped:YES];
	
    isNew= YES;                 /* make sure oneStep knows it has a new
                                 * bitmap to work with, and will clear
                                 * the screen as needed...
                                 */

    return self;
}



- inspector:sender
{
    char buf[MAXPATHLEN];

    if (!sharedInspectorPanel) {
        sprintf(buf,"%s/%s",[(BSThinker()) moduleDirectory:"DigitalClock"],
                "DigitalClock.nib");
        [NXApp loadNibFile:buf owner:self withNames:NO];
    }

    return sharedInspectorPanel;
}


- initFrame:(const NXRect *)frameRect
{
    [super initFrame:frameRect];

    currentLocation.x= 400.0;
    currentLocation.y= 400.0;

    moveVector.x= randBetween(0.5, 1.5);
    moveVector.y= randBetween(0.5, 1.5);
	
    didShorten= NO;
	
    [self inspector:self];      /* Must do this ourselves here instead of
                                 * letting BackSpace take care of it because
                                 * we use the connections defined in there
                                 * down below where we sent setFloatValue, etc
                                 * to the sliders and other controls.
                                 */
	
    if (NXGetDefaultValue([NXApp appName], "digClkViewClockSize") == NULL) {
        NXWriteDefault([NXApp appName], "digClkViewClockSize", "120.0");
        NXWriteDefault([NXApp appName], "digClkViewBounceMult", "1.0");
        NXWriteDefault([NXApp appName], "digClkViewMilitary", "NO");
        clockSize= 120.0;
        bounceMultiplier= 1.0;
        militaryTime= NO;
    }
	
    else {
        clockSize= atof(NXGetDefaultValue([NXApp appName],
                                          "digClkViewClockSize"));
        if (clockSize < 10.0 || clockSize > 300.0)
            clockSize= 120.0;
		
        bounceMultiplier= atof(NXGetDefaultValue([NXApp appName],
                                                 "digClkViewBounceMult"));
        if (bounceMultiplier < .1 || bounceMultiplier > 10.0)
            bounceMultiplier= 1.0;

        if (strcmp(NXGetDefaultValue([NXApp appName], "digClkViewMilitary"),
                   "YES") == 0) {
            militaryTime= YES;
        }
        else militaryTime= NO;
    }
	
    [sizeSlider setFloatValue:clockSize];
    [sizeSlider update];
	
    [speedSlider setFloatValue:bounceMultiplier];
    [speedSlider update];
	
    if (militaryTime == YES)
        [militarySwitch setState:MILITARY];
    else
        [militarySwitch setState:AM_PM];
    [militarySwitch update];

    [self newClock];
	
    return self;
}


- setClockSize:sender
{
    char temp[40];
	
    clockSize= [sender floatValue];
	
    sprintf(temp, "%f", clockSize);
    NXWriteDefault([NXApp appName], "digClkViewClockSize", temp);
	
    [self newClock];

    return self;
}


- setBounceMultiplier:sender
{
    char temp[40];

    bounceMultiplier= [sender floatValue];
	 
    sprintf(temp, "%f", bounceMultiplier);
    NXWriteDefault([NXApp appName], "digClkViewBounceMult", temp);
	
	
    /* update the moveVector as needed so that the changes aare reflected
     * immediately, instead of forcing the user to wait until the clock
     * bounces off of an edge to see the effect.. 
     */
    if (moveVector.x > 0) 
        moveVector.x= bounceMultiplier * randBetween(0.5, 1.5);
    else 
        moveVector.x= (-1) * bounceMultiplier * randBetween(0.5, 1.5);
	
    if (moveVector.y > 0)
        moveVector.y= bounceMultiplier * randBetween(0.5, 1.5);
    else
        moveVector.y= (-1) * bounceMultiplier * randBetween(0.5, 1.5);
	 
    return self;
}


- setMilitaryTime:sender
{
    if ([sender state] == MILITARY) {
        militaryTime= YES;
        NXWriteDefault([NXApp appName], "digClkViewMilitary", "YES");
    }
    else {
        militaryTime= NO;
        NXWriteDefault([NXApp appName], "digClkViewMilitary", "NO");
    }
	
    [self newClock];

    return self;
}

@end

