putty/macosx/osxctrls.m

1805 строки
43 KiB
Objective-C

/*
* osxctrls.m: OS X implementation of the dialog.h interface.
*/
#import <Cocoa/Cocoa.h>
#include "putty.h"
#include "dialog.h"
#include "osxclass.h"
#include "tree234.h"
/*
* Still to be implemented:
*
* - file selectors (NSOpenPanel / NSSavePanel)
*
* - font selectors
* - colour selectors
* * both of these have a conceptual oddity in Cocoa that
* you're only supposed to have one per application. But I
* currently expect to be able to have multiple PuTTY config
* boxes on screen at once; what happens if you trigger the
* font selector in each one at the same time?
* * if it comes to that, the _font_ selector can probably be
* managed by other means: nobody is forcing me to implement
* a font selector using a `Change...' button. The portable
* dialog interface gives me the flexibility to do this how I
* want.
* * The colour selector interface, in its present form, is
* more interesting and _if_ a radical change of plan is
* required then it may stretch across the interface into the
* portable side.
* * Before I do anything rash I should start by looking at the
* Mac Classic port and see how it's done there, on the basis
* that Apple seem reasonably unlikely to have invented this
* crazy restriction specifically for OS X.
*
* - focus management
* * I tried using makeFirstResponder to give keyboard focus,
* but it appeared not to work. Try again, and work out how
* it should be done.
* * also look into tab order. Currently pressing Tab suggests
* that only edit boxes and list boxes can get the keyboard
* focus, and that buttons (in all their forms) are unable to
* be driven by the keyboard. Find out for sure.
*
* - dlg_error_msg
* * this may run into the usual aggro with modal dialog boxes.
*/
/*
* For Cocoa control layout, I need a two-stage process. In stage
* one, I allocate all the controls and measure their natural
* sizes, which allows me to compute the _minimum_ width and height
* of a given section of dialog. Then, in stage two, I lay out the
* dialog box as a whole, decide how much each section of the box
* needs to receive, and assign it its final size.
*/
/*
* As yet unsolved issues [FIXME]:
*
* - Sometimes the height returned from create_ctrls and the
* height returned from place_ctrls differ. Find out why. It may
* be harmless (e.g. results of NSTextView being odd), but I
* want to know.
*
* - NSTextViews are indented a bit. It'd be nice to put their
* left margin at the same place as everything else's.
*
* - I don't yet know whether we even _can_ support tab order or
* keyboard shortcuts. If we can't, then fair enough, we can't.
* But if we can, we should.
*
* - I would _really_ like to know of a better way to correct
* NSButton's stupid size estimates than by subclassing it and
* overriding sizeToFit with hard-wired sensible values!
*
* - Speaking of stupid size estimates, the amount by which I'm
* adjusting a titled NSBox (currently equal to the point size
* of its title font) looks as if it isn't _quite_ enough.
* Figure out what the real amount should be and use it.
*
* - I don't understand why there's always a scrollbar displayed
* in each list box. I thought I told it to autohide scrollers?
*
* - Why do I have to fudge list box heights by adding one? (Might
* it be to do with the missing header view?)
*/
/*
* Subclass of NSButton which corrects the fact that the normal
* one's sizeToFit method persistently returns 32 as its height,
* which is simply a lie. I have yet to work out a better
* alternative than hard-coding the real heights.
*/
@interface MyButton : NSButton
{
int minht;
}
@end
@implementation MyButton
- (id)initWithFrame:(NSRect)r
{
self = [super initWithFrame:r];
minht = 25;
return self;
}
- (void)setButtonType:(NSButtonType)t
{
if (t == NSRadioButton || t == NSSwitchButton)
minht = 18;
else
minht = 25;
[super setButtonType:t];
}
- (void)sizeToFit
{
NSRect r;
[super sizeToFit];
r = [self frame];
r.size.height = minht;
[self setFrame:r];
}
@end
/*
* Class used as the data source for NSTableViews.
*/
@interface MyTableSource : NSObject
{
tree234 *tree;
}
- (id)init;
- (void)add:(const char *)str withId:(int)id;
- (int)getid:(int)index;
- (void)swap:(int)index1 with:(int)index2;
- (void)removestr:(int)index;
- (void)clear;
@end
@implementation MyTableSource
- (id)init
{
self = [super init];
tree = newtree234(NULL);
return self;
}
- (void)dealloc
{
char *p;
while ((p = delpos234(tree, 0)) != NULL)
sfree(p);
freetree234(tree);
[super dealloc];
}
- (void)add:(const char *)str withId:(int)id
{
addpos234(tree, dupprintf("%d\t%s", id, str), count234(tree));
}
- (int)getid:(int)index
{
char *p = index234(tree, index);
return atoi(p);
}
- (void)removestr:(int)index
{
char *p = delpos234(tree, index);
sfree(p);
}
- (void)swap:(int)index1 with:(int)index2
{
char *p1, *p2;
if (index1 > index2) {
int t = index1; index1 = index2; index2 = t;
}
/* delete later one first so it doesn't affect index of earlier one */
p2 = delpos234(tree, index2);
p1 = delpos234(tree, index1);
/* now insert earlier one before later one for the inverse reason */
addpos234(tree, p2, index1);
addpos234(tree, p1, index2);
}
- (void)clear
{
char *p;
while ((p = delpos234(tree, 0)) != NULL)
sfree(p);
}
- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
return count234(tree);
}
- (id)tableView:(NSTableView *)aTableView
objectValueForTableColumn:(NSTableColumn *)aTableColumn
row:(int)rowIndex
{
int j = [[aTableColumn identifier] intValue];
char *p = index234(tree, rowIndex);
while (j >= 0) {
p += strcspn(p, "\t");
if (*p) p++;
j--;
}
return [NSString stringWithCString:p length:strcspn(p, "\t")];
}
@end
/*
* Object to receive messages from various control classes.
*/
@class Receiver;
struct fe_dlg {
NSWindow *window;
NSObject *target;
SEL action;
tree234 *byctrl;
tree234 *bywidget;
tree234 *boxes;
void *data; /* passed to portable side */
Receiver *rec;
};
@interface Receiver : NSObject
{
struct fe_dlg *d;
}
- (id)initWithStruct:(struct fe_dlg *)aStruct;
@end
struct fe_ctrl {
union control *ctrl;
NSButton *button, *button2;
NSTextField *label, *editbox;
NSComboBox *combobox;
NSButton **radiobuttons;
NSTextView *textview;
NSPopUpButton *popupbutton;
NSTableView *tableview;
NSScrollView *scrollview;
int nradiobuttons;
void *privdata;
int privdata_needs_free;
};
static int fe_ctrl_cmp_by_ctrl(void *av, void *bv)
{
struct fe_ctrl *a = (struct fe_ctrl *)av;
struct fe_ctrl *b = (struct fe_ctrl *)bv;
if (a->ctrl < b->ctrl)
return -1;
if (a->ctrl > b->ctrl)
return +1;
return 0;
}
static int fe_ctrl_find_by_ctrl(void *av, void *bv)
{
union control *a = (union control *)av;
struct fe_ctrl *b = (struct fe_ctrl *)bv;
if (a < b->ctrl)
return -1;
if (a > b->ctrl)
return +1;
return 0;
}
struct fe_box {
struct controlset *s;
id box;
};
static int fe_boxcmp(void *av, void *bv)
{
struct fe_box *a = (struct fe_box *)av;
struct fe_box *b = (struct fe_box *)bv;
if (a->s < b->s)
return -1;
if (a->s > b->s)
return +1;
return 0;
}
static int fe_boxfind(void *av, void *bv)
{
struct controlset *a = (struct controlset *)av;
struct fe_box *b = (struct fe_box *)bv;
if (a < b->s)
return -1;
if (a > b->s)
return +1;
return 0;
}
struct fe_backwards { /* map Cocoa widgets back to fe_ctrls */
id widget;
struct fe_ctrl *c;
};
static int fe_backwards_cmp_by_widget(void *av, void *bv)
{
struct fe_backwards *a = (struct fe_backwards *)av;
struct fe_backwards *b = (struct fe_backwards *)bv;
if (a->widget < b->widget)
return -1;
if (a->widget > b->widget)
return +1;
return 0;
}
static int fe_backwards_find_by_widget(void *av, void *bv)
{
id a = (id)av;
struct fe_backwards *b = (struct fe_backwards *)bv;
if (a < b->widget)
return -1;
if (a > b->widget)
return +1;
return 0;
}
static struct fe_ctrl *fe_ctrl_new(union control *ctrl)
{
struct fe_ctrl *c;
c = snew(struct fe_ctrl);
c->ctrl = ctrl;
c->button = c->button2 = nil;
c->label = nil;
c->editbox = nil;
c->combobox = nil;
c->textview = nil;
c->popupbutton = nil;
c->tableview = nil;
c->scrollview = nil;
c->radiobuttons = NULL;
c->nradiobuttons = 0;
c->privdata = NULL;
c->privdata_needs_free = FALSE;
return c;
}
static void fe_ctrl_free(struct fe_ctrl *c)
{
if (c->privdata_needs_free)
sfree(c->privdata);
sfree(c->radiobuttons);
sfree(c);
}
static struct fe_ctrl *fe_ctrl_byctrl(struct fe_dlg *d, union control *ctrl)
{
return find234(d->byctrl, ctrl, fe_ctrl_find_by_ctrl);
}
static void add_box(struct fe_dlg *d, struct controlset *s, id box)
{
struct fe_box *b = snew(struct fe_box);
b->box = box;
b->s = s;
add234(d->boxes, b);
}
static id find_box(struct fe_dlg *d, struct controlset *s)
{
struct fe_box *b = find234(d->boxes, s, fe_boxfind);
return b ? b->box : NULL;
}
static void add_widget(struct fe_dlg *d, struct fe_ctrl *c, id widget)
{
struct fe_backwards *b = snew(struct fe_backwards);
b->widget = widget;
b->c = c;
add234(d->bywidget, b);
}
static struct fe_ctrl *find_widget(struct fe_dlg *d, id widget)
{
struct fe_backwards *b = find234(d->bywidget, widget,
fe_backwards_find_by_widget);
return b ? b->c : NULL;
}
void *fe_dlg_init(void *data, NSWindow *window, NSObject *target, SEL action)
{
struct fe_dlg *d;
d = snew(struct fe_dlg);
d->window = window;
d->target = target;
d->action = action;
d->byctrl = newtree234(fe_ctrl_cmp_by_ctrl);
d->bywidget = newtree234(fe_backwards_cmp_by_widget);
d->boxes = newtree234(fe_boxcmp);
d->data = data;
d->rec = [[Receiver alloc] initWithStruct:d];
return d;
}
void fe_dlg_free(void *dv)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
struct fe_ctrl *c;
struct fe_box *b;
while ( (c = delpos234(d->byctrl, 0)) != NULL )
fe_ctrl_free(c);
freetree234(d->byctrl);
while ( (c = delpos234(d->bywidget, 0)) != NULL )
sfree(c);
freetree234(d->bywidget);
while ( (b = delpos234(d->boxes, 0)) != NULL )
sfree(b);
freetree234(d->boxes);
[d->rec release];
sfree(d);
}
@implementation Receiver
- (id)initWithStruct:(struct fe_dlg *)aStruct
{
self = [super init];
d = aStruct;
return self;
}
- (void)buttonPushed:(id)sender
{
struct fe_ctrl *c = find_widget(d, sender);
assert(c && c->ctrl->generic.type == CTRL_BUTTON);
c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION);
}
- (void)checkboxChanged:(id)sender
{
struct fe_ctrl *c = find_widget(d, sender);
assert(c && c->ctrl->generic.type == CTRL_CHECKBOX);
c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
}
- (void)radioChanged:(id)sender
{
struct fe_ctrl *c = find_widget(d, sender);
int j;
assert(c && c->radiobuttons);
for (j = 0; j < c->nradiobuttons; j++)
if (sender != c->radiobuttons[j])
[c->radiobuttons[j] setState:NSOffState];
c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
}
- (void)popupMenuSelected:(id)sender
{
struct fe_ctrl *c = find_widget(d, sender);
c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
}
- (void)controlTextDidChange:(NSNotification *)notification
{
id widget = [notification object];
struct fe_ctrl *c = find_widget(d, widget);
assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
}
- (void)controlTextDidEndEditing:(NSNotification *)notification
{
id widget = [notification object];
struct fe_ctrl *c = find_widget(d, widget);
assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_REFRESH);
}
- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
id widget = [notification object];
struct fe_ctrl *c = find_widget(d, widget);
assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_SELCHANGE);
}
- (BOOL)tableView:(NSTableView *)aTableView
shouldEditTableColumn:(NSTableColumn *)aTableColumn
row:(int)rowIndex
{
return NO; /* no editing permitted */
}
- (void)listDoubleClicked:(id)sender
{
struct fe_ctrl *c = find_widget(d, sender);
assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION);
}
- (void)dragListButton:(id)sender
{
struct fe_ctrl *c = find_widget(d, sender);
int direction, row, nrows;
assert(c && c->ctrl->generic.type == CTRL_LISTBOX &&
c->ctrl->listbox.draglist);
if (sender == c->button)
direction = -1; /* up */
else
direction = +1; /* down */
row = [c->tableview selectedRow];
nrows = [c->tableview numberOfRows];
if (row + direction < 0 || row + direction >= nrows) {
NSBeep();
return;
}
[[c->tableview dataSource] swap:row with:row+direction];
[c->tableview reloadData];
[c->tableview selectRow:row+direction byExtendingSelection:NO];
c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
}
@end
void create_ctrls(void *dv, NSView *parent, struct controlset *s,
int *minw, int *minh)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
int ccw[100]; /* cumulative column widths */
int cypos[100];
int ncols;
int wmin = 0, hmin = 0;
int i, j, cw, ch;
NSRect rect;
NSFont *textviewfont = nil;
int boxh = 0, boxw = 0;
if (!s->boxname && s->boxtitle) {
/* This controlset is a panel title. */
NSTextField *tf;
tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
[tf setEditable:NO];
[tf setSelectable:NO];
[tf setBordered:NO];
[tf setDrawsBackground:NO];
[tf setStringValue:[NSString stringWithCString:s->boxtitle]];
[tf sizeToFit];
rect = [tf frame];
[parent addSubview:tf];
/*
* I'm going to store this NSTextField in the boxes tree,
* because I really can't face having a special tree234
* mapping controlsets to panel titles.
*/
add_box(d, s, tf);
*minw = rect.size.width;
*minh = rect.size.height;
return;
}
if (*s->boxname) {
/*
* Create an NSBox to contain this subset of controls.
*/
NSBox *box;
NSRect tmprect;
box = [[NSBox alloc] initWithFrame:NSMakeRect(0,0,1,1)];
if (s->boxtitle)
[box setTitle:[NSString stringWithCString:s->boxtitle]];
else
[box setTitlePosition:NSNoTitle];
add_box(d, s, box);
tmprect = [box frame];
[box setContentViewMargins:NSMakeSize(20,20)];
[box setFrameFromContentFrame:NSMakeRect(100,100,100,100)];
rect = [box frame];
[box setFrame:tmprect];
boxh = (int)(rect.size.height - 100);
boxw = (int)(rect.size.width - 100);
[parent addSubview:box];
if (s->boxtitle)
boxh += [[box titleFont] pointSize];
/*
* All subsequent controls will be placed within this box.
*/
parent = box;
}
ncols = 1;
ccw[0] = 0;
ccw[1] = 100;
cypos[0] = 0;
/*
* Now iterate through the controls themselves, create them,
* and add their width and height to the overall width/height
* calculation.
*/
for (i = 0; i < s->ncontrols; i++) {
union control *ctrl = s->ctrls[i];
struct fe_ctrl *c;
int colstart = COLUMN_START(ctrl->generic.column);
int colspan = COLUMN_SPAN(ctrl->generic.column);
int colend = colstart + colspan;
int ytop, wthis;
switch (ctrl->generic.type) {
case CTRL_COLUMNS:
for (j = 1; j < ncols; j++)
if (cypos[0] < cypos[j])
cypos[0] = cypos[j];
assert(ctrl->columns.ncols < lenof(ccw));
ccw[0] = 0;
for (j = 0; j < ctrl->columns.ncols; j++) {
ccw[j+1] = ccw[j] + (ctrl->columns.percentages ?
ctrl->columns.percentages[j] : 100);
cypos[j] = cypos[0];
}
ncols = ctrl->columns.ncols;
continue; /* no actual control created */
case CTRL_TABDELAY:
/*
* I'm currently uncertain that we can implement tab
* order in OS X.
*/
continue; /* no actual control created */
}
c = fe_ctrl_new(ctrl);
add234(d->byctrl, c);
cw = ch = 0;
switch (ctrl->generic.type) {
case CTRL_BUTTON:
case CTRL_CHECKBOX:
{
NSButton *b;
b = [[MyButton alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
[b setBezelStyle:NSRoundedBezelStyle];
if (ctrl->generic.type == CTRL_CHECKBOX)
[b setButtonType:NSSwitchButton];
[b setTitle:[NSString stringWithCString:ctrl->generic.label]];
if (ctrl->button.isdefault)
[b setKeyEquivalent:@"\r"];
else if (ctrl->button.iscancel)
[b setKeyEquivalent:@"\033"];
[b sizeToFit];
rect = [b frame];
[parent addSubview:b];
[b setTarget:d->rec];
if (ctrl->generic.type == CTRL_CHECKBOX)
[b setAction:@selector(checkboxChanged:)];
else
[b setAction:@selector(buttonPushed:)];
add_widget(d, c, b);
c->button = b;
cw = rect.size.width;
ch = rect.size.height;
}
break;
case CTRL_EDITBOX:
{
int editp = ctrl->editbox.percentwidth;
int labelp = editp == 100 ? 100 : 100 - editp;
NSTextField *tf;
NSComboBox *cb;
tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
[tf setEditable:NO];
[tf setSelectable:NO];
[tf setBordered:NO];
[tf setDrawsBackground:NO];
[tf setStringValue:[NSString
stringWithCString:ctrl->generic.label]];
[tf sizeToFit];
rect = [tf frame];
[parent addSubview:tf];
c->label = tf;
cw = rect.size.width * 100 / labelp;
ch = rect.size.height;
if (ctrl->editbox.has_list) {
cb = [[NSComboBox alloc]
initWithFrame:NSMakeRect(0,0,1,1)];
[cb setStringValue:@"x"];
[cb sizeToFit];
rect = [cb frame];
[parent addSubview:cb];
c->combobox = cb;
} else {
if (ctrl->editbox.password)
tf = [NSSecureTextField alloc];
else
tf = [NSTextField alloc];
tf = [tf initWithFrame:NSMakeRect(0,0,1,1)];
[tf setEditable:YES];
[tf setSelectable:YES];
[tf setBordered:YES];
[tf setStringValue:@"x"];
[tf sizeToFit];
rect = [tf frame];
[parent addSubview:tf];
c->editbox = tf;
[tf setDelegate:d->rec];
add_widget(d, c, tf);
}
if (editp == 100) {
/* the edit box and its label are vertically separated */
ch += VSPACING + rect.size.height;
} else {
/* the edit box and its label are horizontally separated */
if (ch < rect.size.height)
ch = rect.size.height;
}
if (cw < rect.size.width * 100 / editp)
cw = rect.size.width * 100 / editp;
}
break;
case CTRL_TEXT:
{
NSTextView *tv;
int testwid;
if (!textviewfont) {
NSTextField *tf;
tf = [[NSTextField alloc] init];
textviewfont = [tf font];
[tf release];
}
testwid = (ccw[colend] - ccw[colstart]) * 3;
tv = [[NSTextView alloc]
initWithFrame:NSMakeRect(0,0,testwid,1)];
[tv setEditable:NO];
[tv setSelectable:NO];
//[tv setBordered:NO];
[tv setDrawsBackground:NO];
[tv setFont:textviewfont];
[tv setString:
[NSString stringWithCString:ctrl->generic.label]];
rect = [tv frame];
[tv sizeToFit];
[parent addSubview:tv];
c->textview = tv;
cw = rect.size.width;
ch = rect.size.height;
}
break;
case CTRL_RADIO:
{
NSTextField *tf;
int j;
if (ctrl->generic.label) {
tf = [[NSTextField alloc]
initWithFrame:NSMakeRect(0,0,1,1)];
[tf setEditable:NO];
[tf setSelectable:NO];
[tf setBordered:NO];
[tf setDrawsBackground:NO];
[tf setStringValue:
[NSString stringWithCString:ctrl->generic.label]];
[tf sizeToFit];
rect = [tf frame];
[parent addSubview:tf];
c->label = tf;
cw = rect.size.width;
ch = rect.size.height;
} else {
cw = 0;
ch = -VSPACING; /* compensate for next advance */
}
c->nradiobuttons = ctrl->radio.nbuttons;
c->radiobuttons = snewn(ctrl->radio.nbuttons, NSButton *);
for (j = 0; j < ctrl->radio.nbuttons; j++) {
NSButton *b;
int ncols;
b = [[MyButton alloc] initWithFrame:NSMakeRect(0,0,1,1)];
[b setBezelStyle:NSRoundedBezelStyle];
[b setButtonType:NSRadioButton];
[b setTitle:[NSString
stringWithCString:ctrl->radio.buttons[j]]];
[b sizeToFit];
rect = [b frame];
[parent addSubview:b];
c->radiobuttons[j] = b;
[b setTarget:d->rec];
[b setAction:@selector(radioChanged:)];
add_widget(d, c, b);
/*
* Add to the height every time we place a
* button in column 0.
*/
if (j % ctrl->radio.ncolumns == 0) {
ch += rect.size.height + VSPACING;
}
/*
* Add to the width by working out how many
* columns this button spans.
*/
if (j == ctrl->radio.nbuttons - 1)
ncols = (ctrl->radio.ncolumns -
(j % ctrl->radio.ncolumns));
else
ncols = 1;
if (cw < rect.size.width * ctrl->radio.ncolumns / ncols)
cw = rect.size.width * ctrl->radio.ncolumns / ncols;
}
}
break;
case CTRL_FILESELECT:
case CTRL_FONTSELECT:
{
NSTextField *tf;
NSButton *b;
int kh;
tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
[tf setEditable:NO];
[tf setSelectable:NO];
[tf setBordered:NO];
[tf setDrawsBackground:NO];
[tf setStringValue:[NSString
stringWithCString:ctrl->generic.label]];
[tf sizeToFit];
rect = [tf frame];
[parent addSubview:tf];
c->label = tf;
cw = rect.size.width;
ch = rect.size.height;
tf = [NSTextField alloc];
tf = [tf initWithFrame:NSMakeRect(0,0,1,1)];
if (ctrl->generic.type == CTRL_FILESELECT) {
[tf setEditable:YES];
[tf setSelectable:YES];
[tf setBordered:YES];
} else {
[tf setEditable:NO];
[tf setSelectable:NO];
[tf setBordered:NO];
[tf setDrawsBackground:NO];
}
[tf setStringValue:@"x"];
[tf sizeToFit];
rect = [tf frame];
[parent addSubview:tf];
c->editbox = tf;
kh = rect.size.height;
if (cw < rect.size.width * 4 / 3)
cw = rect.size.width * 4 / 3;
b = [[MyButton alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
[b setBezelStyle:NSRoundedBezelStyle];
if (ctrl->generic.type == CTRL_FILESELECT)
[b setTitle:@"Browse..."];
else
[b setTitle:@"Change..."];
// [b setKeyEquivalent:somethingorother];
// [b setTarget:somethingorother];
// [b setAction:somethingorother];
[b sizeToFit];
rect = [b frame];
[parent addSubview:b];
c->button = b;
if (kh < rect.size.height)
kh = rect.size.height;
ch += VSPACING + kh;
if (cw < rect.size.width * 4)
cw = rect.size.width * 4;
}
break;
case CTRL_LISTBOX:
{
int listp = ctrl->listbox.percentwidth;
int labelp = listp == 100 ? 100 : 100 - listp;
NSTextField *tf;
NSPopUpButton *pb;
NSTableView *tv;
NSScrollView *sv;
if (ctrl->generic.label) {
tf = [[NSTextField alloc]
initWithFrame:NSMakeRect(0,0,1,1)];
[tf setEditable:NO];
[tf setSelectable:NO];
[tf setBordered:NO];
[tf setDrawsBackground:NO];
[tf setStringValue:
[NSString stringWithCString:ctrl->generic.label]];
[tf sizeToFit];
rect = [tf frame];
[parent addSubview:tf];
c->label = tf;
cw = rect.size.width;
ch = rect.size.height;
} else {
cw = 0;
ch = -VSPACING; /* compensate for next advance */
}
if (ctrl->listbox.height == 0) {
pb = [[NSPopUpButton alloc]
initWithFrame:NSMakeRect(0,0,1,1)];
[pb sizeToFit];
rect = [pb frame];
[parent addSubview:pb];
c->popupbutton = pb;
[pb setTarget:d->rec];
[pb setAction:@selector(popupMenuSelected:)];
add_widget(d, c, pb);
} else {
assert(listp == 100);
if (ctrl->listbox.draglist) {
int bi;
listp = 75;
for (bi = 0; bi < 2; bi++) {
NSButton *b;
b = [[MyButton alloc]
initWithFrame:NSMakeRect(0, 0, 1, 1)];
[b setBezelStyle:NSRoundedBezelStyle];
if (bi == 0)
[b setTitle:@"Up"];
else
[b setTitle:@"Down"];
[b sizeToFit];
rect = [b frame];
[parent addSubview:b];
if (bi == 0)
c->button = b;
else
c->button2 = b;
[b setTarget:d->rec];
[b setAction:@selector(dragListButton:)];
add_widget(d, c, b);
if (cw < rect.size.width * 4)
cw = rect.size.width * 4;
}
}
sv = [[NSScrollView alloc] initWithFrame:
NSMakeRect(20,20,10,10)];
[sv setBorderType:NSLineBorder];
tv = [[NSTableView alloc] initWithFrame:[sv frame]];
[[tv headerView] setFrame:NSMakeRect(0,0,0,0)];
[sv setDocumentView:tv];
[parent addSubview:sv];
[sv setHasVerticalScroller:YES];
[sv setAutohidesScrollers:YES];
[tv setAllowsColumnReordering:NO];
[tv setAllowsColumnResizing:NO];
[tv setAllowsMultipleSelection:ctrl->listbox.multisel];
[tv setAllowsEmptySelection:YES];
[tv setAllowsColumnSelection:YES];
[tv setDataSource:[[MyTableSource alloc] init]];
rect = [tv frame];
/*
* For some reason this consistently comes out
* one short. Add one.
*/
rect.size.height = (ctrl->listbox.height+1)*[tv rowHeight];
[sv setFrame:rect];
c->tableview = tv;
c->scrollview = sv;
[tv setDelegate:d->rec];
[tv setTarget:d->rec];
[tv setDoubleAction:@selector(listDoubleClicked:)];
add_widget(d, c, tv);
}
if (c->tableview) {
int ncols, *percentages;
int hundred = 100;
if (ctrl->listbox.ncols) {
ncols = ctrl->listbox.ncols;
percentages = ctrl->listbox.percentages;
} else {
ncols = 1;
percentages = &hundred;
}
for (j = 0; j < ncols; j++) {
NSTableColumn *col;
col = [[NSTableColumn alloc] initWithIdentifier:
[NSNumber numberWithInt:j]];
[c->tableview addTableColumn:col];
}
}
if (labelp == 100) {
/* the list and its label are vertically separated */
ch += VSPACING + rect.size.height;
} else {
/* the list and its label are horizontally separated */
if (ch < rect.size.height)
ch = rect.size.height;
}
if (cw < rect.size.width * 100 / listp)
cw = rect.size.width * 100 / listp;
}
break;
}
/*
* Update the width and height data for the control we've
* just created.
*/
ytop = 0;
for (j = colstart; j < colend; j++) {
if (ytop < cypos[j])
ytop = cypos[j];
}
for (j = colstart; j < colend; j++)
cypos[j] = ytop + ch + VSPACING;
if (hmin < ytop + ch)
hmin = ytop + ch;
wthis = (cw + HSPACING) * 100 / (ccw[colend] - ccw[colstart]);
wthis -= HSPACING;
if (wmin < wthis)
wmin = wthis;
}
if (*s->boxname) {
/*
* Add a bit to the width and height for the box.
*/
wmin += boxw;
hmin += boxh;
}
//printf("For controlset %s/%s, returning w=%d h=%d\n",
// s->pathname, s->boxname, wmin, hmin);
*minw = wmin;
*minh = hmin;
}
int place_ctrls(void *dv, struct controlset *s, int leftx, int topy,
int width)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
int ccw[100]; /* cumulative column widths */
int cypos[100];
int ncols;
int i, j, ret;
int boxh = 0, boxw = 0;
if (!s->boxname && s->boxtitle) {
/* Size and place the panel title. */
NSTextField *tf = find_box(d, s);
NSRect rect;
rect = [tf frame];
[tf setFrame:NSMakeRect(leftx, topy-rect.size.height,
width, rect.size.height)];
return rect.size.height;
}
if (*s->boxname) {
NSRect rect, tmprect;
NSBox *box = find_box(d, s);
assert(box != NULL);
tmprect = [box frame];
[box setFrameFromContentFrame:NSMakeRect(100,100,100,100)];
rect = [box frame];
[box setFrame:tmprect];
boxw = rect.size.width - 100;
boxh = rect.size.height - 100;
if (s->boxtitle)
boxh += [[box titleFont] pointSize];
topy -= boxh;
width -= boxw;
}
ncols = 1;
ccw[0] = 0;
ccw[1] = 100;
cypos[0] = topy;
ret = 0;
/*
* Now iterate through the controls themselves, placing them
* appropriately.
*/
for (i = 0; i < s->ncontrols; i++) {
union control *ctrl = s->ctrls[i];
struct fe_ctrl *c;
int colstart = COLUMN_START(ctrl->generic.column);
int colspan = COLUMN_SPAN(ctrl->generic.column);
int colend = colstart + colspan;
int xthis, ythis, wthis, ch;
NSRect rect;
switch (ctrl->generic.type) {
case CTRL_COLUMNS:
for (j = 1; j < ncols; j++)
if (cypos[0] > cypos[j])
cypos[0] = cypos[j];
assert(ctrl->columns.ncols < lenof(ccw));
ccw[0] = 0;
for (j = 0; j < ctrl->columns.ncols; j++) {
ccw[j+1] = ccw[j] + (ctrl->columns.percentages ?
ctrl->columns.percentages[j] : 100);
cypos[j] = cypos[0];
}
ncols = ctrl->columns.ncols;
continue; /* no actual control created */
case CTRL_TABDELAY:
continue; /* nothing to do here, move along */
}
c = fe_ctrl_byctrl(d, ctrl);
ch = 0;
ythis = topy;
for (j = colstart; j < colend; j++) {
if (ythis > cypos[j])
ythis = cypos[j];
}
xthis = (width + HSPACING) * ccw[colstart] / 100;
wthis = (width + HSPACING) * ccw[colend] / 100 - HSPACING - xthis;
xthis += leftx;
switch (ctrl->generic.type) {
case CTRL_BUTTON:
case CTRL_CHECKBOX:
rect = [c->button frame];
[c->button setFrame:NSMakeRect(xthis,ythis-rect.size.height,wthis,
rect.size.height)];
ch = rect.size.height;
break;
case CTRL_EDITBOX:
{
int editp = ctrl->editbox.percentwidth;
int labelp = editp == 100 ? 100 : 100 - editp;
int lheight, theight, rheight, ynext, editw;
NSControl *edit = (c->editbox ? c->editbox : c->combobox);
rect = [c->label frame];
lheight = rect.size.height;
rect = [edit frame];
theight = rect.size.height;
if (editp == 100)
rheight = lheight;
else
rheight = (lheight < theight ? theight : lheight);
[c->label setFrame:
NSMakeRect(xthis, ythis-(rheight+lheight)/2,
(wthis + HSPACING) * labelp / 100 - HSPACING,
lheight)];
if (editp == 100) {
ynext = ythis - rheight - VSPACING;
rheight = theight;
} else {
ynext = ythis;
}
editw = (wthis + HSPACING) * editp / 100 - HSPACING;
[edit setFrame:
NSMakeRect(xthis+wthis-editw, ynext-(rheight+theight)/2,
editw, theight)];
ch = (ythis - ynext) + theight;
}
break;
case CTRL_TEXT:
[c->textview setFrame:NSMakeRect(xthis, 0, wthis, 1)];
[c->textview sizeToFit];
rect = [c->textview frame];
[c->textview setFrame:NSMakeRect(xthis, ythis-rect.size.height,
wthis, rect.size.height)];
ch = rect.size.height;
break;
case CTRL_RADIO:
{
int j, ynext;
if (c->label) {
rect = [c->label frame];
[c->label setFrame:NSMakeRect(xthis,ythis-rect.size.height,
wthis,rect.size.height)];
ynext = ythis - rect.size.height - VSPACING;
} else
ynext = ythis;
for (j = 0; j < ctrl->radio.nbuttons; j++) {
int col = j % ctrl->radio.ncolumns;
int ncols;
int lx,rx;
if (j == ctrl->radio.nbuttons - 1)
ncols = ctrl->radio.ncolumns - col;
else
ncols = 1;
lx = (wthis + HSPACING) * col / ctrl->radio.ncolumns;
rx = ((wthis + HSPACING) *
(col+ncols) / ctrl->radio.ncolumns) - HSPACING;
/*
* Set the frame size.
*/
rect = [c->radiobuttons[j] frame];
[c->radiobuttons[j] setFrame:
NSMakeRect(lx+xthis, ynext-rect.size.height,
rx-lx, rect.size.height)];
/*
* Advance to next line if we're in the last
* column.
*/
if (col + ncols == ctrl->radio.ncolumns)
ynext -= rect.size.height + VSPACING;
}
ch = (ythis - ynext) - VSPACING;
}
break;
case CTRL_FILESELECT:
case CTRL_FONTSELECT:
{
int ynext, eh, bh, th, mx;
rect = [c->label frame];
[c->label setFrame:NSMakeRect(xthis,ythis-rect.size.height,
wthis,rect.size.height)];
ynext = ythis - rect.size.height - VSPACING;
rect = [c->editbox frame];
eh = rect.size.height;
rect = [c->button frame];
bh = rect.size.height;
th = (eh > bh ? eh : bh);
mx = (wthis + HSPACING) * 3 / 4 - HSPACING;
[c->editbox setFrame:
NSMakeRect(xthis, ynext-(th+eh)/2, mx, eh)];
[c->button setFrame:
NSMakeRect(xthis+mx+HSPACING, ynext-(th+bh)/2,
wthis-mx-HSPACING, bh)];
ch = (ythis - ynext) + th + VSPACING;
}
break;
case CTRL_LISTBOX:
{
int listp = ctrl->listbox.percentwidth;
int labelp = listp == 100 ? 100 : 100 - listp;
int lheight, theight, rheight, ynext, listw, xlist;
NSControl *list = (c->scrollview ? (id)c->scrollview :
(id)c->popupbutton);
if (ctrl->listbox.draglist) {
assert(listp == 100);
listp = 75;
}
rect = [list frame];
theight = rect.size.height;
if (c->label) {
rect = [c->label frame];
lheight = rect.size.height;
if (labelp == 100)
rheight = lheight;
else
rheight = (lheight < theight ? theight : lheight);
[c->label setFrame:
NSMakeRect(xthis, ythis-(rheight+lheight)/2,
(wthis + HSPACING) * labelp / 100 - HSPACING,
lheight)];
if (labelp == 100) {
ynext = ythis - rheight - VSPACING;
rheight = theight;
} else {
ynext = ythis;
}
} else {
ynext = ythis;
rheight = theight;
}
listw = (wthis + HSPACING) * listp / 100 - HSPACING;
if (labelp == 100)
xlist = xthis;
else
xlist = xthis+wthis-listw;
[list setFrame: NSMakeRect(xlist, ynext-(rheight+theight)/2,
listw, theight)];
/*
* Size the columns for the table view.
*/
if (c->tableview) {
int ncols, *percentages;
int hundred = 100;
int cpercent = 0, cpixels = 0;
NSArray *cols;
if (ctrl->listbox.ncols) {
ncols = ctrl->listbox.ncols;
percentages = ctrl->listbox.percentages;
} else {
ncols = 1;
percentages = &hundred;
}
cols = [c->tableview tableColumns];
for (j = 0; j < ncols; j++) {
NSTableColumn *col = [cols objectAtIndex:j];
int newcpixels;
cpercent += percentages[j];
newcpixels = listw * cpercent / 100;
[col setWidth:newcpixels-cpixels];
cpixels = newcpixels;
}
}
ch = (ythis - ynext) + theight;
if (c->button) {
int b2height, centre;
int bx, bw;
/*
* Place the Up and Down buttons for a drag list.
*/
assert(c->button2);
rect = [c->button frame];
b2height = VSPACING + 2 * rect.size.height;
centre = ynext - rheight/2;
bx = (wthis + HSPACING) * 3 / 4;
bw = wthis - bx;
bx += leftx;
[c->button setFrame:
NSMakeRect(bx, centre+b2height/2-rect.size.height,
bw, rect.size.height)];
[c->button2 setFrame:
NSMakeRect(bx, centre-b2height/2,
bw, rect.size.height)];
}
}
break;
}
for (j = colstart; j < colend; j++)
cypos[j] = ythis - ch - VSPACING;
if (ret < topy - (ythis - ch))
ret = topy - (ythis - ch);
}
if (*s->boxname) {
NSBox *box = find_box(d, s);
assert(box != NULL);
[box sizeToFit];
if (s->boxtitle) {
NSRect rect = [box frame];
rect.size.height += [[box titleFont] pointSize];
[box setFrame:rect];
}
ret += boxh;
}
//printf("For controlset %s/%s, returning ret=%d\n",
// s->pathname, s->boxname, ret);
return ret;
}
void select_panel(void *dv, struct controlbox *b, const char *name)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
int i, j, hidden;
struct controlset *s;
union control *ctrl;
struct fe_ctrl *c;
NSBox *box;
for (i = 0; i < b->nctrlsets; i++) {
s = b->ctrlsets[i];
if (*s->pathname) {
hidden = !strcmp(s->pathname, name) ? NO : YES;
if ((box = find_box(d, s)) != NULL) {
[box setHidden:hidden];
} else {
for (j = 0; j < s->ncontrols; j++) {
ctrl = s->ctrls[j];
c = fe_ctrl_byctrl(d, ctrl);
if (!c)
continue;
if (c->label)
[c->label setHidden:hidden];
if (c->button)
[c->button setHidden:hidden];
if (c->button2)
[c->button2 setHidden:hidden];
if (c->editbox)
[c->editbox setHidden:hidden];
if (c->combobox)
[c->combobox setHidden:hidden];
if (c->textview)
[c->textview setHidden:hidden];
if (c->tableview)
[c->tableview setHidden:hidden];
if (c->scrollview)
[c->scrollview setHidden:hidden];
if (c->popupbutton)
[c->popupbutton setHidden:hidden];
if (c->radiobuttons) {
int j;
for (j = 0; j < c->nradiobuttons; j++)
[c->radiobuttons[j] setHidden:hidden];
}
break;
}
}
}
}
}
void dlg_radiobutton_set(union control *ctrl, void *dv, int whichbutton)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
int j;
assert(c->radiobuttons);
for (j = 0; j < c->nradiobuttons; j++)
[c->radiobuttons[j] setState:
(j == whichbutton ? NSOnState : NSOffState)];
}
int dlg_radiobutton_get(union control *ctrl, void *dv)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
int j;
assert(c->radiobuttons);
for (j = 0; j < c->nradiobuttons; j++)
if ([c->radiobuttons[j] state] == NSOnState)
return j;
return 0; /* should never reach here */
}
void dlg_checkbox_set(union control *ctrl, void *dv, int checked)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
assert(c->button);
[c->button setState:(checked ? NSOnState : NSOffState)];
}
int dlg_checkbox_get(union control *ctrl, void *dv)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
assert(c->button);
return ([c->button state] == NSOnState);
}
void dlg_editbox_set(union control *ctrl, void *dv, char const *text)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
if (c->editbox) {
[c->editbox setStringValue:[NSString stringWithCString:text]];
} else {
assert(c->combobox);
[c->combobox setStringValue:[NSString stringWithCString:text]];
}
}
void dlg_editbox_get(union control *ctrl, void *dv, char *buffer, int length)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
NSString *str;
if (c->editbox) {
str = [c->editbox stringValue];
} else {
assert(c->combobox);
str = [c->combobox stringValue];
}
if (!str)
str = @"";
/* The length parameter to this method doesn't include a trailing NUL */
[str getCString:buffer maxLength:length-1];
}
void dlg_listbox_clear(union control *ctrl, void *dv)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
if (c->tableview) {
[[c->tableview dataSource] clear];
[c->tableview reloadData];
} else {
[c->popupbutton removeAllItems];
}
}
void dlg_listbox_del(union control *ctrl, void *dv, int index)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
if (c->tableview) {
[[c->tableview dataSource] removestr:index];
[c->tableview reloadData];
} else {
[c->popupbutton removeItemAtIndex:index];
}
}
void dlg_listbox_addwithid(union control *ctrl, void *dv,
char const *text, int id)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
if (c->tableview) {
[[c->tableview dataSource] add:text withId:id];
[c->tableview reloadData];
} else {
[c->popupbutton addItemWithTitle:[NSString stringWithCString:text]];
[[c->popupbutton lastItem] setTag:id];
}
}
void dlg_listbox_add(union control *ctrl, void *dv, char const *text)
{
dlg_listbox_addwithid(ctrl, dv, text, -1);
}
int dlg_listbox_getid(union control *ctrl, void *dv, int index)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
if (c->tableview) {
return [[c->tableview dataSource] getid:index];
} else {
return [[c->popupbutton itemAtIndex:index] tag];
}
}
int dlg_listbox_index(union control *ctrl, void *dv)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
if (c->tableview) {
return [c->tableview selectedRow];
} else {
return [c->popupbutton indexOfSelectedItem];
}
}
int dlg_listbox_issel(union control *ctrl, void *dv, int index)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
if (c->tableview) {
return [c->tableview isRowSelected:index];
} else {
return [c->popupbutton indexOfSelectedItem] == index;
}
}
void dlg_listbox_select(union control *ctrl, void *dv, int index)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
if (c->tableview) {
[c->tableview selectRow:index byExtendingSelection:NO];
} else {
[c->popupbutton selectItemAtIndex:index];
}
}
void dlg_text_set(union control *ctrl, void *dv, char const *text)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
assert(c->textview);
[c->textview setString:[NSString stringWithCString:text]];
}
void dlg_filesel_set(union control *ctrl, void *dv, Filename fn)
{
/* FIXME */
}
void dlg_filesel_get(union control *ctrl, void *dv, Filename *fn)
{
/* FIXME */
}
void dlg_fontsel_set(union control *ctrl, void *dv, FontSpec fn)
{
/* FIXME */
}
void dlg_fontsel_get(union control *ctrl, void *dv, FontSpec *fn)
{
/* FIXME */
}
void dlg_update_start(union control *ctrl, void *dv)
{
/* FIXME */
}
void dlg_update_done(union control *ctrl, void *dv)
{
/* FIXME */
}
void dlg_set_focus(union control *ctrl, void *dv)
{
/* FIXME */
}
union control *dlg_last_focused(union control *ctrl, void *dv)
{
return NULL; /* FIXME */
}
void dlg_beep(void *dv)
{
NSBeep();
}
void dlg_error_msg(void *dv, char *msg)
{
/* FIXME */
}
void dlg_end(void *dv, int value)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
[d->target performSelector:d->action
withObject:[NSNumber numberWithInt:value]];
}
void dlg_coloursel_start(union control *ctrl, void *dv,
int r, int g, int b)
{
/* FIXME */
}
int dlg_coloursel_results(union control *ctrl, void *dv,
int *r, int *g, int *b)
{
return 0; /* FIXME */
}
void dlg_refresh(union control *ctrl, void *dv)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
struct fe_ctrl *c;
if (ctrl) {
if (ctrl->generic.handler != NULL)
ctrl->generic.handler(ctrl, d, d->data, EVENT_REFRESH);
} else {
int i;
for (i = 0; (c = index234(d->byctrl, i)) != NULL; i++) {
assert(c->ctrl != NULL);
if (c->ctrl->generic.handler != NULL)
c->ctrl->generic.handler(c->ctrl, d,
d->data, EVENT_REFRESH);
}
}
}
void *dlg_get_privdata(union control *ctrl, void *dv)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
return c->privdata;
}
void dlg_set_privdata(union control *ctrl, void *dv, void *ptr)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
c->privdata = ptr;
c->privdata_needs_free = FALSE;
}
void *dlg_alloc_privdata(union control *ctrl, void *dv, size_t size)
{
struct fe_dlg *d = (struct fe_dlg *)dv;
struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
/*
* This is an internal allocation routine, so it's allowed to
* use smalloc directly.
*/
c->privdata = smalloc(size);
c->privdata_needs_free = TRUE;
return c->privdata;
}