home *** CD-ROM | disk | FTP | other *** search
- // 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
-
-
-