// Clock.m, simple clock view 
// Author: Ali T. Ozer, NeXT Developer Support Group
// Created: May 26, 1989 (for version 0.9)
// Modified: June 14 and Aug 14, 1989 (for version 1.0)
// 
// Modifier: Jiro Nakamura, Independent NeXT Developer
// Modified: May 11, 1990 (for Cassandra)
//
// Subclass of view to implement a simple analog or digital clock. This view is
// pretty generic and can probably be added to any program. Different clock
// faces can be set through the setBackgroundGray: and setClockGray: methods;
// you can also specify TIFF files as background images for the clock through
// the setClockFaceToBitmapNamed: method. Finally you have the option of
// turning the seconds hand on or off. 
static char authorid[] = "$Author: jiro $";
static char rcsid[] = "$Id: Clock.m,v 1.3 90/12/03 01:55:16 jiro Exp Locker: jiro $";

#import "Clock.h"
#import "ClockPS.h"	// PSwrap routines

#import <dpsclient/wraps.h>	
#import <appkit/nextstd.h>
#import <appkit/Application.h>
#import <appkit/Button.h>
#import <appkit/Window.h>
#import <objc/typedstream.h>
#import <string.h>
#import <sys/time.h>		
#import "calendar.h"

// Defining DEBUG for Clock can sometimes get very screen messy
// since Cassandra uses it so often
#ifdef DEBUG
	#undef DEBUG
	#define DEBUG2
#endif

#define MAX_CVIEW_WIDTH	14

@implementation Clock
// ShowTime() is the timed entry function called by the
// timed entry mechanism. It first writes the time out on the face of the 
// clock, and then reinstalls the timed entry if the clock is not showing the
// seconds. If the seconds are being shown, no need to reinstall the timed
// entry; a second skipped here and then won't matter. If minutes are being
// shown, we want to make sure that the minute jumps at the next top of the
// minute, regardless of how long it took to service the timed entry.

void ShowTime (teNum, now, clock) 
DPSTimedEntry teNum;
double now;
id clock;
{
    [clock display];        
    if ([clock showSeconds] == NO) [[clock stopTimedEntry] startTimedEntry:NO];
}

// newFrame creates the view and initializes the various paramaters. The
// constants below determine the lengths of the clock hands.

#define HOURRATIO	0.2	/* Hour hand length compared face size */
#define MINUTERATIO	0.35	/* Minute & seconds hands */
 
+ newFrame:(const NXRect *)frm
{
    self = [super newFrame:frm];
    [self  initMoreStuff];
    
    return self;
}

- initMoreStuff
{
    char	dumbChar[5];
    
    // dumb references to some static constants, just
    // to avoid warning messages from the compiler (Garance)
    dumbChar[0] = authorid[0];		// dumb
    dumbChar[1] = rcsid[0];		// de dumb

    // Set the default state (no special face, no seconds, grays).
    clockFace = nil;
    showSeconds = NO;
    showDate = NO;
    backgroundGray = NX_LTGRAY;
    clockGray = NX_BLACK;
    [self computeNewSizes];
            
    // Start the time entry. YES indicates that this is the first time.
    [self startTimedEntry:YES];
    
    return self;
}

// Good idea to get rid of the timed entry while freeing...

- free
{
    [self stopTimedEntry];
    return [super free];
}

// startTimedEntry will install the timed entry. If fireASAP is YES, the
// timed entry is set to fire off as soon as possible (this would be the case
// at the start of the program, for instance). If fireASAP is NO, then the
// timed entry is set to fire off in one second (if seconds are being shown)
// or at the top of the next minute (in anytime between 0 and 60 seconds).

- startTimedEntry:(BOOL)fireASAP
{
    double fireIn;

    if (fireASAP) fireIn = 0.0;		  // Fire as soon as possible!
    else if (showSeconds) fireIn = 1.0;	  // Fire in a second (good enough)
    else {
        struct timeval currentTime;
    	gettimeofday (&currentTime, NULL);
	fireIn = 60.0 - (currentTime.tv_sec % 60);  // Top of the minute
    }
	
    clockTE = DPSAddTimedEntry(fireIn, &ShowTime, self, NX_MODALRESPTHRESHOLD);

    return self;
}

// stopTimedEntry stops the timed entry. Don't call this method unless
// the timed entry is installed an running.

