163
|
1
|
|
2 #import <stdio.h>
|
|
3
|
|
4 #import "TreeController.h"
|
|
5 #import "NamedTree.h"
|
|
6 #import "TreeView.h"
|
|
7
|
|
8 @implementation TreeController
|
|
9
|
|
10 - appDidInit:(Application *)sender
|
|
11 {
|
|
12 BOOL haveOpenedDocument = NO; // whether we have opened a document
|
|
13
|
|
14 // // Gets the public port for SomeApp
|
|
15 // port_t thePort = NXPortFromName("Emacs", NULL);
|
|
16 //
|
|
17 // if (thePort != PORT_NULL)
|
|
18 // // Sets the Speaker to send its next message to SomeApp's port
|
|
19 // [[NXApp appSpeaker] setSendPort:thePort];
|
|
20
|
|
21 if (NXArgc > 1)
|
|
22 {
|
|
23 int i;
|
|
24
|
|
25 for (i = 1; i < NXArgc; i++)
|
|
26 {
|
|
27 haveOpenedDocument = [self openFile: NXArgv[i]] || haveOpenedDocument;
|
|
28 }
|
|
29 }
|
|
30
|
|
31 return self;
|
|
32 }
|
|
33
|
|
34 - init
|
|
35 {
|
|
36 [super init];
|
|
37 first = YES;
|
|
38 nextX = 200;
|
|
39 nextY = 600;
|
|
40 return self;
|
|
41 }
|
|
42
|
|
43 - info:sender // bring up the info panel, obviously
|
|
44 {
|
|
45 if(!infoPanel)
|
|
46 [NXApp loadNibSection:"InfoPanel.nib" owner:self withNames:NO];
|
|
47 return [infoPanel orderFront:sender];
|
|
48 }
|
|
49
|
|
50 - open:sender
|
|
51 { // use open panel to select a file -- only with .tree extension.
|
|
52 // only one file may be loaded at a time.
|
|
53 const char *const *files;
|
|
54 char *file;
|
|
55 const char *dir;
|
|
56 static const char *const ft[2] = {"tree", NULL};
|
|
57
|
|
58 id openPanel = [OpenPanel new];
|
|
59 [openPanel allowMultipleFiles:NO];
|
|
60 if (first) {
|
|
61 [openPanel runModalForDirectory:[[NXBundle mainBundle] directory]
|
|
62 file:NULL types:ft];
|
|
63 first = NO;
|
|
64 } else [openPanel runModalForTypes:ft];
|
|
65 files = [openPanel filenames];
|
|
66 dir = [openPanel directory];
|
|
67 file = malloc(strlen(files[0]) + strlen(dir) + 8);
|
|
68 strcpy(file, dir);
|
|
69 strcat(file,"/");
|
|
70 strcat(file, files[0]);
|
|
71 strcat(file, "\0");
|
|
72 [self openFile:file];
|
|
73 return self;
|
|
74 }
|
|
75
|
|
76 char nextChar; // This allows me to do single character lookahead in scan
|
|
77
|
|
78 id readToNewline(FILE *file) // used to parse a file; reads until a newline
|
|
79 { // returns a string object... reads until EOL to get string value.
|
|
80 id newString = [[String alloc] init];
|
|
81 char *buffer = (char *)malloc(1024); // should be plenty big
|
|
82 char *spot = buffer;
|
|
83
|
|
84 while (nextChar != '\n')
|
|
85 {
|
|
86 spot[0] = nextChar; spot++; nextChar = fgetc(file);
|
|
87 }
|
|
88 spot[0] = '\0'; // terminate the string
|
|
89 nextChar = fgetc(file); // Get next char for next invocation of this function
|
|
90 [newString setString:buffer];
|
|
91 free(buffer);
|
|
92 return newString;
|
|
93 }
|
|
94
|
|
95 char prevChar; // This allows me to do single character lookback in scan
|
|
96
|
|
97 id readToSep(FILE *file) // used to parse a file; reads until a newline or a "^^" sequence
|
|
98 { // returns a string object... reads until EOL to get string value.
|
|
99 id newString = [[String alloc] init];
|
|
100 char *buffer = (char *)malloc(1024); // should be plenty big
|
|
101 char *spot = buffer;
|
|
102 int c;
|
|
103
|
|
104 while (nextChar != '\n')
|
|
105 {
|
|
106 if (nextChar == '^')
|
|
107 if ((c = fgetc(file)) == '^')
|
|
108 break;
|
|
109 else
|
|
110 ungetc(c, file);
|
|
111 spot[0] = nextChar; spot++; nextChar = fgetc(file);
|
|
112 }
|
|
113 spot[0] = '\0'; // terminate the string
|
|
114 prevChar = nextChar;
|
|
115 nextChar = fgetc(file); // Get next char for next invocation of this function
|
|
116 [newString setString:buffer];
|
|
117 free(buffer);
|
|
118 return newString;
|
|
119 }
|
|
120
|
|
121 // This actually opens a file. WorkSpace and Open panel methods both
|
|
122 // eventually get to here to do the real work. This code is pretty much
|
|
123 // worth ignoring, unless you _really_ care about how I read in the
|
|
124 // files. It's mostly specific to the file format so it's not
|
|
125 // generally useful. The framework for this code came from the
|
|
126 // code in my "Viewer.app" that is in with some PD raytracers I ported
|
|
127 // to the NeXT--allows viewing an rgb bitmap file; I wrote it before
|
|
128 // GW and ImageViewer existed... (See raytracers.tar.Z on sonata/orst)
|
|
129 - (BOOL)openFile:(const char *)name
|
|
130 {
|
|
131 // id alert;
|
|
132 id aString, treeName, rootNode, workingNode, tempNode;
|
|
133 id newString, stack = [[List alloc] init];
|
|
134 int indLevel, numSpaces, indent = 0;
|
|
135 char *tempString;
|
|
136 BOOL rStat = YES;
|
|
137 FILE *file;
|
|
138 // for debugging:
|
|
139 //NXStream *out = NXOpenFile(fileno(stdout), NX_WRITEONLY);
|
|
140
|
|
141 // get a new doc window.
|
|
142 [NXApp loadNibSection:"DocWindow.nib" owner:self withNames:NO];
|
|
143 [[treeView window] setTitleAsFilename:name];
|
|
144 // put up an alert panel to let user know we're busy
|
|
145 // alert = NXGetAlertPanel(NULL, "Reading tree and creating image.",
|
|
146 // NULL, NULL, NULL);
|
|
147 // [alert makeKeyAndOrderFront:self];
|
|
148 // Read the tree file. NOTE THAT THIS DOES NOT DO ERROR CHECKING.
|
|
149 file = fopen(name, "r");
|
|
150 nextChar = fgetc(file); // prime the system
|
|
151 treeName = readToNewline(file); // first line is tree's name
|
|
152 aString = readToSep(file); // Get the name of the root node.
|
|
153 rootNode = [[[NamedTree alloc]
|
|
154 initLabelString:aString] setTreeName:treeName];
|
|
155 if (prevChar != '\n')
|
|
156 [rootNode setValue: readToSep(file)]; // Set the node's value.
|
|
157 [stack insertObject:rootNode at:0];
|
|
158 workingNode = rootNode;
|
|
159 // figure out the indentation
|
|
160 while (nextChar == ' ') {
|
|
161 indent++;
|
|
162 nextChar = fgetc(file);
|
|
163 }
|
|
164 aString = readToSep(file); // get name of child node
|
|
165 tempNode = [[[NamedTree alloc]
|
|
166 initLabelString:aString] setTreeName:treeName];
|
|
167 if (prevChar != '\n')
|
|
168 [tempNode setValue: readToSep(file)]; // Set the node's value.
|
|
169 [workingNode addBranch:tempNode];
|
|
170 [stack insertObject:tempNode at:0];
|
|
171 workingNode = tempNode;
|
|
172 // now that we know the file's char's, we read in the other nodes
|
|
173 // I use a List object as if it were a stack and push parent nodes on
|
|
174 // it while working on children rather than doing a recursive function.
|
|
175 // the comments are sparse, just know that it's mostly pushing and
|
|
176 // popping from the stack to get at the right parent to add a child to.
|
|
177 while (!feof(file)) {
|
|
178 aString = readToSep(file); // next node name + indentation.
|
|
179 // find out # of indentation spaces and strip them off
|
|
180 // *** This gives a warning: ignore it, it's unimportant here.
|
|
181 tempString = [aString stringValue]; numSpaces = 0;
|
|
182 while (tempString[0] == ' ') {
|
|
183 numSpaces++; tempString++;
|
|
184 }
|
|
185 indLevel = numSpaces / indent;
|
|
186 if (indLevel == ([stack count] - 1)) // same level as last object
|
|
187 {
|
|
188 [stack removeObjectAt:0];
|
|
189 workingNode = [stack objectAt:0];
|
|
190 newString = [[String alloc] initString:tempString];
|
|
191 [aString free];
|
|
192 tempNode = [[[NamedTree alloc]
|
|
193 initLabelString:newString] setTreeName:treeName];
|
|
194 if (prevChar != '\n')
|
|
195 [tempNode setValue: readToSep(file)]; // Set the node's value.
|
|
196 [workingNode addBranch:tempNode];
|
|
197 [stack insertObject:tempNode at:0];
|
|
198 workingNode = tempNode;
|
|
199 } else if (indLevel == ([stack count])) { // child of last node
|
|
200 newString = [[String alloc] initString:tempString];
|
|
201 [aString free];
|
|
202 tempNode = [[[NamedTree alloc]
|
|
203 initLabelString:newString] setTreeName:treeName];
|
|
204 if (prevChar != '\n')
|
|
205 [tempNode setValue: readToSep(file)]; // Set the node's value.
|
|
206 [workingNode addBranch:tempNode];
|
|
207 [stack insertObject:tempNode at:0];
|
|
208 workingNode = tempNode;
|
|
209 } else if (indLevel < [stack count]) { // higher level, so pop
|
|
210 while (indLevel < [stack count]) { // pop until at right level
|
|
211 [stack removeObjectAt:0];
|
|
212 workingNode = [stack objectAt:0];
|
|
213 } // now add new node since we're at the level
|
|
214 newString = [[String alloc] initString:tempString];
|
|
215 [aString free];
|
|
216 tempNode = [[[NamedTree alloc]
|
|
217 initLabelString:newString] setTreeName:treeName];
|
|
218 if (prevChar != '\n')
|
|
219 [tempNode setValue: readToSep(file)]; // Set the node's value.
|
|
220 [workingNode addBranch:tempNode];
|
|
221 [stack insertObject:tempNode at:0];
|
|
222 workingNode = tempNode;
|
|
223 } else { // typically, if user goes in two levels at once, which
|
|
224 // doesn't make any sense semantically
|
|
225 fprintf(stderr, "Error: level too deep!\n");
|
|
226 rStat = NO;
|
|
227 }
|
|
228 }
|
|
229 // Debugging code to pretty print the parsed tree. If this output
|
|
230 // is correct, then we know that the parse was OK.
|
|
231 //printf("\nHere's the parsed tree:\n");
|
|
232 //printf("Tree name: \"%s\".", [treeName stringValue]);
|
|
233 //[rootNode dumpTree:out level:0 indent:" "];
|
|
234 //printf("\n\n");
|
|
235 //NXClose(out);
|
|
236 // end debug code
|
|
237 // rStat = return status of tree reader YES = success
|
|
238 // Now attach the Tree to the TreeView...
|
|
239 [treeView attachTree:rootNode];
|
|
240 // and now bring up the window for the user
|
|
241 [[treeView window] moveTo:nextX :(nextY-218)];
|
|
242 // Get rid of the alert
|
|
243 // [alert orderOut:self];
|
|
244 // [alert free];
|
|
245 nextX += 22; nextY -= 25;
|
|
246 if (nextX > 370)
|
|
247 {
|
|
248 nextX = 200; nextY = 600;
|
|
249 }
|
|
250 [[treeView window] makeKeyAndOrderFront:self];
|
|
251 return rStat;
|
|
252 }
|
|
253
|
|
254
|
|
255 // The next two methods allow the WorkSpace to open a .tree file when
|
|
256 // it is double-clicked. (Or any file that's cmd-dragged over our icon.)
|
|
257
|
|
258 - (BOOL)appAcceptsAnotherFile:sender { return YES; }
|
|
259 - (int)app:sender openFile:(const char *)file type:(const char *)type
|
|
260 {
|
|
261 return [self openFile:file];
|
|
262 }
|
|
263
|
|
264
|
|
265 @end
|