// VennCell.m
// Copyright (c) 1992 by Paul Burchard.
// May be used in any form with appropriate acknowledgement.

#import "VennCell.h"
#import <appkit/appkit.h>

#define ARGS		2

#define MAX2(x,y)	(((x)>(y))?(x):(y))
#define MAX3(x,y,z)	(((x)>(y))?(((x)>(z))?(x):(z)):(((y)>(z))?(y):(z)))

static const char *vennDefaultLabels[1<<ARGS] ={ "", "OLD", "NEW", "" };

@implementation VennCell

- init
{
    return [self initTextCell:"12"];// "replace" function
}

- initTextCell:(const char *)aString
{
    int i, vennState;
    
    // Use intValue to hold state.
    [super initTextCell:aString];
    vennState = ([super intValue] & ((1<<(1<<ARGS))-1));
    [super setIntValue:vennState];
        
    // Create Venn Regions as ButtonCells.
    // Regions correspond in order to the bits of the state.
    for(i=0; i<(1<<ARGS); i++)
    {
	vennRegions[i] =
	    [[ButtonCell alloc] initTextCell:vennDefaultLabels[i]];
	[vennRegions[i] setType:NX_PUSHONPUSHOFF];
	[vennRegions[i] setBordered:((i>0) ? YES : NO)];
    	[vennRegions[i] setState:((vennState & (1<<i)) ? YES:NO)];
    }

    borderWidth = 4.0;
    [self awake];
    return self;
}

- awake
{
    NXRect minRect;

    // Restart dynamic variables after init or read:
    [super awake];
    
    // Restart tracking cache.
    _view = nil;
    trackingIn = nil;
    
    // Fill drawing cache with minimum dimensions.
    NXSetRect(&minRect, 0.0, 0.0, 0.0, 0.0);
    [self calcCellSize:&minRect.size];
    [self calcDrawInfo:&minRect];
    return self;
}

- free
{
    int i;
    
    for(i=0; i<(1<<ARGS); i++) [vennRegions[i] free];
    return [super free];
}

- read:(NXTypedStream *)stream
{
    int i;
    
    // Read superclass data.
    [super read:stream];
    
    // Read in subcells.
    for(i=0; i<(1<<ARGS); i++) vennRegions[i] = NXReadObject(stream);
    
    // Get appearance parameters.
    NXReadTypes(stream, "f", &borderWidth);
    return self;
}

- write:(NXTypedStream *)stream
{
    int i;
    
    // Write superclass data.
    [super write:stream];
    
    // Write out subcells.
    for(i=0; i<(1<<ARGS); i++) NXWriteObject(stream, vennRegions[i]);
    
    // Write appearance parameters.
    NXWriteTypes(stream, "f", &borderWidth);
    return self;
}

- drawSelf:(const NXRect *)cellFrame inView:controlView
{
    // Cache drawing params if necessary.
    if(!NXEqualRect(&drawRect, cellFrame)) [self calcDrawInfo:cellFrame];
    // Cache control view if necessary.
    if(controlView!=_view && [controlView isKindOf:[Control class]])
	_view = controlView;

    // Border is integral part of cell; just draw ``inside''.
    [self drawInside:cellFrame inView:controlView];
    return self;
}

- drawInside:(const NXRect *)cellFrame inView:controlView
{
    int i;
    
    // Subcells are overlapping, and therefore must be drawn in this order.
    // Using cached drawing info to place subcells.
    for(i=0; i<(1<<ARGS); i++)
	[vennRegions[i] drawSelf:&vennRects[i] inView:controlView];
    return self;
}

- calcSubCellRects:(NXRect *)theVennRects inRect:(const NXRect *)aRect
{
    int i;
    float t, h, hmin, w, wmin, x, y, p;
    NXSize minSizes[1<<ARGS];
    NXRect noRect;
    
    // Get minumum sizes for subcells first.
    // Note that ButtonCell does not give correct minimum size
    //     (neither with calcCell:, nor with calcCell:: and a zero size rect).
    // When the rect is too small horizontally, the button gets big vertically.
    // So we have to kludge by using a rect with large width.
    NXSetRect(&noRect, 0.0, 0.0, 2000.0, 0.0);
    for(i=1; i<(1<<ARGS); i++)
    	[vennRegions[i] calcCellSize:&minSizes[i] inRect:&noRect];
    t = MAX2(minSizes[1].height, minSizes[2].height);
    wmin = MAX3(minSizes[1].width/2, minSizes[1].width/2, minSizes[3].width);
    hmin = minSizes[3].height;
    
    // Calc basic spacing parameters.
    p = borderWidth;
    x = NX_X(aRect); y = NX_Y(aRect);
    w = (NX_WIDTH(aRect) - 2*p) / 3;
    h = (NX_HEIGHT(aRect) - 2*t - 2*p) / 3;
    if(w < wmin) w = wmin; 
    if(h < hmin) h = hmin;

    // Calc rectangles.
    NXSetRect(&theVennRects[0], x, y, 3*w + 2*p, 3*h + 2*t + 2*p);
    NXSetRect(&theVennRects[1], x + p, y + p + h + t, 2*w, 2*h + t);
    NXSetRect(&theVennRects[2], x + w + p, y + p, 2*w, 2*h + t);
    NXSetRect(&theVennRects[3], x + w + p, y + p + h + t, w, h);
    return self;
}