- stopTimedEntry
{
    DPSRemoveTimedEntry (clockTE);
    return self;
}

// drawSelf:: displays the face of the clock, showing the correct time.
// The seconds hand will be shown only if desired.

- drawSelf:(NXRect *)rects :(int)rectCount
{	
    int min, hour, sec;
    struct tm *localTime;
    struct timeval currentTime;
    static char buf[10];
	extern const char *shortWeekDays[7];
	extern const char *shortMonths[12];
	
	// Additions by Jiro for target/action response
	//Send the <action> to the <target>.
	// Mod Jan 6, 1990
   	[target	perform:action  with:self];
	
	gettimeofday (&currentTime, NULL);
	localTime = localtime (&(currentTime.tv_sec));
	min  = localTime->tm_min;
	hour = localTime->tm_hour; 
	sec  = localTime->tm_sec;

	// Erase the background and composite the clock face 
	// in if there is one.
	// Note that we erase even though we might have a bitmap; 
	// this way if the
	// bitmap is smaller than the view it'll still look OK. In a more
	// efficient frame of mind, one could try to avoid unnecessary fills.

	if (backgroundGray >= 0.0 && backgroundGray <= 1.0) {
		PSsetgray (backgroundGray);
		NXRectFill (&bounds);
		}
		
	// If we have a clockface, let's display it
	if(clockFace != nil) 
		{
		#ifdef DEBUG
			fprintf(stderr,"%s: Compositing clock face.\n",
				 PROGNAME);
		#endif
		[clockFace composite:NX_COPY toPoint:&compositeTo];
		}

	switch( clockType)
		{
		case CLOCK_ANALOG:
			// Display the seconds arm, if desired, and then 
			// the hours and minutes.
			if (showSeconds)
				drawClockHand (-6.0 * sec, minuteLen, 
					clockGray, 0.0);
   			drawClockHand (- (hour + min / 60.0) * 30.0, hourLen,
				 clockGray, 2.0);
   			drawClockHand (- fmod(min, 60.0) * 6.0, minuteLen,
				clockGray, 2.0);
			if( showDate)
				{
  				sprintf(buf, "%s %2d",
					shortMonths[localTime->tm_mon],
				  	 localTime->tm_mday);
   				drawClockString( 0, 7, 8, buf);
    				drawClockString( 0, -10, 8, (char *) 
					shortWeekDays[localTime->tm_wday]);
				}
			break;
		case CLOCK_DIGITAL:
			if( !militaryTime)
				{			
				if( hour >= 12 )
					hour -= 12;
				if( hour == 0 )
					hour = 12;			
				}
						
			sprintf(buf, "%2d:%02d", hour, min);
			drawClockString( 0, 6, 18, buf);
			sprintf(buf,"%s %s %2d", (char *) 
			shortWeekDays[localTime->tm_wday],
			shortMonths[localTime->tm_mon], localTime->tm_mday);
			drawClockString( 0, -1, 8, buf);
			PSmoveto( -10, -5);
			PSlineto( 10, -5);
			PSstroke();
			drawClockString( 0, -14, 8, eventTime);
			drawClockString( 0, -21, 8, eventMessage);
			
			break;
		default:
			fprintf(stderr,"%s: Unrecognized clock type: %d\n",
				PROGNAME, clockType);
		}	
    return self;
}

// Set the clock face to the specified bitmap. If bitmapName is NULL,
// then we have no clock face. 

- setClockFaceToBitmapNamed:(const char *)bitmapName
{

	#ifdef DEBUG2
		fprintf(stderr, "%s: Setting clockface bitmap to %s\n",
				PROGNAME, bitmapName);
	#endif
	
	if (bitmapName && (clockFace = [NXImage findImageNamed:bitmapName])) 
		{
		[self computeBitmapLocation];
		}
	else 
   		{
		#ifdef DEBUG2
			fprintf(stderr,"%s: Couldn't find a icon for %s.\n",
				PROGNAME, bitmapName);
		#endif
		clockFace = nil;
    		}
       
    [self display];

    return self;
}

// Compute and cache the location where the clock face should be
// composited into. This method should be invoked everytime the clock face 
// changes or the view is resized.
//
// We assume that the view is translated so that the origin is at the center.

- computeBitmapLocation
{
    if (clockFace) {
        NXSize bitmapSize;
        [clockFace getSize:&bitmapSize];
        compositeTo.x = - bitmapSize.width / 2.0;
        compositeTo.y = - bitmapSize.height / 2.0;
    }
    return self;
}


