home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.0 / NeXTSTEP3.0.iso / NextDeveloper / Examples / AppKit / BusyBox / GaugeView.m < prev    next >
Text File  |  1990-10-26  |  8KB  |  292 lines

  1. /* 
  2.  * GaugeView.m, analog gauge view
  3.  * Author: Bruce Blumberg, NeXT Developer Support Group.
  4.  * Originally written for 0.6 mid 1988, modified for 1.0 by Ali Ozer.
  5.  * Redesigned for 2.0 by Julie Zelenski, NeXT Developer Support
  6.  *
  7.  * Subclass of view to implement a simple round analog gauge. You can set the 
  8.  * minimum, maximum value, start angle, angle range, title, font, and more.  
  9.  * It is a pretty generic round gauge view, if you ever have need for one.
  10.  *
  11.  * You may freely copy, distribute and reuse the code in this example.  
  12.  * NeXT disclaims any warranty of any kind, expressed or implied, as to 
  13.  * its fitness for any particular use.
  14.  */
  15.  
  16. #import "GaugeView.h"
  17. #import "Gauge.h"    // PSwrap routines
  18. #import <appkit/Font.h>
  19. #import <appkit/Window.h>
  20. #import <appkit/Slider.h>
  21. #import <soundkit/Sound.h>
  22. #import <dpsclient/wraps.h>    // PScomposite(), ...
  23. #import <math.h>        // sin(), cos(), ...
  24. #import <string.h>        // strcpy(), ...
  25.  
  26.  
  27. @implementation GaugeView : View
  28.  
  29.  
  30. #define HANDRATIO    0.65    /* hand length compared face size */
  31.  
  32. - initFrame:(NXRect *)frameRect; 
  33. /* Basic initFrame method which just calls the more complicated 
  34.  * initFrame:min:max... method with some generally good default values.
  35.  */
  36. {
  37.     [self initFrame:frameRect min:0.0 max:100.0 
  38.             startAngle:215.0 range:250.0 tickInterval:10]; 
  39.     return self;
  40. }
  41.  
  42. - initFrame:(NXRect *)frameRect min:(float)min max:(float)max 
  43.     startAngle:(float)start range:(float)range ti(6terval:(int)interval
  44. /* Init method for newly created analog gauge. It creates the face of the 
  45.  * gauge in an offscreen window called cacheWindow when drawSelf:: is called 
  46.  * the image is composited into the view and the hand is drawn on top of the 
  47.  * face.
  48.  */
  49. {
  50.     [super initFrame:frameRect];
  51.     /* Create offscreen window for face. Note that the defer: argument
  52.      * has to be NO for windows which will remain offscreen. Deferred
  53.      * windows only get created on a orderFront: (or any other method
  54.      * that causes them to come on screen).
  55.      */
  56.     cacheWindow = [[Window allocFromZone:[self zone]]
  57.                  initContent:frameRect 
  58.                                style:NX_PLAINSTYLE
  59.                  backing:NX_RETAINED 
  60.               buttonMask:0 
  61.                    defer:NO];    
  62.     startAngle = start;
  63.     angleRange = range;
  64.     tickInterval = interval;
  65.     value = min;
  66.     [self setMin:min];
  67.     [self setMax:max];
  68.     [self setFont:"Helvetica" size:10.0];
  69.     [self setTitle:"Stress"];
  70.     center.x = bounds.size.width/2.0;
  71.     center.y = bounds.size.height/2.0;
  72.     radius = (bounds.size.height/2.0) - 8.0;
  73.  
  74.     /* This pswrap creates a PS function which is used to draw the hand. */
  75.     PSWmakeHand(radius*HANDRATIO);
  76.     [self drawFace];
  77.     return self;
  78. }
  79.  
  80. - free;
  81. /* Free cacheWindow, too!
  82.  */
  83. {
  84.     [cacheWindow free];
  85.     return [super free];
  86. }
  87.  
  88.  
  89. /* SET PARAMETERS */
  90.  
  91. - setFont:(char *)fName size:(float)fSize
  92. /* Sets font used for drawing title and numbers. Change is displayed
  93.  * next time drawSelf:: is executed. needRedraw is set to indicate that 
  94.  * face image must be redrawn.
  95.  */
  96. {    
  97.     font = [Font newFont:fName size:fSize style:0 matrix:NX_IDENTITYMATRIX];
  98.     needRedraw = YES;
  99.     return self;
  100. }
  101.  
  102. - setTitle:(char *)str;
  103. /* Sets title of gauge.  needRedraw is set to indicate that 
  104.  * face image must be redrawn.
  105.  */
  106. {
  107.     strcpy(title,str);
  108.     needRedraw = YES;
  109.     return self;
  110. }
  111.  
  112. - setMin:(float)newValue;
  113. /* Sets minimum for gauge, recalculates degreesPerUnit which is used to 
  114.  * determine interval of ticks and labels. needRedraw is set to indicate that 
  115.  * face image must be redrawn.
  116.  */
  117. {
  118.     minValue = newValue;
  119.     degreesPerUnit = angleRange/(maxValue-minValue);
  120.     needRedraw = YES;
  121.     return self;
  122. }
  123.  
  124. - setMax:(float)newValue;
  125. /* Sets maximum for gauge, recalculates degreesPerUnit which is used to 
  126.  * determine interval of ti(7and labels. needRedraw is set to indicate that 
  127.  * face image must be redrawn.
  128.  */
  129. {
  130.     maxValue = newValue;
  131.     degreesPerUnit = angleRange/(maxValue-minValue);
  132.     needRedraw = YES;
  133.     return self;
  134. }
  135.  
  136. - setStartAngle:(float)newValue;
  137. /* Sets start angle for gauge, which is the angle of the arm when at the 
  138.  * minimum value.  needRedraw is set to indicate that face image must be 
  139.  * redrawn.
  140.  */
  141. {
  142.     startAngle = newValue;
  143.     needRedraw = YES;
  144.     return self;
  145. }
  146.  
  147. - setAngleRange:(float)newValue;
  148. /* Sets angle range for gauge, which is the sweep of the arm from minimum
  149.  * value to maximum value.  The value cannot exceed 360 degrees (a full
  150.  * revolution).  Recalculates degreesPerUnit which is used to 
  151.  * determine interval of ticks and labels. needRedraw is set to indicate that 
  152.  * face image must be redrawn.
  153.  */
  154. {
  155.     if (newValue > 360) newValue = 360.0;
  156.     angleRange = newValue;
  157.     degreesPerUnit = angleRange/(maxValue-minValue);
  158.     needRedraw = YES;
  159.     return self;
  160. }
  161.  
  162. - setTickInterval:(int)newValue;
  163. /* Sets tick interval for gauge, which is number of units between tick
  164.  * marks on gauge face.  needRedraw is set to indicate that 
  165.  * face image must be redrawn.
  166.  */
  167. {
  168.     tickInterval = newValue;
  169.     needRedraw = YES;
  170.     return self;
  171. }
  172.  
  173.  
  174. - setValueFormCell:anObject;
  175. {
  176.     valueFormCell = anObject;
  177.     [valueFormCell setFloatingPointFormat:NO left:3 right:0];
  178.     return self;
  179. }
  180.  
  181. - (BOOL)textWillEnd:textObject;
  182. /* Rejects entry into the form cell if it isn't in the range of the
  183.  * min and max value for the gauge.
  184.  */
  185. {
  186.     id cell;
  187.     float newValue;
  188.     
  189.     cell = [textObject delegate];
  190.     newValue = [cell floatValue];
  191.     return (newValue > maxValue) || (newValue < minValue);
  192. }
  193.  
  194. /* TARGET/ACTION METHODS  */
  195.  
  196. - changeValue:sender
  197. /* Target/Action for a IB control.  Takes floatValue from control (could be
  198.  * slider or field), makes sure slider and field are in sync,  displays arm 
  199.  * at new value.
  200.  */
  201. {   float newValue;
  202.  
  203.     newValue = [sender floatValue];
  204.     if (newValue != value) {    // if value changed
  205.         value = newValue;
  206.         if (sender == valueSlider)
  207.         [valueFormCell setFloatValue:newValue];
  208.     else
  209.         [valueSlider setFloatValue:newValue];
  210.     [self display]; 
  211.         if (value == maxValue)  // if at maximum value
  212.             [[Sound findSoundFor:"HighStress"] play];
  213.     }
  214.     return(8f;
  215. }
  216.     
  217.     
  218. /* PRIVATE METHODS */
  219.  
  220. - drawFace
  221. /* drawFace draws the gauge face image in the offscreen window.  It erases
  222.  * the background, draws the circular border, displays the gauge title, 
  223.  * draws the tick marks and labels them appropriately. The offscreen cache 
  224.  * is composited on screen and then the hand is drawn on top of the face 
  225.  * for the current gauge value.
  226.  */
  227. {    
  228.     float angle, angleIncrement;
  229.     int number;
  230.     NXSize string;
  231.     NXPoint pt;
  232.     char numString[10];
  233.     
  234.     [[cacheWindow contentView] lockFocus];
  235.     
  236.     PSsetgray(NX_LTGRAY);
  237.     NXRectFill(&bounds);
  238.     PSWdrawBorder(center.x, center.y, radius);
  239.     angleIncrement = angleRange/((maxValue-minValue)/tickInterval); 
  240.     PSWdrawTicks(center.x, center.y, radius*HANDRATIO,
  241.             angleIncrement/2, startAngle, startAngle+angleRange); 
  242.     [font set];    
  243.     string.height = [font pointSize];
  244.     string.width = [font getWidthOf:title];
  245.     PSWdrawString((bounds.size.width-string.width)/2, center.y+8, title);
  246.     
  247.     number =  minValue;
  248.     for(angle=startAngle;angle>=startAngle-angleRange;angle -= angleIncrement){
  249.         sprintf(numString,"%d",number);
  250.     string.width = [font getWidthOf:numString];
  251.     pt.x = cos(angle/57.3)*(radius-1.0-string.width/2)+ center.x; 
  252.     pt.y = sin(angle/57.3)*(radius-1.0-string.height/2) + center.y ;
  253.     PSWdrawString(pt.x-(string.width/2), pt.y-(string.height/2.5),
  254.             numString);
  255.     number += tickInterval;
  256.     }
  257.     
  258.     [[cacheWindow contentView] unlockFocus]; 
  259.     needRedraw = NO;
  260.     return self;
  261. }    
  262.  
  263.  
  264. - drawHand
  265. /* Calculates the angle for the current value and draws the hand there.
  266.  */
  267. {
  268.     float valueAngle;
  269.     
  270.     valueAngle = startAngle - degreesPerUnit*(value-minValue);
  271.     PSWdrawHand(center.x,center.y,valueAngle);
  272.     return self;
  273. }
  274.  
  275. - drawSelf:(NXRect *)drawRects :(int)rectCount
  276. /* Draws face and hand of gauge. If needRedraw is YES, a parameter has 
  277.  * changed and the face must be redrawn in the offscreen window. 
  278.  * Otherwise, the image in cacheWindow is copied into the bounds of the 
  279.  * view, and a PSWrap is called which draws the hand in the 
  280.  * correct position.
  281.  */
  282. {
  283.     
  284.     if (needRedraw)
  285.         [self drawFace];
  286.     PScomposite(0.0, 0.0, bounds.size.width, bounds.size.height,
  287.             [cacheWindow gState], 0.0, 0.0, NX_COPY);
  288.     [self drawHand];
  289.     return self;
  290. }
  291.  
  292. @end