home *** CD-ROM | disk | FTP | other *** search
/ Education Sampler 1992 [NeXTSTEP] / Education_1992_Sampler.iso / Programming / Classes / 2.0_Venn / VennCell.m < prev    next >
Encoding:
Text File  |  1992-06-16  |  12.2 KB  |  479 lines

  1. // VennCell.m
  2. // Copyright (c) 1992 by Paul Burchard.
  3. // May be used in any form with appropriate acknowledgement.
  4.  
  5. #import "VennCell.h"
  6. #import <appkit/appkit.h>
  7.  
  8. #define ARGS        2
  9.  
  10. #define MAX2(x,y)    (((x)>(y))?(x):(y))
  11. #define MAX3(x,y,z)    (((x)>(y))?(((x)>(z))?(x):(z)):(((y)>(z))?(y):(z)))
  12.  
  13. static const char *vennDefaultLabels[1<<ARGS] ={ "", "OLD", "NEW", "" };
  14.  
  15. @implementation VennCell
  16.  
  17. - init
  18. {
  19.     return [self initTextCell:"12"];// "replace" function
  20. }
  21.  
  22. - initTextCell:(const char *)aString
  23. {
  24.     int i, vennState;
  25.     
  26.     // Use intValue to hold state.
  27.     [super initTextCell:aString];
  28.     vennState = ([super intValue] & ((1<<(1<<ARGS))-1));
  29.     [super setIntValue:vennState];
  30.         
  31.     // Create Venn Regions as ButtonCells.
  32.     // Regions correspond in order to the bits of the state.
  33.     for(i=0; i<(1<<ARGS); i++)
  34.     {
  35.     vennRegions[i] =
  36.         [[ButtonCell alloc] initTextCell:vennDefaultLabels[i]];
  37.     [vennRegions[i] setType:NX_PUSHONPUSHOFF];
  38.     [vennRegions[i] setBordered:((i>0) ? YES : NO)];
  39.         [vennRegions[i] setState:((vennState & (1<<i)) ? YES:NO)];
  40.     }
  41.  
  42.     borderWidth = 4.0;
  43.     [self awake];
  44.     return self;
  45. }
  46.  
  47. - awake
  48. {
  49.     NXRect minRect;
  50.  
  51.     // Restart dynamic variables after init or read:
  52.     [super awake];
  53.     
  54.     // Restart tracking cache.
  55.     _view = nil;
  56.     trackingIn = nil;
  57.     
  58.     // Fill drawing cache with minimum dimensions.
  59.     NXSetRect(&minRect, 0.0, 0.0, 0.0, 0.0);
  60.     [self calcCellSize:&minRect.size];
  61.     [self calcDrawInfo:&minRect];
  62.     return self;
  63. }
  64.  
  65. - free
  66. {
  67.     int i;
  68.     
  69.     for(i=0; i<(1<<ARGS); i++) [vennRegions[i] free];
  70.     return [super free];
  71. }
  72.  
  73. - read:(NXTypedStream *)stream
  74. {
  75.     int i;
  76.     
  77.     // Read superclass data.
  78.     [super read:stream];
  79.     
  80.     // Read in subcells.
  81.     for(i=0; i<(1<<ARGS); i++) vennRegions[i] = NXReadObject(stream);
  82.     
  83.     // Get appearance parameters.
  84.     NXReadTypes(stream, "f", &borderWidth);
  85.     return self;
  86. }
  87.  
  88. - write:(NXTypedStream *)stream
  89. {
  90.     int i;
  91.     
  92.     // Write superclass data.
  93.     [super write:stream];
  94.     
  95.     // Write out subcells.
  96.     for(i=0; i<(1<<ARGS); i++) NXWriteObject(stream, vennRegions[i]);
  97.     
  98.     // Write appearance parameters.
  99.     NXWriteTypes(stream, "f", &borderWidth);
  100.     return self;
  101. }
  102.  
  103. - drawSelf:(const NXRect *)cellFrame inView:controlView
  104. {
  105.     // Cache drawing params if necessary.
  106.     if(!NXEqualRect(&drawRect, cellFrame)) [self calcDrawInfo:cellFrame];
  107.     // Cache control view if necessary.
  108.     if(controlView!=_view && [controlView isKindOf:[Control class]])
  109.     _view = controlView;
  110.  
  111.     // Border is integral part of cell; just draw ``inside''.
  112.     [self drawInside:cellFrame inView:controlView];
  113.     return self;
  114. }
  115.  
  116. - drawInside:(const NXRect *)cellFrame inView:controlView
  117. {
  118.     int i;
  119.     
  120.     // Subcells are overlapping, and therefore must be drawn in this order.
  121.     // Using cached drawing info to place subcells.
  122.     for(i=0; i<(1<<ARGS); i++)
  123.     [vennRegions[i] drawSelf:&vennRects[i] inView:controlView];
  124.     return self;
  125. }
  126.  
  127. - calcSubCellRects:(NXRect *)theVennRects inRect:(const NXRect *)aRect
  128. {
  129.     int i;
  130.     float t, h, hmin, w, wmin, x, y, p;
  131.     NXSize minSizes[1<<ARGS];
  132.     NXRect noRect;
  133.     
  134.     // Get minumum sizes for subcells first.
  135.     // Note that ButtonCell does not give correct minimum size
  136.     //     (neither with calcCell:, nor with calcCell:: and a zero size rect).
  137.     // When the rect is too small horizontally, the button gets big vertically.
  138.     // So we have to kludge by using a rect with large width.
  139.     NXSetRect(&noRect, 0.0, 0.0, 2000.0, 0.0);
  140.     for(i=1; i<(1<<ARGS); i++)
  141.         [vennRegions[i] calcCellSize:&minSizes[i] inRect:&noRect];
  142.     t = MAX2(minSizes[1].height, minSizes[2].height);
  143.     wmin = MAX3(minSizes[1].width/2, minSizes[1].width/2, minSizes[3].width);
  144.     hmin = minSizes[3].height;
  145.     
  146.     // Calc basic spacing parameters.
  147.     p = borderWidth;
  148.     x = NX_X(aRect); y = NX_Y(aRect);
  149.     w = (NX_WIDTH(aRect) - 2*p) / 3;
  150.     h = (NX_HEIGHT(aRect) - 2*t - 2*p) / 3;
  151.     if(w < wmin) w = wmin; 
  152.     if(h < hmin) h = hmin;
  153.  
  154.     // Calc rectangles.
  155.     NXSetRect(&theVennRects[0], x, y, 3*w + 2*p, 3*h + 2*t + 2*p);
  156.     NXSetRect(&theVennRects[1], x + p, y + p + h + t, 2*w, 2*h + t);
  157.     NXSetRect(&theVennRects[2], x + w + p, y + p, 2*w, 2*h + t);
  158.     NXSetRect(&theVennRects[3], x + w + p, y + p + h + t, w, h);
  159.     return self;
  160. }
  161.  
  162. - calcDrawInfo:(const NXRect *)aRect
  163. {
  164.     // Cache drawing information.
  165.     // Called by controlView's calcSize method.
  166.     drawRect = *aRect;
  167.     [self calcSubCellRects:vennRects inRect:&drawRect];
  168.     return self;
  169. }
  170.  
  171. - calcCellSize:(NXSize *)minSize
  172. {
  173.     NXRect noRect;
  174.     
  175.     // Compute minimum size of Venn cell into minSize.
  176.     NXSetRect(&noRect, 0.0, 0.0, 0.0, 0.0);
  177.     [self calcCellSize:minSize inRect:&noRect];
  178.     return self;
  179. }
  180.  
  181. - calcCellSize:(NXSize *)minSize inRect:(const NXRect *)aRect
  182. {
  183.     NXRect rects[1<<ARGS], noRect;
  184.     
  185.     // Compute minimum size of Venn cell into theSize.
  186.     // This does not adapt with aRect.
  187.     NXSetRect(&noRect, 0.0, 0.0, 0.0, 0.0);
  188.     [self calcSubCellRects:rects inRect:&noRect];
  189.     minSize->width = NX_WIDTH(&rects[0]);
  190.     minSize->height = NX_HEIGHT(&rects[0]);
  191.     return self;
  192. }
  193.  
  194. - highlight:(const NXRect *)cellFrame inView:controlView lit:(BOOL)flag
  195. {
  196.     // No-op: highlighting done by subcells during tracking.
  197.     return self;
  198. }
  199.  
  200. - (BOOL)startTrackingAt:(const NXPoint *)startPoint inView:controlView
  201. {
  202.     int i;
  203.  
  204.     // Find out which subcell we are now in.
  205.     for(trackingIn=nil, i=0; i<(1<<ARGS); i++)
  206.     if([controlView mouse:startPoint inRect:&vennRects[i]])
  207.         trackingIn = vennRegions[i];
  208.     if(!trackingIn) return NO;
  209.  
  210.     // Toggle the region defined by the subcell, updating our state.
  211.     // Tell subcell to begin tracking.
  212.     [trackingIn incrementState];//???!!!
  213.     [self toggleVennRegion:trackingIn];
  214.     [trackingIn startTrackingAt:startPoint inView:controlView];
  215.     return YES;
  216. }
  217.  
  218. - (BOOL)continueTracking:(const NXPoint *)lastPoint at:(const NXPoint *)currentPoint inView:controlView
  219. {
  220.     int i;
  221.     id nowTrackingIn;
  222.     
  223.     // Find out which subcell we are now in.
  224.     for(nowTrackingIn=nil, i=0; i<(1<<ARGS); i++)
  225.     if([controlView mouse:currentPoint inRect:&vennRects[i]])
  226.         nowTrackingIn = vennRegions[i];
  227.     if(!nowTrackingIn)
  228.     {
  229.         // Mouse left Venn cell.
  230.     // Stop tracking subcell; notify control view to stop tracking us.
  231.     [trackingIn stopTracking:lastPoint at:currentPoint inView:controlView mouseIsUp:NO];
  232.     trackingIn = nil; return NO;
  233.     }
  234.     
  235.     // If tracking in new subcell, then:
  236.     // Stop tracking old subcell.
  237.     // Toggle the region defined by the new subcell, updating our state.
  238.     // Tell new subcell to begin tracking.
  239.     if(nowTrackingIn != trackingIn)
  240.     {
  241.     [trackingIn stopTracking:lastPoint at:currentPoint inView:controlView mouseIsUp:NO];
  242.     trackingIn = nowTrackingIn;
  243.     [trackingIn incrementState];//???!!!
  244.     [self toggleVennRegion:trackingIn];
  245.     [trackingIn startTrackingAt:currentPoint inView:controlView];
  246.     }
  247.     
  248.     // Otherwise, just tell old subcell to keep going.
  249.     else [trackingIn continueTracking:lastPoint at:currentPoint inView:controlView];
  250.     return YES;
  251. }
  252.  
  253. - stopTracking:(const NXPoint *)lastPoint at:(const NXPoint *)stopPoint inView:controlView mouseIsUp:(BOOL)flag
  254. {
  255.     int i;
  256.     id nowTrackingIn;
  257.     
  258.     // Find out which subcell we are now in.
  259.     for(nowTrackingIn=nil, i=0; i<(1<<ARGS); i++)
  260.     if([controlView mouse:stopPoint inRect:&vennRects[i]])
  261.         nowTrackingIn = vennRegions[i];
  262.     if(!nowTrackingIn)
  263.     {
  264.         // Mouse left Venn cell.
  265.     // Stop tracking subcell; notify control view to stop tracking us.
  266.     [trackingIn stopTracking:lastPoint at:stopPoint inView:controlView mouseIsUp:NO];
  267.     trackingIn = nil;
  268.     
  269.     // Make sure user sees new state.
  270.     [controlView update];
  271.     return self;
  272.     }
  273.     
  274.     // If tracking in new subcell, then:
  275.     // Stop tracking old subcell.
  276.     // Toggle the region defined by the new subcell, updating our state.
  277.     // Tell new subcell to start and stop tracking.
  278.     if(nowTrackingIn != trackingIn)
  279.     {
  280.     [trackingIn stopTracking:lastPoint at:stopPoint inView:controlView mouseIsUp:NO];
  281.     trackingIn = nowTrackingIn;
  282.     [trackingIn incrementState];//???!!!
  283.     [self toggleVennRegion:trackingIn];
  284.     [trackingIn startTrackingAt:stopPoint inView:controlView];
  285.     [trackingIn stopTracking:stopPoint at:stopPoint inView:controlView mouseIsUp:NO];
  286.     }
  287.  
  288.     // Otherwise just stop tracking old subcell.
  289.     else [trackingIn stopTracking:lastPoint at:stopPoint inView:controlView mouseIsUp:NO];
  290.     
  291.     // Make sure the user sees the new state.
  292.     [controlView update];
  293.     return self;
  294. }
  295.  
  296. - (int)argCount;
  297. {
  298.     // For extensibility to ternary Boolean ops...
  299.     return ARGS;
  300. }
  301.  
  302. - (int)state
  303. {
  304.     return ([super intValue] & ((1<<(1<<ARGS))-1));
  305. }
  306.  
  307. - (BOOL)evalOp:(BOOL)arg1 :(BOOL)arg2
  308. {
  309.     // The i-th bit of the state tells whether the Boolean function is
  310.     // true when evaluated with j-th arg = j-th bit of i.
  311.     return(([self state] & (1<<((arg1 ? 1:0) | (arg2 ? 2:0)))) ? YES:NO);
  312. }
  313.  
  314. - setStateFromOp:(BOOLOP)anOp
  315. {
  316.     int i, s;
  317.     
  318.     for(s=i=0; i<(1<<ARGS); i++) if(anOp(((i&1) ? YES:NO), ((i&2) ? YES:NO)))
  319.         s |= (1<<i);
  320.     return [self setState:s];
  321. }
  322.  
  323. - setState:(int)value
  324. {
  325.     int i;
  326.     
  327.     // Use intValue to hold state.
  328.     [super setIntValue:(value & ((1<<(1<<ARGS))-1))];
  329.     
  330.     // Set state of individual Venn regions according to intValue.
  331.     // Regions correspond in order to the bits of the state.
  332.     for(i=0; i<(1<<ARGS); i++)
  333.         [vennRegions[i] setState:((value & (1<<i)) ? YES:NO)];
  334.     
  335.     // Redraw if necessary, and notify target of possible state change.
  336.     if(_view) [_view update];
  337.     [target perform:action with:self];
  338.     return self;
  339. }
  340.  
  341. - incrementState
  342. {
  343.     // Does nothing.
  344.     // This is because the default trackMouse:inRect:ofView: calls 
  345.     // incrementState to try to toggle the cell state as part of its user
  346.     // response; this is incompatible with how VennCell's state works.
  347.     return self;
  348. }
  349.  
  350. - toggleVennRegion:sender;
  351. {
  352.     int i;
  353.     
  354.     for(i=0; i<(1<<ARGS); i++) if(sender == vennRegions[i]) break;
  355.     if(i >= (1<<ARGS)) return nil;
  356.     return [self setState:([self state] ^ (1<<i))];
  357. }
  358.  
  359. - takeStateFrom:sender
  360. {
  361.     return [self setState:[sender state]];
  362. }
  363.  
  364. - takeStateFromIntValue:sender
  365. {
  366.     return [self setState:[sender intValue]];
  367. }
  368.  
  369. - setStateReplace:sender
  370. {
  371.     return [self setState:12];
  372. }
  373.  
  374. - setStateRefine:sender
  375. {
  376.     return [self setState:8];
  377. }
  378.  
  379. - setStateAdd:sender
  380. {
  381.     return [self setState:14];
  382. }
  383.  
  384. - setStateRemove:sender
  385. {
  386.     return [self setState:2];
  387. }
  388.  
  389. - setStateReverse:sender
  390. {
  391.     return [self setState:3];
  392. }
  393.  
  394. - setFirstTitle:(const char *)aString
  395. {
  396.     [vennRegions[1] setTitle:aString];
  397.     
  398.     // Recompute cached subcell sizes and redraw cell if necessary.
  399.     [self calcDrawInfo:&drawRect];
  400.     if(_view) [_view update];
  401.     return self;
  402. }
  403.  
  404. - setSecondTitle:(const char *)aString
  405. {
  406.     [vennRegions[2] setTitle:aString];
  407.     
  408.     // Recompute cached subcell sizes and redraw cell if necessary.
  409.     [self calcDrawInfo:&drawRect];
  410.     if(_view) [_view update];
  411.     return self;
  412. }
  413.  
  414. - takeFirstTitleFrom:sender
  415. {
  416.     if([sender respondsTo:@selector(title)])
  417.     return [self setFirstTitle:[sender title]];
  418.     else if([sender respondsTo:@selector(stringValue)])
  419.     return [self setFirstTitle:[sender stringValue]];
  420.     else return nil;
  421. }
  422.  
  423. - takeSecondTitleFrom:sender
  424. {
  425.     if([sender respondsTo:@selector(title)])
  426.     return [self setSecondTitle:[sender title]];
  427.     else if([sender respondsTo:@selector(stringValue)])
  428.     return [self setSecondTitle:[sender stringValue]];
  429.     else return nil;
  430. }
  431.  
  432. - (const char *)firstTitle
  433. {
  434.     return [vennRegions[1] title];
  435. }
  436.  
  437. - (const char *)secondTitle
  438. {
  439.     return [vennRegions[2] title];
  440. }
  441.  
  442. - setFont:fontObj
  443. {
  444.     int i;
  445.     
  446.     // If initialized, set font in subcells and recompute sizes.
  447.     for(i=0; i<(1<<ARGS); i++) if(!vennRegions[i]) break;
  448.     if(i >= (1<<ARGS))
  449.     {
  450.     for(i=0; i<(1<<ARGS); i++) [vennRegions[i] setFont:fontObj];
  451.     [self calcDrawInfo:&drawRect];
  452.     }
  453.  
  454.     // Set our font and (implicitly) update cell.
  455.     [super setFont:fontObj];
  456.     return self;
  457. }
  458.  
  459. - setBorderWidth:(float)width
  460. {
  461.     // Make sure border width is positive.
  462.     borderWidth = width;
  463.     if(borderWidth < 0.0) borderWidth = 0.0;
  464.     
  465.     // Recompute cached subcell sizes and redraw cell if necessary.
  466.     [self calcDrawInfo:&drawRect];
  467.     if(_view) [_view update];
  468.     return self;
  469. }
  470.  
  471. - (float)borderWidth
  472. {
  473.     return borderWidth;
  474. }
  475.  
  476. @end
  477.  
  478.  
  479.