// Figure the lengths of the hands (based on the size of the view) and
// translate the view so that the center is (0,0).

- computeNewSizes
{
    // Translate view so that the center is 0,0.
    [self setDrawOrigin:-bounds.size.width/2.0 :-bounds.size.height/2.0];

    // Compute the hand lengths and cache bitmap location.
    [self computeBitmapLocation];
    minuteLen = MIN(bounds.size.width, bounds.size.height) * MINUTERATIO;
    hourLen = MIN(bounds.size.width, bounds.size.height) * HOURRATIO;

    return self;
}

// Overriding sizeTo:: allows us to resize and fix up the clock whenever
// the size is changed.

- sizeTo:(NXCoord)w :(NXCoord)h
{
    [super sizeTo:w :h];
    [self computeNewSizes];
    return self;
}

// setShowSecondsToBool: sets whether or not the seconds hand is shown.
// The timed entry must be reinstalled whenever this setting is changed
// as time to the next firing changes.

- setShowSecondsToBool:(BOOL)seconds
{ 
    showSeconds = seconds;
    [[self stopTimedEntry] startTimedEntry:NO];
    [self display];

    return self;
}

- setShowDateToBool: (BOOL) date
{
	showDate  = date;
    [[self stopTimedEntry] startTimedEntry:NO];
    [self display];

    return self;
}

- setMilitaryTimeToBool: (BOOL) mt
{
	militaryTime = mt;
    [[self stopTimedEntry] startTimedEntry:NO];
    [self display];

    return self;
}

- setClockTypeToInt: (int) type
{
	clockType = type;
	[[self stopTimedEntry] startTimedEntry:NO];
	[self display];

	return self;
}


// Methods to set/get background and clock hand colors.

- setBackgroundGrayToFloat:(float)gray 
{
    backgroundGray = gray;
    [self display];

    return self;
}

- setClockGrayToFloat:(float)gray
{
    clockGray = gray;
    [self display];

    return self;
}

- setMessage: (char *) buf1 : (char *) buf2
{
	strncpy( eventTime, buf1, MAX_CVIEW_WIDTH);
	strncpy( eventMessage, buf2, MAX_CVIEW_WIDTH);
	
	if( clockType == CLOCK_DIGITAL)
		[self display];
	return self;
}

// Methods to return the values of the above parameters

- (BOOL)showSeconds 
{ 
    return showSeconds; 
}

- (BOOL)showDate 
{ 
    return showDate; 
}

- (const char *)clockFaceBitmapName 
{ 
    return [clockFace name]; 
}

- (float)backgroundGray
{ 
    return backgroundGray; 
}

- (float)clockGray 
{ 
    return clockGray; 
}

// Finally, the IB-callable (target/action) versions of the above methods.

- setClockFace:sender
{
    return [self setClockFaceToBitmapNamed:[sender stringValue]];
}

- setShowSeconds:sender
{
    return [self setShowSecondsToBool:[sender state]];
}

- setShowDate:sender
{
    return [self setShowDateToBool:[sender state]];
}

- setBackgroundGray:(id)sender
{
    return [self setBackgroundGrayToFloat:[sender floatValue]];
}
    
- setClockGray:(id)sender
{
    return [self setClockGrayToFloat:[sender floatValue]];
}

// Additions to allow target/action response
// Mods by Jiro Nakamura, Jan 6, 1990
- setTarget: (id) aTarget
{
	target = aTarget;
	return self;
}

- setAction: (SEL) anAction
{
	action = anAction;
	return self;
}

// Archiving methods. 

- awake
{
    [self computeNewSizes];
    [self startTimedEntry:YES];
    return self;
}

- write:(NXTypedStream *)stream
{
    const char *bitmapName = [self clockFaceBitmapName];

    [super write:stream];
    NXWriteTypes (stream, "ffi*", 
		  &backgroundGray, &clockGray, &showSeconds, &bitmapName);
    return self;
}

- read:(NXTypedStream *)stream
{
    char *bitmapName;

    [super read:stream];
    NXReadTypes (stream, "ffi*",
		  &backgroundGray, &clockGray, &showSeconds, &bitmapName);
    [self setClockFaceToBitmapNamed:bitmapName];
    if (bitmapName) free (bitmapName);

    return self;
}

@end