- calcDrawInfo:(const NXRect *)aRect
{
    // Cache drawing information.
    // Called by controlView's calcSize method.
    drawRect = *aRect;
    [self calcSubCellRects:vennRects inRect:&drawRect];
    return self;
}

- calcCellSize:(NXSize *)minSize
{
    NXRect noRect;
    
    // Compute minimum size of Venn cell into minSize.
    NXSetRect(&noRect, 0.0, 0.0, 0.0, 0.0);
    [self calcCellSize:minSize inRect:&noRect];
    return self;
}

- calcCellSize:(NXSize *)minSize inRect:(const NXRect *)aRect
{
    NXRect rects[1<<ARGS], noRect;
    
    // Compute minimum size of Venn cell into theSize.
    // This does not adapt with aRect.
    NXSetRect(&noRect, 0.0, 0.0, 0.0, 0.0);
    [self calcSubCellRects:rects inRect:&noRect];
    minSize->width = NX_WIDTH(&rects[0]);
    minSize->height = NX_HEIGHT(&rects[0]);
    return self;
}

- highlight:(const NXRect *)cellFrame inView:controlView lit:(BOOL)flag
{
    // No-op: highlighting done by subcells during tracking.
    return self;
}

- (BOOL)startTrackingAt:(const NXPoint *)startPoint inView:controlView
{
    int i;

    // Find out which subcell we are now in.
    for(trackingIn=nil, i=0; i<(1<<ARGS); i++)
	if([controlView mouse:startPoint inRect:&vennRects[i]])
	    trackingIn = vennRegions[i];
    if(!trackingIn) return NO;

    // Toggle the region defined by the subcell, updating our state.
    // Tell subcell to begin tracking.
    [trackingIn incrementState];//???!!!
    [self toggleVennRegion:trackingIn];
    [trackingIn startTrackingAt:startPoint inView:controlView];
    return YES;
}

- (BOOL)continueTracking:(const NXPoint *)lastPoint at:(const NXPoint *)currentPoint inView:controlView
{
    int i;
    id nowTrackingIn;
    
    // Find out which subcell we are now in.
    for(nowTrackingIn=nil, i=0; i<(1<<ARGS); i++)
	if([controlView mouse:currentPoint inRect:&vennRects[i]])
	    nowTrackingIn = vennRegions[i];
    if(!nowTrackingIn)
    {
    	// Mouse left Venn cell.
	// Stop tracking subcell; notify control view to stop tracking us.
	[trackingIn stopTracking:lastPoint at:currentPoint inView:controlView mouseIsUp:NO];
	trackingIn = nil; return NO;
    }
    
    // If tracking in new subcell, then:
    // Stop tracking old subcell.
    // Toggle the region defined by the new subcell, updating our state.
    // Tell new subcell to begin tracking.
    if(nowTrackingIn != trackingIn)
    {
	[trackingIn stopTracking:lastPoint at:currentPoint inView:controlView mouseIsUp:NO];
	trackingIn = nowTrackingIn;
	[trackingIn incrementState];//???!!!
	[self toggleVennRegion:trackingIn];
	[trackingIn startTrackingAt:currentPoint inView:controlView];
    }
    
    // Otherwise, just tell old subcell to keep going.
    else [trackingIn continueTracking:lastPoint at:currentPoint inView:controlView];
    return YES;
}

- stopTracking:(const NXPoint *)lastPoint at:(const NXPoint *)stopPoint inView:controlView mouseIsUp:(BOOL)flag
{
    int i;
    id nowTrackingIn;
    
    // Find out which subcell we are now in.
    for(nowTrackingIn=nil, i=0; i<(1<<ARGS); i++)
	if([controlView mouse:stopPoint inRect:&vennRects[i]])
	    nowTrackingIn = vennRegions[i];
    if(!nowTrackingIn)
    {
    	// Mouse left Venn cell.
	// Stop tracking subcell; notify control view to stop tracking us.
	[trackingIn stopTracking:lastPoint at:stopPoint inView:controlView mouseIsUp:NO];
	trackingIn = nil;
	
	// Make sure user sees new state.
	[controlView update];
	return self;
    }
    
    // If tracking in new subcell, then:
    // Stop tracking old subcell.
    // Toggle the region defined by the new subcell, updating our state.
    // Tell new subcell to start and stop tracking.
    if(nowTrackingIn != trackingIn)
    {
	[trackingIn stopTracking:lastPoint at:stopPoint inView:controlView mouseIsUp:NO];
	trackingIn = nowTrackingIn;
	[trackingIn incrementState];//???!!!
	[self toggleVennRegion:trackingIn];
	[trackingIn startTrackingAt:stopPoint inView:controlView];
	[trackingIn stopTracking:stopPoint at:stopPoint inView:controlView mouseIsUp:NO];
    }

    // Otherwise just stop tracking old subcell.
    else [trackingIn stopTracking:lastPoint at:stopPoint inView:controlView mouseIsUp:NO];
    
    // Make sure the user sees the new state.
    [controlView update];
    return self;
}

