This is HenonView.m in view mode; [Download] [Up]
// // HenonView:View // // written by Anders Bertelrud // (c) 1990, 1991 by Anders Bertelrud // #import "HenonView.h" #import <dpsclient/dpsclient.h> #import <appkit/appkit.h> #import <objc/NXStringTable.h> #define MAXREAL 1e+10 //#define MAXINT 65535 @implementation HenonView ////////////////////// Creation & Destruction Methods ////////////////////// - initFrame:(const NXRect *)f { self = [super initFrame:f]; // Create, initialize, and configure a new NXImage (for use as a // mapping cache). henonCache = [[NXImage alloc] initSize:&f->size]; [henonCache useCacheWithDepth:NX_TwoBitGrayDepth]; useGrid = YES; continuousPlot = YES; isRunning = NO; timerTicking = NO; // Initialize the default plot values, and init the mapping cache. [self setPlotValues1:self]; [self initHenonCache]; return self; } - free { [self stopTimer]; [henonCache free]; [super free]; return nil; } //////////////////////////// Rendering Methods ///////////////////////////// - drawSelf:(const NXRect *)rects :(int)rectCount { // Since the mapping is cached, all we have to do is to composite // the cache into the panel. [henonCache composite:NX_COPY toPoint:&bounds.origin]; return self; } /////////////////////////////// File Methods /////////////////////////////// - saveAsTIFF:sender { id savePanel = [SavePanel new]; NXStream *tiffStream; // This method should probably allow the user to specify compression // type and (in the case of JPEG) quantization value. This could be // done using an accessory view. [savePanel setRequiredFileType:"tiff"]; if ([savePanel runModal]) { if ((tiffStream = NXOpenMemory(NULL, 0, NX_WRITEONLY)) == NULL) { NXRunAlertPanel([stringTable valueForStringKey:"FS error"], [stringTable valueForStringKey:"Couldn't write TIFF to"], [stringTable valueForStringKey:"OK"], NULL, NULL, [savePanel filename]); } else { [henonCache writeTIFF:tiffStream]; NXSaveToFile(tiffStream, [savePanel filename]); NXCloseMemory(tiffStream, NX_FREEBUFFER); } } return self; } ////////////////////////////// Action Methods ////////////////////////////// - startStop:sender { if (!isRunning) { // We aren't already plotting a map, so start now. L = [[lField cell] floatValue]; T = [[tField cell] floatValue]; R = [[rField cell] floatValue]; B = [[bField cell] floatValue]; // Now check values and stuff. It is really very bad design philo- // sophy to yell at the user using alert panels; this is just a cheap // demo, however, so we'll leave it at this for now. At least we're // using string tables to make internationalization easy. if (L >= R) { NXRunAlertPanel([stringTable valueForStringKey:"Range error"], [stringTable valueForStringKey:"Right must be > left"], [stringTable valueForStringKey:"OK"], NULL, NULL); return self; } if (B >= T) { NXRunAlertPanel([stringTable valueForStringKey:"Range error"], [stringTable valueForStringKey:"Top must be > bottom"], [stringTable valueForStringKey:"OK"], NULL, NULL); return self; } orbits = [[orbitField cell] intValue]; if (orbits < 1) { NXRunAlertPanel([stringTable valueForStringKey:"Range error"], [stringTable valueForStringKey:"# orbits must be int >= 1"], [stringTable valueForStringKey:"OK"], NULL, NULL); return self; } points = [[pointsField cell] intValue]; if (points < 1) { NXRunAlertPanel([stringTable valueForStringKey:"Range error"], [stringTable valueForStringKey:"# pts/orbit must be int >= 1"], [stringTable valueForStringKey:"OK"], NULL, NULL); return self; } dy0 = [[incYField cell] floatValue]; dx0 = [[incXField cell] floatValue]; y0 = [[startYField cell] floatValue]; x0 = [[startXField cell] floatValue]; phase = [[phaseField cell] floatValue]; if (phase < 0 || phase > 3.141593) { NXRunAlertPanel([stringTable valueForStringKey:"Range error"], [stringTable valueForStringKey:"Phase angle must be 0<=a<=pi"], [stringTable valueForStringKey:"OK"], NULL, NULL); return self; } // Calculate some things used by the caculation algorithm. cosa = cos(phase); sina = sin(phase); xOld = x0; yOld = y0; xScale = bounds.size.width / (R - L); yScale = bounds.size.height / (T - B); currentOrbit = 1; currentPoint = 1; isRunning = YES; // Start the plot. [[currentOrbitField cell] setIntValue:currentOrbit]; [self initHenonCache]; [self display]; // Start the timer. [self startTimer]; [nextOrbitButton setEnabled:YES]; [startStopButton setTitle:[stringTable valueForStringKey:"Stop"]]; } else { // We're plotting a map, so stop now that the button has been clicked. isRunning = NO; [self stopTimer]; [nextOrbitButton setEnabled:NO]; [[currentOrbitField cell] setStringValue:""]; [startStopButton setTitle:[stringTable valueForStringKey:"Plot"]]; } return self; } - gridChecked:sender { useGrid = [sender state]; return self; } - nextOrbit:sender { // This is a quick way to make sure that we'll go into the next orbit // the next time doHenonCalculation is called. currentPoint = points + 1; return self; } - setPlotValues1:sender { isRunning = YES; // Hack so that startStop: will do the right thing. [self startStop:self]; // Stop the current plot. phase = 1.111; [phaseField setFloatValue:phase]; [[phaseSlider cell] setFloatValue:phase]; L = -1.2; [lField setFloatValue:L]; T = 1.2; [tField setFloatValue:T]; R = 1.2; [rField setFloatValue:R]; B = -1.2; [bField setFloatValue:B]; x0 = 0.098; [startXField setFloatValue:x0]; y0 = 0.061; [startYField setFloatValue:y0]; dx0 = 0.04; [incXField setFloatValue:dx0]; dy0 = 0.03; [incYField setFloatValue:dy0]; orbits = 38; [orbitField setIntValue:orbits]; points = 700; [pointsField setIntValue:points]; return self; } - setPlotValues2:sender { isRunning = YES; // Hack so that startStop: will do the right thing. [self startStop:self]; // Stop the current plot. phase = 0.264; [phaseField setFloatValue:phase]; [[phaseSlider cell] setFloatValue:phase]; L = -1.2; [lField setFloatValue:L]; T = 1.2; [tField setFloatValue:T]; R = 1.2; [rField setFloatValue:R]; B = -1.2; [bField setFloatValue:B]; x0 = 0.098; [startXField setFloatValue:x0]; y0 = 0.061; [startYField setFloatValue:y0]; dx0 = 0.04; [incXField setFloatValue:dx0]; dy0 = 0.03; [incYField setFloatValue:dy0]; orbits = 25; [orbitField setIntValue:orbits]; points = 300; [pointsField setIntValue:points]; return self; } - setPlotValues3:sender { isRunning = YES; // Hack so that startStop: will do the right thing. [self startStop:self]; // Stop the current plot. phase = 1.5732; [phaseField setFloatValue:phase]; [[phaseSlider cell] setFloatValue:phase]; L = -3.0; [lField setFloatValue:L]; T = 3.0; [tField setFloatValue:T]; R = 3.0; [rField setFloatValue:R]; B = -3.0; [bField setFloatValue:B]; x0 = 0.098; [startXField setFloatValue:x0]; y0 = 0.061; [startYField setFloatValue:y0]; dx0 = 0.04; [incXField setFloatValue:dx0]; dy0 = 0.03; [incYField setFloatValue:dy0]; orbits = 70; [orbitField setIntValue:orbits]; points = 400; [pointsField setIntValue:points]; return self; } ////////////////////////////// Timer Methods /////////////////////////////// // The timed entry function. void timedEntryFunction (DPSTimedEntry entryID, double currentTime, id self) { [self doHenonCalculation]; } - startTimer { [self stopTimer]; timerTicking = YES; timer = DPSAddTimedEntry((double)0.0, (DPSTimedEntryProc)&timedEntryFunction, self, NX_BASETHRESHOLD); return self; } - stopTimer { if (timerTicking) { DPSRemoveTimedEntry(timer); timerTicking = NO; } return self; } ////////////////////////// Miscellaneous Methods /////////////////////////// - doHenonCalculation { float xNew, yNew; // If you want to modify the plot algorithm, this is the place to do it. // This method is called continuously (from the timed entry function), and // it calculates exactly one point in the HƯnon map. The location of the // point is based on the location of the previous point as well as on the // values given in the plot panel. // If, after calculating the current point, we have calculated all the // points in the orbit, we update the HƯnon window and start the next // orbit. if ([henonCache lockFocus]) { PSsetgray(NX_BLACK); if (currentPoint <= points) { if (abs(xOld) < MAXREAL && abs(yOld) < MAXREAL) { xNew = xOld*cosa - (yOld - xOld*xOld)*sina; yNew = xOld*sina + (yOld - xOld*xOld)*cosa; if (abs(xNew - L) < MAXINT/xScale && abs(T - yNew) < MAXINT/yScale) { float x, y; x = (xNew - L) * xScale; y = (T - yNew) * yScale; if (x >= bounds.origin.x && x <= bounds.origin.x + bounds.size.width && y >= bounds.origin.y && y <= bounds.origin.y + bounds.size.height) { PSmoveto(x, y); PSrlineto(0, 0); PSstroke(); } } xOld = xNew; yOld = yNew; } } else { if (currentOrbit < orbits) { // Calculate next starting point. xOld = x0 + currentOrbit * dx0; yOld = y0 + currentOrbit * dy0; // Reset the current point, and update the TextField in // the HƯnon panel to reflect this change. currentPoint = 1; [[currentOrbitField cell] setIntValue:++currentOrbit]; // Update the panel to show the just completed orbit. [self lockFocus]; [henonCache composite:NX_COPY toPoint:&bounds.origin]; [self unlockFocus]; [window flushWindow]; } else { // We're now finished with the plot, so clean things up. [self stopTimer]; isRunning = NO; [startStopButton setTitle:"Plot"]; [nextOrbitButton setEnabled:NO]; [[currentOrbitField cell] setStringValue:""]; } } // Set things up so that the next point will be calculated the next // time the timed entry calles doHenonCalculation. ++currentPoint; [henonCache unlockFocus]; } return self; } - initHenonCache { float bR, bT, bL, bB, bW, bH; NXPoint origin; [henonCache lockFocus]; // Figure out shorthands for some edge values. bL = bounds.origin.x; bT = bounds.origin.y + bounds.size.height; bR = bounds.origin.x + bounds.size.width; bB = bounds.origin.y; bW = bounds.size.width; bH = bounds.size.height; PSsetgray(NX_WHITE); NXRectFill(&bounds); origin.x = (0.0 - L) * bW / (R - L); origin.y = (0.0 - B) * bH / (T - B); if ((R - L) < R && (T - B) < T) { origin.x = bR - 10; origin.x = bB + 10; } // If the user wants a grid, draw one. if (useGrid) { PSmoveto(0, origin.y); PSlineto(bR, origin.y); PSmoveto(origin.x, 0); PSlineto(origin.x, bT); PSsetgray(NX_DKGRAY); PSsetlinewidth(0); PSstroke(); } [henonCache unlockFocus]; return self; } //////////////////////////// Delegation Methods //////////////////////////// - appDidInit:sender { [window makeKeyAndOrderFront:self]; return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.