Initial checkin of a native Mac OS X port, sharing most of its code
[u/mdw/putty] / macosx / osxdlg.m
1 /*
2 * osxdlg.m: various PuTTY dialog boxes for OS X.
3 */
4
5 #import <Cocoa/Cocoa.h>
6 #include "putty.h"
7 #include "storage.h"
8 #include "dialog.h"
9 #include "osxclass.h"
10
11 /*
12 * The `ConfigWindow' class is used to start up a new PuTTY
13 * session.
14 */
15
16 @class ConfigTree;
17 @interface ConfigTree : NSObject
18 {
19 NSString **paths;
20 int *levels;
21 int nitems, itemsize;
22 }
23 - (void)addPath:(char *)path;
24 @end
25
26 @implementation ConfigTree
27 - (id)init
28 {
29 self = [super init];
30 paths = NULL;
31 levels = NULL;
32 nitems = itemsize = 0;
33 return self;
34 }
35 - (void)addPath:(char *)path
36 {
37 if (nitems >= itemsize) {
38 itemsize += 32;
39 paths = sresize(paths, itemsize, NSString *);
40 levels = sresize(levels, itemsize, int);
41 }
42 paths[nitems] = [[NSString stringWithCString:path] retain];
43 levels[nitems] = ctrl_path_elements(path) - 1;
44 nitems++;
45 }
46 - (void)dealloc
47 {
48 int i;
49
50 for (i = 0; i < nitems; i++)
51 [paths[i] release];
52
53 sfree(paths);
54 sfree(levels);
55
56 [super dealloc];
57 }
58 - (id)iterateChildren:(int)index ofItem:(id)item count:(int *)count
59 {
60 int i, plevel;
61
62 if (item) {
63 for (i = 0; i < nitems; i++)
64 if (paths[i] == item)
65 break;
66 assert(i < nitems);
67 plevel = levels[i];
68 i++;
69 } else {
70 i = 0;
71 plevel = -1;
72 }
73
74 if (count)
75 *count = 0;
76
77 while (index > 0) {
78 if (i >= nitems || levels[i] != plevel+1)
79 return nil;
80 if (count)
81 (*count)++;
82 do {
83 i++;
84 } while (i < nitems && levels[i] > plevel+1);
85 index--;
86 }
87
88 return paths[i];
89 }
90 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
91 {
92 return [self iterateChildren:index ofItem:item count:NULL];
93 }
94 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
95 {
96 int count = 0;
97 /* pass nitems+1 to ensure we run off the end */
98 [self iterateChildren:nitems+1 ofItem:item count:&count];
99 return count;
100 }
101 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
102 {
103 return [self outlineView:outlineView numberOfChildrenOfItem:item] > 0;
104 }
105 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
106 {
107 /*
108 * Trim off all path elements except the last one.
109 */
110 NSArray *components = [item componentsSeparatedByString:@"/"];
111 return [components objectAtIndex:[components count]-1];
112 }
113 @end
114
115 @implementation ConfigWindow
116 - (id)initWithConfig:(Config)aCfg
117 {
118 NSScrollView *scrollview;
119 NSTableColumn *col;
120 ConfigTree *treedata;
121 int by = 0, mby = 0;
122 int wmin = 0;
123 int hmin = 0;
124 int panelht = 0;
125
126 get_sesslist(&sl, TRUE);
127
128 ctrlbox = ctrl_new_box();
129 setup_config_box(ctrlbox, &sl, FALSE /*midsession*/, aCfg.protocol,
130 0 /* protcfginfo */);
131 unix_setup_config_box(ctrlbox, FALSE /*midsession*/);
132
133 cfg = aCfg; /* structure copy */
134
135 self = [super initWithContentRect:NSMakeRect(0,0,300,300)
136 styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask |
137 NSClosableWindowMask)
138 backing:NSBackingStoreBuffered
139 defer:YES];
140 [self setTitle:@"PuTTY Configuration"];
141
142 [self setIgnoresMouseEvents:NO];
143
144 dv = fe_dlg_init(&cfg, self, self, @selector(configBoxFinished:));
145
146 scrollview = [[NSScrollView alloc] initWithFrame:NSMakeRect(20,20,10,10)];
147 treeview = [[NSOutlineView alloc] initWithFrame:[scrollview frame]];
148 [scrollview setBorderType:NSLineBorder];
149 [scrollview setDocumentView:treeview];
150 [[self contentView] addSubview:scrollview];
151 [scrollview setHasVerticalScroller:YES];
152 [scrollview setAutohidesScrollers:YES];
153 /* FIXME: the below is untested. Test it then remove this notice. */
154 [treeview setAllowsColumnReordering:NO];
155 [treeview setAllowsColumnResizing:NO];
156 [treeview setAllowsMultipleSelection:NO];
157 [treeview setAllowsEmptySelection:NO];
158 [treeview setAllowsColumnSelection:YES];
159
160 treedata = [[[ConfigTree alloc] init] retain];
161
162 col = [[NSTableColumn alloc] initWithIdentifier:nil];
163 [treeview addTableColumn:col];
164 [treeview setOutlineTableColumn:col];
165
166 [[treeview headerView] setFrame:NSMakeRect(0,0,0,0)];
167
168 /*
169 * Create the controls.
170 */
171 {
172 int i;
173 char *path = NULL;
174
175 for (i = 0; i < ctrlbox->nctrlsets; i++) {
176 struct controlset *s = ctrlbox->ctrlsets[i];
177 int mw, mh;
178
179 if (!*s->pathname) {
180
181 create_ctrls(dv, [self contentView], s, &mw, &mh);
182
183 by += 20 + mh;
184
185 if (wmin < mw + 40)
186 wmin = mw + 40;
187 } else {
188 int j = path ? ctrl_path_compare(s->pathname, path) : 0;
189
190 if (j != INT_MAX) { /* add to treeview, start new panel */
191 char *c;
192
193 /*
194 * We expect never to find an implicit path
195 * component. For example, we expect never to
196 * see A/B/C followed by A/D/E, because that
197 * would _implicitly_ create A/D. All our path
198 * prefixes are expected to contain actual
199 * controls and be selectable in the treeview;
200 * so we would expect to see A/D _explicitly_
201 * before encountering A/D/E.
202 */
203 assert(j == ctrl_path_elements(s->pathname) - 1);
204
205 c = strrchr(s->pathname, '/');
206 if (!c)
207 c = s->pathname;
208 else
209 c++;
210
211 [treedata addPath:s->pathname];
212 path = s->pathname;
213
214 panelht = 0;
215 }
216
217 create_ctrls(dv, [self contentView], s, &mw, &mh);
218 if (wmin < mw + 3*20+150)
219 wmin = mw + 3*20+150;
220 panelht += mh + 20;
221 if (hmin < panelht - 20)
222 hmin = panelht - 20;
223 }
224 }
225 }
226
227 {
228 int i;
229 NSRect r;
230
231 [treeview setDataSource:treedata];
232 for (i = [treeview numberOfRows]; i-- ;)
233 [treeview expandItem:[treeview itemAtRow:i] expandChildren:YES];
234
235 [treeview sizeToFit];
236 r = [treeview frame];
237 if (hmin < r.size.height)
238 hmin = r.size.height;
239 }
240
241 [self setContentSize:NSMakeSize(wmin, hmin+60+by)];
242 [scrollview setFrame:NSMakeRect(20, 40+by, 150, hmin)];
243 [treeview setDelegate:self];
244 mby = by;
245
246 /*
247 * Now place the controls.
248 */
249 {
250 int i;
251 char *path = NULL;
252 panelht = 0;
253
254 for (i = 0; i < ctrlbox->nctrlsets; i++) {
255 struct controlset *s = ctrlbox->ctrlsets[i];
256
257 if (!*s->pathname) {
258 by -= VSPACING + place_ctrls(dv, s, 20, by, wmin-40);
259 } else {
260 if (!path || strcmp(s->pathname, path))
261 panelht = 0;
262
263 panelht += VSPACING + place_ctrls(dv, s, 2*20+150,
264 40+mby+hmin-panelht,
265 wmin - (3*20+150));
266
267 path = s->pathname;
268 }
269 }
270 }
271
272 select_panel(dv, ctrlbox, [[treeview itemAtRow:0] cString]);
273
274 [treeview reloadData];
275
276 dlg_refresh(NULL, dv);
277
278 [self center]; /* :-) */
279
280 return self;
281 }
282 - (void)configBoxFinished:(id)object
283 {
284 int ret = [object intValue]; /* it'll be an NSNumber */
285 if (ret) {
286 [controller performSelectorOnMainThread:
287 @selector(newSessionWithConfig:)
288 withObject:[NSData dataWithBytes:&cfg length:sizeof(cfg)]
289 waitUntilDone:NO];
290 }
291 [self close];
292 }
293 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
294 {
295 const char *path = [[treeview itemAtRow:[treeview selectedRow]] cString];
296 select_panel(dv, ctrlbox, path);
297 }
298 - (BOOL)outlineView:(NSOutlineView *)outlineView
299 shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
300 {
301 return NO; /* no editing! */
302 }
303 @end
304
305 /* ----------------------------------------------------------------------
306 * Various special-purpose dialog boxes.
307 */
308
309 int askappend(void *frontend, Filename filename)
310 {
311 return 0; /* FIXME */
312 }
313
314 void askalg(void *frontend, const char *algtype, const char *algname)
315 {
316 fatalbox("Cipher algorithm dialog box not supported yet");
317 return; /* FIXME */
318 }
319
320 void verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
321 char *keystr, char *fingerprint)
322 {
323 int ret;
324
325 /*
326 * Verify the key.
327 */
328 ret = verify_host_key(host, port, keytype, keystr);
329
330 if (ret == 0)
331 return;
332
333 /*
334 * FIXME FIXME FIXME. I currently lack any sensible means of
335 * asking the user for a verification non-application-modally,
336 * _or_ any means of closing just this connection if the answer
337 * is no (the Unix and Windows ports just exit() in this
338 * situation since they're one-connection-per-process).
339 *
340 * What I need to do is to make this function optionally-
341 * asynchronous, much like the interface to agent_query(). It
342 * can either run modally and return a result directly, _or_ it
343 * can kick off a non-modal dialog, return a `please wait'
344 * status, and the dialog can call the backend back when the
345 * result comes in. Also, in either case, the aye/nay result
346 * wants to be passed to the backend so that it can tear down
347 * the connection if the answer was nay.
348 *
349 * For the moment, I simply bomb out if we have an unrecognised
350 * host key. This makes this port safe but not very useful: you
351 * can only use it at all if you already have a host key cache
352 * set up by running the Unix port.
353 */
354 fatalbox("Host key dialog box not supported yet");
355 }
356
357 void old_keyfile_warning(void)
358 {
359 /*
360 * This should never happen on OS X. We hope.
361 */
362 }
363
364 void about_box(void *window)
365 {
366 /* FIXME */
367 }