- (int)argCount;
{
    // For extensibility to ternary Boolean ops...
    return ARGS;
}

- (int)state
{
    return ([super intValue] & ((1<<(1<<ARGS))-1));
}

- (BOOL)evalOp:(BOOL)arg1 :(BOOL)arg2
{
    // The i-th bit of the state tells whether the Boolean function is
    // true when evaluated with j-th arg = j-th bit of i.
    return(([self state] & (1<<((arg1 ? 1:0) | (arg2 ? 2:0)))) ? YES:NO);
}

- setStateFromOp:(BOOLOP)anOp
{
    int i, s;
    
    for(s=i=0; i<(1<<ARGS); i++) if(anOp(((i&1) ? YES:NO), ((i&2) ? YES:NO)))
    	s |= (1<<i);
    return [self setState:s];
}

- setState:(int)value
{
    int i;
    
    // Use intValue to hold state.
    [super setIntValue:(value & ((1<<(1<<ARGS))-1))];
    
    // Set state of individual Venn regions according to intValue.
    // Regions correspond in order to the bits of the state.
    for(i=0; i<(1<<ARGS); i++)
    	[vennRegions[i] setState:((value & (1<<i)) ? YES:NO)];
    
    // Redraw if necessary, and notify target of possible state change.
    if(_view) [_view update];
    [target perform:action with:self];
    return self;
}

- incrementState
{
    // Does nothing.
    // This is because the default trackMouse:inRect:ofView: calls 
    // incrementState to try to toggle the cell state as part of its user
    // response; this is incompatible with how VennCell's state works.
    return self;
}

- toggleVennRegion:sender;
{
    int i;
    
    for(i=0; i<(1<<ARGS); i++) if(sender == vennRegions[i]) break;
    if(i >= (1<<ARGS)) return nil;
    return [self setState:([self state] ^ (1<<i))];
}

- takeStateFrom:sender
{
    return [self setState:[sender state]];
}

- takeStateFromIntValue:sender
{
    return [self setState:[sender intValue]];
}

- setStateReplace:sender
{
    return [self setState:12];
}

- setStateRefine:sender
{
    return [self setState:8];
}

- setStateAdd:sender
{
    return [self setState:14];
}

- setStateRemove:sender
{
    return [self setState:2];
}

- setStateReverse:sender
{
    return [self setState:3];
}

- setFirstTitle:(const char *)aString
{
    [vennRegions[1] setTitle:aString];
    
    // Recompute cached subcell sizes and redraw cell if necessary.
    [self calcDrawInfo:&drawRect];
    if(_view) [_view update];
    return self;
}

- setSecondTitle:(const char *)aString
{
    [vennRegions[2] setTitle:aString];
    
    // Recompute cached subcell sizes and redraw cell if necessary.
    [self calcDrawInfo:&drawRect];
    if(_view) [_view update];
    return self;
}

- takeFirstTitleFrom:sender
{
    if([sender respondsTo:@selector(title)])
	return [self setFirstTitle:[sender title]];
    else if([sender respondsTo:@selector(stringValue)])
	return [self setFirstTitle:[sender stringValue]];
    else return nil;
}

- takeSecondTitleFrom:sender
{
    if([sender respondsTo:@selector(title)])
	return [self setSecondTitle:[sender title]];
    else if([sender respondsTo:@selector(stringValue)])
	return [self setSecondTitle:[sender stringValue]];
    else return nil;
}

- (const char *)firstTitle
{
    return [vennRegions[1] title];
}

- (const char *)secondTitle
{
    return [vennRegions[2] title];
}

- setFont:fontObj
{
    int i;
    
    // If initialized, set font in subcells and recompute sizes.
    for(i=0; i<(1<<ARGS); i++) if(!vennRegions[i]) break;
    if(i >= (1<<ARGS))
    {
	for(i=0; i<(1<<ARGS); i++) [vennRegions[i] setFont:fontObj];
	[self calcDrawInfo:&drawRect];
    }

    // Set our font and (implicitly) update cell.
    [super setFont:fontObj];
    return self;
}

- setBorderWidth:(float)width
{
    // Make sure border width is positive.
    borderWidth = width;
    if(borderWidth < 0.0) borderWidth = 0.0;
    
    // Recompute cached subcell sizes and redraw cell if necessary.
    [self calcDrawInfo:&drawRect];
    if(_view) [_view update];
    return self;
}

- (float)borderWidth
{
    return borderWidth;
}

@end


