1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-10 09:58:01 +00:00
putty-source/macosx/osxdlg.m
Simon Tatham 46bfde32e8 Initial checkin of a native Mac OS X port, sharing most of its code
with the Unix port and layering a Cocoa GUI on top. The basics all
work: there's a configuration panel and a terminal window, the
timing interface works and the select interface functions. The same
application can run both SSH (or other network) connections and
local pty sessions, and multiple sessions in the same process are
fully supported.

However, it's horribly unfinished in a wide variety of other ways;
anyone interested is invited to read README.OSX and wince at the
length and content of its `unfinished' list.

[originally from svn r5308]
2005-02-15 21:45:50 +00:00

368 lines
9.0 KiB
Objective-C

/*
* osxdlg.m: various PuTTY dialog boxes for OS X.
*/
#import <Cocoa/Cocoa.h>
#include "putty.h"
#include "storage.h"
#include "dialog.h"
#include "osxclass.h"
/*
* The `ConfigWindow' class is used to start up a new PuTTY
* session.
*/
@class ConfigTree;
@interface ConfigTree : NSObject
{
NSString **paths;
int *levels;
int nitems, itemsize;
}
- (void)addPath:(char *)path;
@end
@implementation ConfigTree
- (id)init
{
self = [super init];
paths = NULL;
levels = NULL;
nitems = itemsize = 0;
return self;
}
- (void)addPath:(char *)path
{
if (nitems >= itemsize) {
itemsize += 32;
paths = sresize(paths, itemsize, NSString *);
levels = sresize(levels, itemsize, int);
}
paths[nitems] = [[NSString stringWithCString:path] retain];
levels[nitems] = ctrl_path_elements(path) - 1;
nitems++;
}
- (void)dealloc
{
int i;
for (i = 0; i < nitems; i++)
[paths[i] release];
sfree(paths);
sfree(levels);
[super dealloc];
}
- (id)iterateChildren:(int)index ofItem:(id)item count:(int *)count
{
int i, plevel;
if (item) {
for (i = 0; i < nitems; i++)
if (paths[i] == item)
break;
assert(i < nitems);
plevel = levels[i];
i++;
} else {
i = 0;
plevel = -1;
}
if (count)
*count = 0;
while (index > 0) {
if (i >= nitems || levels[i] != plevel+1)
return nil;
if (count)
(*count)++;
do {
i++;
} while (i < nitems && levels[i] > plevel+1);
index--;
}
return paths[i];
}
- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
{
return [self iterateChildren:index ofItem:item count:NULL];
}
- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
int count = 0;
/* pass nitems+1 to ensure we run off the end */
[self iterateChildren:nitems+1 ofItem:item count:&count];
return count;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
return [self outlineView:outlineView numberOfChildrenOfItem:item] > 0;
}
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
/*
* Trim off all path elements except the last one.
*/
NSArray *components = [item componentsSeparatedByString:@"/"];
return [components objectAtIndex:[components count]-1];
}
@end
@implementation ConfigWindow
- (id)initWithConfig:(Config)aCfg
{
NSScrollView *scrollview;
NSTableColumn *col;
ConfigTree *treedata;
int by = 0, mby = 0;
int wmin = 0;
int hmin = 0;
int panelht = 0;
get_sesslist(&sl, TRUE);
ctrlbox = ctrl_new_box();
setup_config_box(ctrlbox, &sl, FALSE /*midsession*/, aCfg.protocol,
0 /* protcfginfo */);
unix_setup_config_box(ctrlbox, FALSE /*midsession*/);
cfg = aCfg; /* structure copy */
self = [super initWithContentRect:NSMakeRect(0,0,300,300)
styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask |
NSClosableWindowMask)
backing:NSBackingStoreBuffered
defer:YES];
[self setTitle:@"PuTTY Configuration"];
[self setIgnoresMouseEvents:NO];
dv = fe_dlg_init(&cfg, self, self, @selector(configBoxFinished:));
scrollview = [[NSScrollView alloc] initWithFrame:NSMakeRect(20,20,10,10)];
treeview = [[NSOutlineView alloc] initWithFrame:[scrollview frame]];
[scrollview setBorderType:NSLineBorder];
[scrollview setDocumentView:treeview];
[[self contentView] addSubview:scrollview];
[scrollview setHasVerticalScroller:YES];
[scrollview setAutohidesScrollers:YES];
/* FIXME: the below is untested. Test it then remove this notice. */
[treeview setAllowsColumnReordering:NO];
[treeview setAllowsColumnResizing:NO];
[treeview setAllowsMultipleSelection:NO];
[treeview setAllowsEmptySelection:NO];
[treeview setAllowsColumnSelection:YES];
treedata = [[[ConfigTree alloc] init] retain];
col = [[NSTableColumn alloc] initWithIdentifier:nil];
[treeview addTableColumn:col];
[treeview setOutlineTableColumn:col];
[[treeview headerView] setFrame:NSMakeRect(0,0,0,0)];
/*
* Create the controls.
*/
{
int i;
char *path = NULL;
for (i = 0; i < ctrlbox->nctrlsets; i++) {
struct controlset *s = ctrlbox->ctrlsets[i];
int mw, mh;
if (!*s->pathname) {
create_ctrls(dv, [self contentView], s, &mw, &mh);
by += 20 + mh;
if (wmin < mw + 40)
wmin = mw + 40;
} else {
int j = path ? ctrl_path_compare(s->pathname, path) : 0;
if (j != INT_MAX) { /* add to treeview, start new panel */
char *c;
/*
* We expect never to find an implicit path
* component. For example, we expect never to
* see A/B/C followed by A/D/E, because that
* would _implicitly_ create A/D. All our path
* prefixes are expected to contain actual
* controls and be selectable in the treeview;
* so we would expect to see A/D _explicitly_
* before encountering A/D/E.
*/
assert(j == ctrl_path_elements(s->pathname) - 1);
c = strrchr(s->pathname, '/');
if (!c)
c = s->pathname;
else
c++;
[treedata addPath:s->pathname];
path = s->pathname;
panelht = 0;
}
create_ctrls(dv, [self contentView], s, &mw, &mh);
if (wmin < mw + 3*20+150)
wmin = mw + 3*20+150;
panelht += mh + 20;
if (hmin < panelht - 20)
hmin = panelht - 20;
}
}
}
{
int i;
NSRect r;
[treeview setDataSource:treedata];
for (i = [treeview numberOfRows]; i-- ;)
[treeview expandItem:[treeview itemAtRow:i] expandChildren:YES];
[treeview sizeToFit];
r = [treeview frame];
if (hmin < r.size.height)
hmin = r.size.height;
}
[self setContentSize:NSMakeSize(wmin, hmin+60+by)];
[scrollview setFrame:NSMakeRect(20, 40+by, 150, hmin)];
[treeview setDelegate:self];
mby = by;
/*
* Now place the controls.
*/
{
int i;
char *path = NULL;
panelht = 0;
for (i = 0; i < ctrlbox->nctrlsets; i++) {
struct controlset *s = ctrlbox->ctrlsets[i];
if (!*s->pathname) {
by -= VSPACING + place_ctrls(dv, s, 20, by, wmin-40);
} else {
if (!path || strcmp(s->pathname, path))
panelht = 0;
panelht += VSPACING + place_ctrls(dv, s, 2*20+150,
40+mby+hmin-panelht,
wmin - (3*20+150));
path = s->pathname;
}
}
}
select_panel(dv, ctrlbox, [[treeview itemAtRow:0] cString]);
[treeview reloadData];
dlg_refresh(NULL, dv);
[self center]; /* :-) */
return self;
}
- (void)configBoxFinished:(id)object
{
int ret = [object intValue]; /* it'll be an NSNumber */
if (ret) {
[controller performSelectorOnMainThread:
@selector(newSessionWithConfig:)
withObject:[NSData dataWithBytes:&cfg length:sizeof(cfg)]
waitUntilDone:NO];
}
[self close];
}
- (void)outlineViewSelectionDidChange:(NSNotification *)notification
{
const char *path = [[treeview itemAtRow:[treeview selectedRow]] cString];
select_panel(dv, ctrlbox, path);
}
- (BOOL)outlineView:(NSOutlineView *)outlineView
shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
return NO; /* no editing! */
}
@end
/* ----------------------------------------------------------------------
* Various special-purpose dialog boxes.
*/
int askappend(void *frontend, Filename filename)
{
return 0; /* FIXME */
}
void askalg(void *frontend, const char *algtype, const char *algname)
{
fatalbox("Cipher algorithm dialog box not supported yet");
return; /* FIXME */
}
void verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
char *keystr, char *fingerprint)
{
int ret;
/*
* Verify the key.
*/
ret = verify_host_key(host, port, keytype, keystr);
if (ret == 0)
return;
/*
* FIXME FIXME FIXME. I currently lack any sensible means of
* asking the user for a verification non-application-modally,
* _or_ any means of closing just this connection if the answer
* is no (the Unix and Windows ports just exit() in this
* situation since they're one-connection-per-process).
*
* What I need to do is to make this function optionally-
* asynchronous, much like the interface to agent_query(). It
* can either run modally and return a result directly, _or_ it
* can kick off a non-modal dialog, return a `please wait'
* status, and the dialog can call the backend back when the
* result comes in. Also, in either case, the aye/nay result
* wants to be passed to the backend so that it can tear down
* the connection if the answer was nay.
*
* For the moment, I simply bomb out if we have an unrecognised
* host key. This makes this port safe but not very useful: you
* can only use it at all if you already have a host key cache
* set up by running the Unix port.
*/
fatalbox("Host key dialog box not supported yet");
}
void old_keyfile_warning(void)
{
/*
* This should never happen on OS X. We hope.
*/
}
void about_box(void *window)
{
/* FIXME */
}