This is GnuplotPlot.m in view mode; [Download] [Up]
/*
* Copyright (C) 1993 Robert Davis
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of Version 2, or any later version, of
* the GNU General Public License as published by the Free Software
* Foundation.
*/
static char RCSId[]="$Id: GnuplotPlot.m,v 1.15 1993/05/29 00:10:27 davis Exp $";
#import <appkit/Application.h>
#import <appkit/FontManager.h>
#import <appkit/Pasteboard.h>
#import <appkit/SavePanel.h>
#import <appkit/Window.h>
#import <appkit/publicWraps.h> /* NXBeep() */
#import <objc/List.h>
#import <objc/NXStringTable.h>
#import <sys/stat.h> /* stat() */
#import <sys/types.h> /* stat() */
#import <streams/streams.h>
#import <ctype.h> /* isspace() */
#import <libc.h> /* MAXPATHLEN */
#import <strings.h> /* strlen(), strcpy() */
#import <stdlib.h>
#import "FunctionObject.h"
#import "GnuplotPlot.h"
#import "Gnuplot.h"
#import "PlotView.h"
#import "PopUpScrollView.h"
#import "Status.h"
@interface GnuplotPlot (Private)
- _saveWithNewName:(BOOL)doNewName retainNewName:(BOOL)doRetain;
- _write:(const char *)filename;
- _read:(const char *) filename;
@end
#define DEFAULT_WIN_WIDTH 380
#define DEFAULT_WIN_HEIGHT 269
extern int scanner (char expression[]);
extern int almost_equals (int t_num, char *str);
extern char input_line[];
extern int nextfe_color; /* Is this a color machine? */
/*** Preferences -- Pseudo Class Variables ***/
static int constantUpdate = UPDATE_NOT3D; /* Replot after any change? */
/* Default window size */
static NXSize winSize = { DEFAULT_WIN_WIDTH, DEFAULT_WIN_HEIGHT };
/*
* This function computes a new location for each new window created.
* p is the origin of the windows frame, w is the size of the window.
*/
static void _newLocation (NXPoint *p, NXSize w)
{
static count = 0; /* This tracks all our instances */
p->x += (21.0 * count);
p->y -= (24.0 * count) + (w.height - DEFAULT_WIN_HEIGHT);
count = (count > 10)? 0 : count+1;
}
static char *endOfCommand (char *aString)
{
char *cur;
char c;
for (cur = aString ; cur ; cur++)
switch (*cur) {
case '\\':
if (*(cur+1) && (*(cur+1) == '\n'))
cur++;
break;
case '\n':
case '\0':
return cur;
case ';':
return cur;
case '\'':
case '"': /* Skip quote */
/* Note that gnuplot does not allow nested quotes. */
c = *(cur++);
while (*cur && (*cur != c) && (*cur != '\n'))
cur++;
if (*cur == '\n')
return cur;
break;
case '#':
while (cur++ && *cur)
if ((*cur == '\n') && (*(cur-1) != '\\'))
return cur;
break;
}
return NULL;
}
static char *commandcpy (char *dest, char *source)
{
char *cursource, *curdest, *end;
end = endOfCommand (source);
curdest = dest;
for (cursource = source ; cursource && (cursource != end); cursource++)
if (*cursource == '\\' && (cursource+1 != end)
&& (*(cursource+1) == '\n'))
cursource++;
else
*(curdest++) = *cursource;
*(curdest++) = '\n';
*curdest = '\0';
return end;
}
/*
* stripCommands Removes the 'set output "output.eps"\n', 'set
* terminal ...\n', and 'pause ...' lines and
* comments from the string.
*/
static char *stripCommands (char **aString)
{
if (aString && *aString) {
char *cur, *end, *newText;
BOOL plotAlready = NO;
cur = *aString;
newText = (char *) malloc (strlen (*aString));
*newText = '\0';
while (cur && *cur) {
while (isspace(*cur))
cur++;
end = commandcpy (input_line, cur);
scanner (input_line);
if (plotAlready)
break;
else if (!( (almost_equals (0, "se$t") &&
(almost_equals (1, "o$utput") ||
almost_equals (1, "t$erminal"))) ||
almost_equals (0, "pa$use") )) {
strcat (newText, input_line);
}
plotAlready = plotAlready || almost_equals (0, "p$lot")
|| almost_equals (0, "sp$lot");
cur = (end ? end + 1 : NULL);
}
strcpy (*aString, newText);
return *aString;
}
return NULL;
}
@implementation GnuplotPlot
+ initialize
{
/*
* This is probably temporary -- until color is handled better,
* this just checks to see if we're on a color machine and sets
* the color variable of the terminal accordingly.
*/
nextfe_color = (([NXApp colorScreen]->depth) == NX_TwoBitGrayDepth)? 0 : 1;
return self;
}
+ setConstantUpdate:(int)updateType
{
constantUpdate = updateType;
return self;
}
+ setHalvePlot:(BOOL)condition
{
if (condition) {
winSize.width = 380;
winSize.height = 269;
} else {
winSize.width = 740;
winSize.height = 521;
}
return [Status setHalvePlot:condition];
}
- init
{
return [self initFromFile: NULL];
}
- initFromFile:(const char *) filename
{
NXRect theFrame;
const char *validSendTypes[2];
[super init];
controller = [NXApp delegate];
zone = [self zone];
[NXApp loadNibSection: "GnuplotPlot.nib"
owner: self
withNames: NO
fromZone: zone];
status = [[Status allocFromZone:zone] init];
[status setDelegate: self];
[plotScrollView setVertScrollerRequired: YES];
[plotScrollView setHorizScrollerRequired: YES];
[plotScrollView setBorderType:NX_BEZEL];
plotView = [[PlotView allocFromZone:zone] initFrame:NULL];
[[plotScrollView setDocView:plotView] free];
stringSet = [controller stringSet];
fullPath = NULL;
/* Setup services */
validSendTypes[0] = NXFilenamePboardType;
validSendTypes[1] = NULL;
[NXApp registerServicesMenuSendTypes: validSendTypes andReturnTypes: NULL];
[window sizeWindow:winSize.width :winSize.height]; /* Default size */
[window getFrame:&theFrame]; /* Position the window */
_newLocation (&theFrame.origin, winSize);
[window moveTo:NX_X(&theFrame) :NX_Y(&theFrame)];
[window setMiniwindowIcon: "GnuplotDoc.tiff"];
if (filename) {
[self setName:filename];
isTitled = YES;
[window makeKeyAndOrderFront:self];
if (![self plotFromFile:filename]) {
[NXApp delayedFree:self];
return nil;
}
} else {
[window makeKeyAndOrderFront:self];
[self setName: NULL];
isTitled = NO;
}
[self setDocEdited: NO];
return self;
}
- free
{
NXZoneFree (zone, fullPath);
NXZoneFree (zone, readText);
[status free];
[window free];
return [super free];
}
/*** Target/Action ***/
- save:sender
{
return [self _saveWithNewName:NO retainNewName:YES];
}
- saveAs:sender
{
return [self _saveWithNewName:YES retainNewName:YES];
}
- saveTo:sender
{
return [self _saveWithNewName:YES retainNewName:NO];
}
- revertToSaved:sender
{
switch (NXRunAlertPanel ([stringSet valueForStringKey: "revertT"],
[stringSet valueForStringKey: "revert"],
[stringSet valueForStringKey: "revertB"],
[stringSet valueForStringKey: "cancelB"],
NULL, fullPath)) {
case NX_ALERTDEFAULT:
[status resetCurrent];
[self plotFromFile:fullPath];
[self setDocEdited:NO];
/*
* Make sure any panels that are showing info about this
* document know that we just changed.
*/
[controller updateApp];
[[FontManager new] setSelFont:[status font] isMultiple:NO];
return self;
break;
case NX_ALERTOTHER:
return nil;
break;
}
return self;
}
- close:sender
{
[window performClose:self];
return self;
}
- print:sender;
{
return [plotView printPSCode:sender];
}
- setName: (const char *) name
{
char title[255];
char *oldFullPath = fullPath;
if (name) {
fullPath = NXCopyStringBufferFromZone (name, zone);
[window setTitleAsFilename:fullPath];
isTitled = YES;
} else {
sprintf (title, "%s%d", [stringSet valueForStringKey: "untitled"],
[controller numberNew]);
fullPath = NXCopyStringBufferFromZone (title, zone);
[window setTitleAsFilename:title];
isTitled = NO;
}
NXZoneFree (zone, oldFullPath);
return self;
}
- (const char *) name
{
return fullPath;
}
- setCurrentFont:aFont
{
if (aFont) {
[status setFont:aFont];
}
return self;
}
- currentFont
{
return [status font];
}
- window
{
return window;
}
- (Status *)status
{
return status;
}
- plotFromFile:(const char *)aFullPath
{
id returnVal = self;
if (![self _read:aFullPath]) /* _read into readText */
return nil;
stripCommands (&readText); /* Ignore output, terminal, and */
/* multiple plot commands */
[status setAppendix: readText];
/*
* We don't check the return status of plot: because even if
* there are errors, we want the file to remain open -- maybe the
* user can correct the errors without having to use vi.
*/
[self plot:self];
[status grabCurrent];
[status setAppendix: NULL];
NXZoneFree (zone, readText);
readText = NULL;
return returnVal;
}
- plot:sender
{
id returnVal = self;
/*
* We don't return nil if the settings don't allow us to create a
* valid plot -- we simply don't plot.
*/
if (![status canPlot]) {
[plotView newStream:NULL];
} else {
char *newTitle;
char *oldTitle = NXCopyStringBufferFromZone ([window title], zone);
newTitle = NXZoneMalloc (zone, strlen (oldTitle) + 12);
sprintf (newTitle, [stringSet valueForStringKey:"plotting..."],
oldTitle);
[window setTitle:newTitle];
NXPing();
NXZoneFree (zone, oldTitle);
NXZoneFree (zone, newTitle);
/*
* We do, however, report an error if the settings do allow
* us to plot but we were unable to (e.g. if gnuplot bailed
* out).
*/
if (![status plot]) {
[self reportScriptError:self];
returnVal = nil;
} else {
[plotView newStream:[status stream]];
/*
* Make sure the scale of the doc matches the current
* popUp setting
*/
[plotScrollView update]; /* Not done at every window update */
[window update];
}
[window setTitleAsFilename:fullPath];
NXPing();
}
return returnVal;
}
- reportScriptError: sender;
{
NXRunAlertPanel ([stringSet valueForStringKey:"scriptErrorT"],
[stringSet valueForStringKey:"scriptError"],
NULL, NULL, NULL, [self name]);
[plotView newStream: NULL];
[window display];
return self;
}
- addDataFile:(const char *)aPath
{
FunctionObject *functionObject;
if (![FunctionObject isAcceptableDataFile:aPath])
return nil;
functionObject = [[FunctionObject allocFromZone: [status zone]]
initFromString: aPath
isThreeD: [status isThreeD]];
// [functionObject setDataFile:YES];
[[status functions] addObject:functionObject];
[status reportSettingsChange:self];
return self;
}
- setDocEdited:(BOOL) state
{
if (((constantUpdate == UPDATE_ALWAYS) ||
((constantUpdate == UPDATE_NOT3D) && ![status isThreeD])) && state)
[self plot:self];
[window setDocEdited:state];
return self;
}
- (BOOL) isDocEdited
{
return [window isDocEdited];
}
- hideDocument:sender
{
[window orderOut:sender];
return self;
}
/*** Services ***/
- validRequestorForSendType:(NXAtom)sendType andReturnType:(NXAtom)returnType
/*
* Services menu support.
* We are a valid requestor if the send type is filename and there is
* no return data from the request.
*/
{
return (isTitled && (sendType == NXFilenamePboardType) &&
(!returnType || !*returnType)) ? self : nil;
}
- (BOOL)writeSelectionToPasteboard:pboard types:(NXAtom *)types
/*
* Services menu support.
* Here we are asked by the Services menu mechanism to supply the
* filename (which we said we were a valid requestor for in the above
* method).
*/
{
int save;
if (isTitled) {
while (types && *types)
if (*types == NXFilenamePboardType)
break;
else
types++;
if (types && *types) {
if ([self isDocEdited]) {
save = NXRunAlertPanel (
[stringSet valueForStringKey:"servicesT"],
[stringSet valueForStringKey:"serviceSave"],
[stringSet valueForStringKey:"save"],
[stringSet valueForStringKey:"dont save"], NULL);
if (save == NX_ALERTDEFAULT)
[self save:self];
}
[pboard declareTypes:&NXFilenamePboardType num:1 owner:self];
[pboard writeType:NXFilenamePboardType
data:fullPath length:strlen (fullPath)+1];
return YES;
}
}
return NO;
}
/*** Autoupdate Methods ***/
- (BOOL)validateCommand:menuCell
{
SEL action = [menuCell action];
BOOL edited = [self isDocEdited];
BOOL canPlot = [status canPlot];
/*
* Note that we only allow a document to be saved when the
* settings validly produce a plot. Also, if we need to see if
* the plot has a file with which it is associated.
*/
if (action == @selector(save:))
return (edited || !isTitled) && canPlot;
else if (action == @selector(saveAs:))
return canPlot;
else if (action == @selector(saveTo:))
return canPlot;
else if (action == @selector(saveAll:)) /* See Gnuplot class */
return (edited || !isTitled) && canPlot;
else if (action == @selector(revertToSaved:))
return (edited && isTitled);
else if (action == @selector(print:))
return canPlot;
else if (action == @selector(plot:))
return canPlot;
return YES;
}
/*** Window Delegate Methods ***/
- windowDidBecomeMain:sender
{
/* Update font panel */
if (plotView) {
[window makeFirstResponder: plotView];
// [[FontManager new] setSelFont:[status font] isMultiple:NO];
}
[controller setCurrentDoc:self];
return self;
}
- windowDidMiniaturize:sender
{
[controller setCurrentDoc:nil];
return self;
}
- windowDidResignMain:sender
{
// [[FontManager new] setEnabled:NO];
return self;
}
- windowDidResize:sender
{
int aTag;
/*
* Make sure the scale of the doc matches the current popUp
* setting. (If it's set to "Always Fit," which is 0, then resizing
* window should cause it to rescale.)
*/
aTag = [plotScrollView currentTag];
if (!aTag)
[plotScrollView setCurrentTag: aTag];
return self;
}
- windowDidUpdate:sender
{
[[FontManager new] setEnabled:([status canPlot] && [window isMainWindow])];
return self;
}
- windowWillClose:sender
{
id returnVal = self;
BOOL done = NO;
if ([self isDocEdited] && [status canPlot]) {
while (!done) { /* Keep asking until user tells what to do */
switch (NXRunAlertPanel([stringSet valueForStringKey:"willCloseT"],
[stringSet valueForStringKey:"willClose"],
[stringSet valueForStringKey:"save"],
[stringSet valueForStringKey:"dont save"],
[stringSet valueForStringKey:"cancelB"],
[self name])) {
case NX_ALERTDEFAULT: /* Save */
if (returnVal = [self save:self])
done = YES;
break;
case NX_ALERTALTERNATE:
returnVal = self; /* Don't Save */
done = YES;
break;
case NX_ALERTOTHER: /* Cancel */
returnVal = nil;
done = YES;
break;
}
}
}
if (returnVal) {
plotScrollView = nil; /* Don't want stale instance vars */
plotView = nil;
[window orderOut:self];
[controller docDidClose:self];
[NXApp delayedFree: self];
}
return returnVal;
}
- windowWillResize:sender toSize:(NXSize *) frameSize
{
NXRect rect;
float x, y, w, h;
[plotView getFrame:&rect];
/*
* If the frame rectangle of the view has both a length and a
* width, i.e. if there's something to see, we control the sizing
* so it can never be bigger than the rectangle (except to allow
* room for border and sliders).
*/
if ((w = NX_WIDTH(&rect)) && (h = NX_HEIGHT(&rect))) {
x = w + 22; /* Make room for borders & sliders */
y = h + 49;
/* If the current setting isn't "Always Fit" */
if ([plotScrollView currentTag]) {
if (frameSize->width > x) frameSize->width = x;
if (frameSize->height > y) frameSize->height = y;
}
}
/*
* Don't allow the window to get small enough to lose the scroll
* buttons and the pop-up list.
*/
if (frameSize->width < 147) frameSize->width = 147;
if (frameSize->height < 65) frameSize->height = 65;
return self;
}
/*** Status Delegate Methods ***/
- settingsDidChange:sender
{
[self setDocEdited: YES];
return self;
}
// Shuts up the compiler about unused RCSId
- (const char *) rcsid
{
return RCSId;
}
@end
@implementation GnuplotPlot (Private)
/*
* This method is based on a similar method from the NeXTSTEP
* Developer Example WhatsUpDoc. All varieties of save go through
* this routine. It covers all the cases of running the Save Panel
* and retaining the name chosen.
*/
- _saveWithNewName:(BOOL)doNewName retainNewName:(BOOL)doRetain
{
const char *saveName; /* filename to save into */
id savePanel = [SavePanel new];
if (doNewName || !isTitled) { /* saveAs or saveTo */
if ([savePanel runModalForDirectory:fullPath
file: (isTitled ? rindex (fullPath, '/') + 1
: [self name])]) {
saveName = [savePanel filename];
} else
return nil; /* cancelled from SavePanel */
} else { /* ordinary Save */
/*
* In order to save a document, it must be able to produce a
* valid plot and it must either be edited or be untitled.
*/
if ((![self isDocEdited] && isTitled) || ![status canPlot])
return nil;
saveName = fullPath;
}
switch ([controller saveType]) {
case SAVE_GNUPLOT:
if (![self _write:saveName]) {
NXRunAlertPanel ([stringSet valueForStringKey: "Save"],
[stringSet valueForStringKey: "cantSave"],
[stringSet valueForStringKey: "OKB"],
NULL, NULL, saveName);
return nil;
}
/* Update the document name */
if (doRetain) { /* if requested */
[self setName:saveName];
[self setDocEdited:NO];
}
break;
case SAVE_EPS:
if (![plotView saveEPSToFile:[savePanel filename]]) {
NXRunAlertPanel ([stringSet valueForStringKey: "Save"],
[stringSet valueForStringKey: "cantSave"],
[stringSet valueForStringKey: "OKB"],
NULL, NULL, saveName);
return nil;
}
break;
case SAVE_TIFF:
if (![plotView saveTIFFToFile:[savePanel filename]]) {
NXRunAlertPanel ([stringSet valueForStringKey: "Save"],
[stringSet valueForStringKey: "cantSave"],
[stringSet valueForStringKey: "OKB"],
NULL, NULL, saveName);
return nil;
}
break;
}
return self;
}
/*
* This method does all the writing so any application-specific stuff
* should go here.
*/
- _write:(const char *)filename
{
NXModalSession *session;
id modalPanel = NXGetAlertPanel ("Save", "Replotting %s.",
NULL,NULL,NULL, [self name]);
if ([Status lastplot] != status) {
session = [NXApp beginModalSession:NULL for:modalPanel];
[self plot:self];
[NXApp endModalSession:session];
NXFreeAlertPanel(modalPanel);
}
return [status saveToFile:filename];
}
- _read:(const char *) filename
{
NXStream *stream;
struct stat fileinfo;
if (stat (filename, &fileinfo)) {
NXRunAlertPanel ([stringSet valueForStringKey: "openT"],
[stringSet valueForStringKey: "cantOpen"],
[stringSet valueForStringKey: "OKB"],
NULL, NULL, filename);
return nil;
}
if (stream = NXMapFile (filename, NX_READONLY)) {
if (readText)
NXZoneFree (zone, readText);
readText = NXZoneMalloc (zone, fileinfo.st_size + 1);
NXRead (stream, readText, fileinfo.st_size);
readText[fileinfo.st_size] = '\0';
NXClose (stream);
/* Check to see if the file we read is writable -- foreshadowing */
if (!(fileinfo.st_mode & S_IWRITE))
NXRunAlertPanel ([stringSet valueForStringKey: "warningT"],
[stringSet valueForStringKey: "unwritable"],
[stringSet valueForStringKey: "OKB"],
NULL, NULL, filename);
} else {
NXRunAlertPanel ([stringSet valueForStringKey: "openT"],
[stringSet valueForStringKey: "cantOpen"],
[stringSet valueForStringKey: "OKB"],
NULL, NULL, filename);
return nil;
}
return self;
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.