#import "Options.h"
#import "DiagnosticsPanel.h"

#import "Controller.h"

@implementation Option

NSArray *optClasses = nil;

+ optEnumerator
{
    if(optClasses==nil){
        optClasses =
            [NSArray arrayWithObjects:[OrientationOpt class],
                     [BorderOpt class],
                     [TOCOpt class],
                     [DeviceOpt class],
                     [DuplexOpt class],
                     [PredefVirtOpt class],
                     [MediaOpt class],
                     [StyleSheetOpt class],
                     [EncodingOpt class],
                     [PrologueOpt class],
                     [VirtualsOpt class],
                     [TitleOpt class],
                     [FooterOpt class],
                     [FontSizeOpt class],
                     [PagesOpt class],
                     [MarginOpt class],
                     [JobTitleOpt class],
                     [CharsPerLineOpt class],
                     [LinesPerPageOpt class],
                     [HeaderOpt class],
                     nil];
        [optClasses retain];
    }

    return [optClasses objectEnumerator];
}

- initWithController:(id)theCon;
{
    con     = theCon;
    enabled = NO;
    control = nil;
    errMsg  = nil;
    return self;
}

- setOptEnabled:(BOOL)eflag
{
    if(eflag==enabled){
        return self;
    }

    enabled = eflag;

    [control setEnabled:enabled];
    [control setNeedsDisplay:YES];

    return self;
}


- (BOOL)optEnabled
{
    return enabled;
}

- (BOOL)usedByA2PS
{
    return YES;
}

- (BOOL)usedByGS
{
    return NO;
}

- (NSArray *)optionsForA2PS
{
    [self subclassResponsibility:_cmd];
    return nil;
}

- (NSArray *)optionsForGS
{
    [self subclassResponsibility:_cmd];
    return nil;
}

- control
{
    return control;
}

- (NSString *)title
{
    [self subclassResponsibility:_cmd];
    return nil;
}

- (NSString *)errMsg
{
    return errMsg;
}

- cleanupOption
{
    [control release];
    return self;
}

- (NSMutableDictionary *)dataDict:(NSDictionary *)src
{
    if(src==nil){
        return [NSMutableDictionary dictionary];
    }

    return [NSMutableDictionary dictionaryWithDictionary:src];
}

#define ENKEY @"Enabled"

- (NSUserDefaults *)writeUserDefaults
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSMutableDictionary *ent = 
        [self dataDict:[defaults dictionaryForKey:[self title]]];

    [ent setObject:(enabled==YES ? @"Yes" : @"No") forKey:ENKEY];

    [defaults setObject:ent forKey:[self title]];
    
    return defaults;
}

- (NSUserDefaults *)readUserDefaults
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSDictionary *ent = [defaults dictionaryForKey:[self title]];
    
    if(ent!=nil){
        NSString *enstr = [ent objectForKey:ENKEY];
        if(enstr!=nil && [enstr isEqualToString:@"Yes"]==YES){
            [self setOptEnabled:YES];
        }
        else{
            [self setOptEnabled:NO];
        }
    }

    return defaults;
}


@end

@implementation RadioOpt

- initWithController:(id)theCon
{
    NSRect cframe = {{0, 0}, {50, 50}};
    id cell;
    NSButtonCell *proto;
    NSArray *titles = [self titles];
    NSEnumerator *titleEnum;
    NSString *title;
    int row;

    [super initWithController:theCon];

    proto = [[NSButtonCell alloc] initImageCell:nil];
    [proto setButtonType:NSRadioButton];
    [proto setBordered:NO];
    [proto setFont:[NSFont labelFontOfSize:
                               [[proto font] pointSize]+2]];
    
    control = [[NSMatrix alloc] initWithFrame:cframe
                                mode:NSRadioModeMatrix
                                prototype:proto
                                numberOfRows:[titles count]
                                numberOfColumns:1];
    [control setIntercellSpacing:NSMakeSize(4, 4)];
    
    titleEnum = [titles objectEnumerator];
    row = 0;
    while((title=[titleEnum nextObject])!=nil){
        cell = [control cellAtRow:row column:0];
        [cell setTitle:title];
        [cell setTag:row++];
        [cell setEnabled:NO];
    }

    [control sizeToFit];
    [control setAutoresizingMask:NSViewNotSizable];

    [control retain];

    return self;
}

- (NSArray *)titles
{
    [self subclassResponsibility:_cmd];
    return nil;
}


- setOptEnabled:(BOOL)eflag
{
    int row, max = [control numberOfRows];

    if(eflag==enabled){
        return self;
    }

    enabled = eflag;

    for(row=0; row<max; row++){
        [[control cellAtRow:row column:0]
            setEnabled:eflag];
    }

    [control display];
    return self;
}

#define SELKEY @"Radio"

- (NSUserDefaults *)writeUserDefaults
{
    NSUserDefaults *defaults = [super writeUserDefaults];
    NSMutableDictionary *ent = 
        [self dataDict:[defaults dictionaryForKey:[self title]]];

    NSCell *cell = [control selectedCell];
    if(cell!=nil){
        [ent setObject:[cell title] forKey:SELKEY];
    }

    [defaults setObject:ent forKey:[self title]];

    return defaults;
}

- (NSUserDefaults *)readUserDefaults
{
    NSUserDefaults *defaults = [super readUserDefaults];
    NSDictionary *ent = [defaults dictionaryForKey:[self title]];

    if(ent!=nil){
        NSString *selstr = [ent objectForKey:SELKEY];

        if(selstr!=nil){
            int row = 0, mx = [[self titles] count];
            while(row<mx){
                NSCell *cell = [control cellAtRow:row column:0];
                if([[cell title] isEqualToString:selstr]){
                    [control selectCell:cell];
                    break;
                }
                
                row++;
            }
            
            if(row==mx){
                NSLog(@"warning: setting %@ of %@ unknown",
                      selstr, [self title]);
            }
        }
    }

    return defaults;
}

@end

typedef enum {
    OPortrait = 0,
    OLandscape
} Orientation;


@implementation OrientationOpt

- initWithController:(id)theCon
{
    [super initWithController:theCon];
    [self setOptEnabled:YES];
    return self;
}


- (NSArray *)titles
{
    return [NSArray arrayWithObjects:@"Portrait", 
                    @"Landscape", nil];
}

- (NSArray *)optionsForA2PS
{
    Orientation current =
        [[control selectedCell] tag];
    return 
        [NSArray arrayWithObject:
                     (current==OPortrait ?
                      @"--portrait" : @"--landscape")];
}

- (NSString *)title
{
    return @"Orientation";
}

@end

typedef enum {
    OBorderOn = 0,
    OBorderOff
} Border;

@implementation BorderOpt

- (NSArray *)titles
{
    return [NSArray arrayWithObjects:@"borders on", 
                    @"borders off", nil];
}

- (NSArray *)optionsForA2PS
{
    Orientation current =
        [[control selectedCell] tag];
    return 
        [NSArray arrayWithObject:
                     (current==OBorderOn ?
                      @"--borders=yes" : @"--borders=no")];
}

- (NSString *)title
{
    return @"Borders";
}

@end


@implementation DuplexOpt

- (NSArray *)titles
{
    return [NSArray arrayWithObjects:@"simplex",
                    @"duplex", @"tumble", nil];
}

- (NSArray *)optionsForA2PS
{
    NSString *fullOpt, *choice = 
        [[control selectedCell] title];

    fullOpt = [NSString stringWithFormat:@"--sides=%@", choice];
    return [NSArray arrayWithObject:fullOpt];
}

- (NSString *)title
{
    return @"Duplex";
}

@end

@implementation DeviceOpt

- (BOOL)usedByA2PS
{
    return NO;
}

- (BOOL)usedByGS
{
    return YES;
}

- (NSArray *)titles
{
    return [NSArray arrayWithObjects:@"TIFF G3 (monochrome)",
                    @"TIFF G4 (monochrome)",
                    @"TIFF 24 bit color",
                    @"PNG ALPHA",
                    nil];
}

- (NSArray *)optionsForGS
{
    int current =
        [[control selectedCell] tag];
    NSString *opts[] = { 
        @"tiffg3", @"tiffg4", @"tiff24nc", @"pngalpha",
        nil
    }, *fullOpt;

    fullOpt = [NSString stringWithFormat:@"-sDEVICE=%@",
                        opts[current]];

    return [NSArray arrayWithObject:fullOpt];

}

- (NSString *)title
{
    return @"PostScript Device";
}

@end


@implementation PredefVirtOpt

- initWithController:(id)theCon
{
    NSRect cframe = {{0, 0}, {50, 50}};
    id cell;
    NSButtonCell *proto;
    int row, col, value;

    [super initWithController:theCon];

    proto = [[NSButtonCell alloc] initImageCell:nil];
    [proto setButtonType:NSRadioButton];
    [proto setBordered:NO];
    [proto setFont:[NSFont labelFontOfSize:
                               [[proto font] pointSize]+2]];
    
    control = [[NSMatrix alloc] initWithFrame:cframe
                                mode:NSRadioModeMatrix
                                prototype:proto
                                numberOfRows:3
                                numberOfColumns:3];
    
    for(row=0, value=1; row<3; row++){
        for(col=0; col<3; col++, value++){
            cell = [control cellAtRow:row column:col];
            [cell 
                setTitle:
                    [NSString stringWithFormat:@"%d", value]];
            [cell setTag:value];
            [cell setEnabled:NO];
        }
    }

    [control sizeToFit];

    [control retain];

    return self;
}

- setOptEnabled:(BOOL)eflag
{
    int row, col;
    id cell;

    if(eflag==enabled){
        return self;
    }

    enabled = eflag;

    for(row=0; row<3; row++){
        for(col=0; col<3; col++){
            [[control cellAtRow:row column:col] 
                setEnabled:eflag];
        }
    }

    [control setNeedsDisplay:YES];
    return self;
}

- (NSArray *)optionsForA2PS
{
    int current =
        [[control selectedCell] tag];
    return 
        [NSArray arrayWithObject:
                     [NSString stringWithFormat:@"-%d", current]];
}

- (NSString *)title
{
    return @"Predefined Virtuals";
}

#define PDVKEY @"Virtual"

- (NSUserDefaults *)writeUserDefaults
{
    NSUserDefaults *defaults = [super writeUserDefaults];
    NSMutableDictionary *ent = 
        [self dataDict:[defaults dictionaryForKey:[self title]]];

    NSCell *cell = [control selectedCell];
    if(cell!=nil){
        NSString *pdvstr =
            [NSString stringWithFormat:@"%d", [cell tag]];
        [ent setObject:pdvstr forKey:PDVKEY];
    }

    [defaults setObject:ent forKey:[self title]];

    return defaults;
}

- (NSUserDefaults *)readUserDefaults
{
    NSUserDefaults *defaults = [super readUserDefaults];
    NSDictionary *ent = [defaults dictionaryForKey:[self title]];

    if(ent!=nil){
        NSString *pdvstr = [ent objectForKey:PDVKEY];

        if(pdvstr!=nil){
            int vtag;
            [[NSScanner scannerWithString:pdvstr]
                scanInt:&vtag];

            if([control selectCellWithTag:vtag]==NO){
                NSLog(@"warning: tag %d not present in %@",
                      vtag, [self title]);
            }
        }
    }

    return defaults;
}

@end

@implementation OptFromA2PS : Option

#define BRWIDTH  125
#define BRHEIGHT 150

- initWithController:(id)theCon
{
    NSRect browseRect = {{0, 0}, {BRWIDTH, BRHEIGHT}};

    [super initWithController:theCon];

    control = [[NSBrowser alloc] initWithFrame:browseRect];

    [control setDelegate:self];
    [control setAllowsMultipleSelection:NO];
    [control setTitled:NO];
    [control setHasHorizontalScroller:NO];

    [control setTarget:self];
    [control setDoubleAction:@selector(doLookup:)];

    [control retain];

    entries = nil;

    return self;
}

- setOptEnabled:(BOOL)eflag
{
    NSEnumerator *cellEnum;
    id cell;

    if(eflag==enabled){
        return self;
    }

    enabled = eflag;

    cellEnum = [[[control matrixInColumn:0] cells] 
                   objectEnumerator];
    while((cell = [cellEnum nextObject])!=nil){
        [cell setEnabled:eflag];
    }
    [control setNeedsDisplay:YES];

    return self;
}

- (NSString *)titleForLookup
{
    [self subclassResponsibility:_cmd];
    return nil;
}

#define MAXLEN 100

- doLookup:(id)sender
{
    NSString *key = [[control selectedCell] stringValue], *title,
        *value = [entries objectForKey:key];
    const char *str = [value cString];
    int newlines = 0;

    while(*str){
        if(*str++=='\n'){
            newlines++;
        }
    }

    title = [NSString stringWithFormat:[self titleForLookup], key];

    if(newlines<2){
        NSRunAlertPanel(title, value, @"Ok", NULL, NULL);
    }
    else{
        ShowDiagnostics(title, value);
    }

    return self;
}

- (void)browser:(NSBrowser *)sender createRowsForColumn:(int)column
       inMatrix:(NSMatrix *)matrix
{
    NSEnumerator *keyEn = 
        [[[entries allKeys]
             sortedArrayUsingSelector:@selector(compare:)]
            objectEnumerator];
    id key, cell;
    int index = 0;

    [matrix addColumn];
    while((key = [keyEn nextObject])!=nil){
        if(index){
            [matrix addRow];
        }

        cell = [matrix cellAtRow:index column:0];
        [cell setStringValue:key];
        [cell setLeaf:YES];
        [cell setEnabled:NO];

        index++;
    }
}

- (NSString *)key
{
    return [[control selectedCell] stringValue];
}

#define MINLEN 4

- (BOOL)isDivider:(NSString *)line
{
    const char *str = [line cString];

    if([line length]<MINLEN){
        return NO;
    }

    while(*str){
        if(*str++!='-'){
            return NO;
        }
    }

    return YES;
}

- (NSMutableArray *)dividers:(NSArray *)lines
{
    int max = [lines count], current;
    NSMutableArray *result =
        [NSMutableArray arrayWithCapacity:1];
    
    for(current=0; current<max; current++){
        if([self isDivider:[lines objectAtIndex:current]]==YES){
            [result addObject:[NSNumber numberWithInt:current]];
        }
    }

    return result;
}

- cleanupOption
{
    if(entries!=nil){
        [entries release];
    }
    return [super cleanupOption];
}

#define A2PSKEY @"Browser"

- (NSUserDefaults *)writeUserDefaults
{
    NSUserDefaults *defaults = [super writeUserDefaults];
    NSMutableDictionary *ent = 
        [self dataDict:[defaults dictionaryForKey:[self title]]];

    NSCell *cell = [control selectedCell];
    if(cell!=nil){
        [ent setObject:[cell stringValue] forKey:A2PSKEY];
    }

    [defaults setObject:ent forKey:[self title]];

    return defaults;
}

- (NSUserDefaults *)readUserDefaults
{
    NSUserDefaults *defaults = [super readUserDefaults];
    NSDictionary *ent = [defaults dictionaryForKey:[self title]];

    if(ent!=nil){
        NSString *a2psstr = [ent objectForKey:A2PSKEY];

        if(a2psstr!=nil){
            NSEnumerator 
                *cellEnum = 
                [[[control matrixInColumn:0] cells] 
                    objectEnumerator];
            NSCell *cell;
            int row = 0;
            
            while((cell = [cellEnum nextObject])!=nil){
                if([[cell stringValue] isEqualToString:a2psstr]){
                    [control selectRow:row inColumn:0];
                    break;
                }

                row++;
            }
            
            if(row==[entries count]){
                NSLog(@"warning: setting %@ of %@ unknown",
                      a2psstr, [self title]);
            }
        }
    }

    return defaults;
}

@end

@implementation MediaOpt

- initWithController:(id)theCon
{
    NSArray *args = [NSArray arrayWithObject:@"--list=media"];
    NSEnumerator *lines;
    NSString *line, *fmt, *prefix;

    [super initWithController:theCon];    
    lines = [[theCon runCommand:[theCon a2psStr] arguments:args]
                objectEnumerator];
                                                                
    line = [lines nextObject]; 
    prefix = @"Known Media";
    if(line==nil || [line hasPrefix:prefix]==NO){
        fmt = @"expected\n\"%@\"\ngot\n\"%@\"";
        errMsg = [NSString stringWithFormat:fmt, prefix, line];
        return nil;
    }

    line = [lines nextObject]; 
    prefix = @"  Name      \t dimensions ( llx,  lly,  urx,  ury)";
    if(line==nil || [line hasPrefix:prefix]==NO){
        fmt = @"expected\n\"%@\"\ngot\n\"%@\"";
        errMsg = [NSString stringWithFormat:fmt, prefix, line];
        return nil;
    }

    entries = [NSMutableDictionary dictionaryWithCapacity:1];

    while((line = [lines nextObject])!=nil){
        NSString *key, *value;
        int len = [line length];
        const char *str = [line cString], *prev, *cur;
        if(len<3){
            break;
        }
        prev = cur = str+2;
        while(*cur && !isspace(*cur)){
            cur++;
        }
        if(!(*cur)){
            fmt = @"media info truncated, got\n%@";
            errMsg = [NSString stringWithFormat:fmt, line];
            return nil;
        }
        key = [NSString stringWithCString:prev length:(cur-prev)];
        while(*cur && isspace(*cur)){
            cur++;
        }
        if(!(*cur)){
            fmt = @"media dimensions truncated, got\n%@";
            errMsg = [NSString stringWithFormat:fmt, line];
            return nil;
        }
        value = [NSString stringWithCString:cur length:(len-(cur-str))];
        [entries setObject:value forKey:key];
    }

    if(![entries count]){
        errMsg = @"no media found";
        return nil;
    }
    [entries retain];

    [control loadColumnZero];
    [control selectRow:0 inColumn:0];

    return self;
}

- (NSString *)titleForLookup
{
    return @"Dimensions of medium %@";
}

- (BOOL)usedByGS
{
    return YES;
}

- (NSArray *)optionsForA2PS
{
    NSString *key, *optStr;

    key = [[control selectedCell] stringValue];
    optStr = [NSString stringWithFormat:@"--medium=%@", key];

    return [NSArray arrayWithObject:optStr];
}

- (NSArray *)optionsForGS
{
    NSString *key, *optStr;

    key = [[control selectedCell] stringValue];
    optStr = [NSString stringWithFormat:@"-sPAPERSIZE=%@", key];

    return [NSArray arrayWithObjects:optStr, @"-dFIXEDMEDIA", nil];
}

- (NSSize)mediumSize
{
    NSString *dims = 
        [entries objectForKey:[[control selectedCell] stringValue]];
    int width, height;

    sscanf([dims cString], "%d x %d", &width, &height);
    return NSMakeSize(width, height);
}

- (NSString *)title
{
    return @"Media";
}

@end

NSString *StringFromInterval(NSArray *src, int min, int max)
{
    NSString *result = @"";
    int current;

    for(current=min; current<=max; current++){
        result = [result stringByAppendingFormat:@"%@\n",
                         [src objectAtIndex:current]];
    }

    return result;
}


@implementation StyleSheetOpt

- initWithController:(id)theCon
{
    NSArray *args = [NSArray arrayWithObject:@"--list=style-sheets"];

    NSArray *lines;
    NSEnumerator *lineEn;
    NSMutableArray *divs;
    int lineCount, d, dc;
    
    NSString *line, *fmt, *prefix;

    [super initWithController:theCon];    
    lines = [theCon runCommand:[theCon a2psStr] arguments:args];
    lineEn = [lines objectEnumerator];

    line = [lineEn nextObject];
    prefix = @"                               Known Style Sheets";
    if(line==nil || [line hasPrefix:prefix]==NO){
        fmt = @"expected\n\"%@\"\ngot\n\"%@\"";
        errMsg = [NSString stringWithFormat:fmt, prefix, line];
        return nil;
    }

    line = [lineEn nextObject]; 
    prefix = @"                               ==================";
    if(line==nil || [line hasPrefix:prefix]==NO){
        fmt = @"expected\n\"%@\"\ngot\n\"%@\"";
        errMsg = [NSString stringWithFormat:fmt, prefix, line];
        return nil;
    }

    line = [lineEn nextObject];
    if(line==nil || [line length]){
        fmt = @"expected blank line\ngot\n\"%@\"";
        errMsg = [NSString stringWithFormat:fmt, line];
        return nil;
    }

    entries = [NSMutableDictionary dictionaryWithCapacity:1];

    divs = [self dividers:lines]; dc = [divs count];
    for(d=0; d<dc; d++){
        NSString *key, *value;
        const char *title;
        int d1, d2, len;

        d1 = [[divs objectAtIndex:d] intValue];
        d2 = (d==dc-1 ? [lines count]+1 :
              [[divs objectAtIndex:d+1] intValue]);

        line = [lines objectAtIndex:d1-1];
        title = [line cString];
        while(*title && *title++!='(');
        if(*title){
            len = 0;
            while(*title && *title++!='.'){
                len++;
            }
            if(len){
                key = [NSString stringWithCString:title-len-1 length:len];
                value = StringFromInterval(lines, d1-1, d2-2);
                [entries setObject:value forKey:key];
            }
        }
    }

    if(![entries count]){
        errMsg = @"no style sheets found";
        return nil;
    }
    [entries retain];

    [control loadColumnZero];
    [control selectRow:0 inColumn:0];

    return self;
}

- (NSString *)titleForLookup
{
    return @"Description of style sheet \"%@\"";
}

- (NSArray *)optionsForA2PS
{
    NSString *key, *optStr;

    key = [[control selectedCell] stringValue];
    optStr = [NSString stringWithFormat:@"--pretty-print=%@", key];

    return [NSArray arrayWithObject:optStr];
}

- (NSString *)title
{
    return @"Style sheets";
}

@end

@implementation EncodingOpt

- initWithController:(id)theCon
{
    NSArray *args = [NSArray arrayWithObject:@"--list=encodings"];

    NSArray *lines;
    NSEnumerator *lineEn;
    NSMutableArray *divs;
    int lineCount, d, dc;
    
    NSString *line, *fmt;

    [super initWithController:theCon];    
    lines = [theCon runCommand:[theCon a2psStr] arguments:args];
    lineEn = [lines objectEnumerator];

    line = [lineEn nextObject];
    if(line==nil || [line isEqual:@"Known Encodings"]==NO){
        fmt = @"expected \"Known Encodings\"\ngot\n\"%@\"";
        errMsg = [NSString stringWithFormat:fmt, line];
        return nil;
    }

    entries = [NSMutableDictionary dictionaryWithCapacity:1];

    divs = [self dividers:lines]; dc = [divs count];
    for(d=0; d<dc; d++){
        NSString *key, *value;
        const char *title;
        int d1, d2, len;

        d1 = [[divs objectAtIndex:d] intValue];
        d2 = (d==dc-1 ? [lines count]+1 :
              [[divs objectAtIndex:d+1] intValue]);

        line = [lines objectAtIndex:d1-1];
        title = [line cString];
        while(*title && *title++!='(');
        if(*title){
            len = 0;
            while(*title && *title++!=')'){
                len++;
            }
            if(len){
                key = [NSString stringWithCString:title-len-1 length:len];
                value = StringFromInterval(lines, d1-1, d2-2);
                [entries setObject:value forKey:key];
            }
        }
    }

    if(![entries count]){
        errMsg = @"no encodings found";
        return nil;
    }
    [entries retain];

    [control loadColumnZero];
    [control selectRow:0 inColumn:0];

    return self;
}

- (NSString *)titleForLookup
{
    return @"Description of encoding \"%@\"";
}

- (NSArray *)optionsForA2PS
{
    NSString *key, *optStr;

    key = [[control selectedCell] stringValue];
    optStr = [NSString stringWithFormat:@"--encoding=%@", key];

    return [NSArray arrayWithObject:optStr];
}

- (NSString *)title
{
    return @"Encodings";
}

@end

@implementation PrologueOpt

#define PRO_PREFIX "Prologue \""


- initWithController:(id)theCon
{
    NSArray *args = [NSArray arrayWithObject:@"--list=prologues"];

    NSArray *lines;
    NSEnumerator *lineEn;
    NSMutableArray *divs;
    int lineCount, d, dc, plen = strlen(PRO_PREFIX);
    
    NSString *line, *fmt;

    [super initWithController:theCon];    
    lines = [theCon runCommand:[theCon a2psStr] arguments:args];
    lineEn = [lines objectEnumerator];

    line = [lineEn nextObject];
    if(line==nil || [line isEqual:@"Known Prologues"]==NO){
        fmt = @"expected \"Known Prologues\"\ngot\n\"%@\"";
        errMsg = [NSString stringWithFormat:fmt, line];
        return nil;
    }

    entries = [NSMutableDictionary dictionaryWithCapacity:1];

    divs = [self dividers:lines]; dc = [divs count];
    for(d=0; d<dc; d++){
        NSString *key, *value;
        const char *title;
        int d1, d2, len;

        d1 = [[divs objectAtIndex:d] intValue];
        d2 = (d==dc-1 ? [lines count] :
              [[divs objectAtIndex:d+1] intValue]);

        line = [lines objectAtIndex:d1];
        title = [line cString];
        len = strlen(title);

        key = [NSString stringWithCString:title+plen 
                        length:len-plen-2];
        value = StringFromInterval(lines, d1, d2-1);
        [entries setObject:value forKey:key];
    }

    if(![entries count]){
        errMsg = @"no prologues found";
        return nil;
    }
    [entries retain];

    [control loadColumnZero];
    [control selectRow:0 inColumn:0];

    return self;
}

- (NSString *)titleForLookup
{
    return @"Description of prologue \"%@\"";
}

- (NSArray *)optionsForA2PS
{
    NSString *key, *optStr;

    key = [[control selectedCell] stringValue];
    optStr = [NSString stringWithFormat:@"--prologue=%@", key];

    return [NSArray arrayWithObject:optStr];
}

- (NSString *)title
{
    return @"Prologues";
}

- (BOOL)isDivider:(NSString *)line
{
    const char *str = [line cString];
    int cur, plen, len = [line length];
    
    plen = strlen(PRO_PREFIX);
    if(strncmp(str, PRO_PREFIX, plen)){
        return NO;
    }
        
    if(str[len-1]!=':' || str[len-2]!='"'){
        return NO;
    }
     
    for(cur=plen; cur<len-2; cur++){
        if(!(isalnum(str[cur]) || 
             str[cur]=='-' || str[cur]=='_')){
            return NO;
        }
    }

    return YES;
}

@end

#define ENTRYCHARS 8

@implementation FormOpt

- initWithController:(id)theCon
{
    NSRect cframe = {{0, 0}, {50, 50}};
    id cell;
    NSArray *entries = [self entries];
    NSEnumerator *entryEnum;
    NSString *entry;
    int width = 0, height = 0;

    [super initWithController:theCon];
    
    control = [[NSForm alloc] initWithFrame:cframe];
    
    entryEnum = [entries objectEnumerator];
    while((entry=[entryEnum nextObject])!=nil){
        cell = [control addEntry:entry];
        [cell setEnabled:NO];
        if(!width){
            NSSize size = [cell cellSize];
            width  = (size.width-[cell titleWidth])*ENTRYCHARS;
            height = size.height;
        }
    }

    [control setCellSize:NSMakeSize(width, height)];
    [control setAutoresizingMask:NSViewNotSizable];

    [control retain];

    return self;
}

- (NSArray *)entries
{
    [self subclassResponsibility:_cmd];
    return nil;
}


- setOptEnabled:(BOOL)eflag
{
    int row, max = [control numberOfRows];

    if(eflag==enabled){
        return self;
    }

    enabled = eflag;

    for(row=0; row<max; row++){
        [[control cellAtIndex:row] setEnabled:eflag];
    }

    [control display];
    return self;
}

#define FORMKEY @"Form"

- (NSUserDefaults *)writeUserDefaults
{
    NSUserDefaults *defaults = [super writeUserDefaults];
    NSMutableDictionary *ent = 
        [self dataDict:[defaults dictionaryForKey:[self title]]];

    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    int count = [[self entries] count], row;

    for(row=0; row<count; row++){
        NSCell *cell = [control cellAtIndex:row];
        [dict setObject:[cell stringValue] forKey:[cell title]];
    }

    [ent setObject:dict forKey:FORMKEY];

    [defaults setObject:ent forKey:[self title]];

    return defaults;
}

- (NSUserDefaults *)readUserDefaults
{
    NSUserDefaults *defaults = [super readUserDefaults];
    NSDictionary *ent = [defaults dictionaryForKey:[self title]];

    if(ent!=nil){
        NSDictionary *dict = [ent objectForKey:FORMKEY];

        if(dict!=nil){
            int count = [[self entries] count], row, set = 0;

            for(row=0; row<count; row++){
                NSCell *cell = [control cellAtIndex:row];
                NSString *str = [dict objectForKey:[cell title]];

                if(str!=nil){
                    [cell setStringValue:str];
                    set++;
                }
            }

            
            if(set<count){
                NSLog(@"warning: only %d keys out of %d for %@",
                      set, count, [self title]);
            }
        }
    }

    return defaults;
}

@end


@implementation VirtualsOpt


- (NSArray *)entries
{
    return [NSArray arrayWithObjects:@"Rows", 
                    @"Columns", nil];
}

- (NSArray *)optionsForA2PS
{
    int rows = [[control cellAtIndex:0] intValue],
        cols = [[control cellAtIndex:1] intValue];
    NSString 
        *rstr = [NSString stringWithFormat:@"--rows=%d", rows],
        *cstr = [NSString stringWithFormat:@"--columns=%d", cols];

    return 
        [NSArray arrayWithObjects:rstr, cstr, nil];
}

- (NSString *)title
{
    return @"Virtuals";
}

@end

@implementation TitleOpt


- (NSArray *)entries
{
    return [NSArray arrayWithObjects:@"Left", @"Center", 
                    @"Right", nil];
}

- (NSArray *)optionsForA2PS
{
    NSString 
        *ltitle = [[control cellAtIndex:0] stringValue],
        *ctitle = [[control cellAtIndex:1] stringValue],
        *rtitle = [[control cellAtIndex:2] stringValue];

    NSString 
        *lstr = [NSString stringWithFormat:@"--left-title=%@", ltitle],
        *cstr = [NSString stringWithFormat:@"--center-title=%@", ctitle],
        *rstr = [NSString stringWithFormat:@"--right-title=%@", rtitle];

    return 
        [NSArray arrayWithObjects:lstr, cstr, rstr, nil];
}

- (NSString *)title
{
    return @"Title";
}

@end

@implementation FooterOpt


- (NSArray *)entries
{
    return [NSArray arrayWithObjects:@"Left", @"Center", 
                    @"Right", nil];
}

- (NSArray *)optionsForA2PS
{
    NSString 
        *lftr = [[control cellAtIndex:0] stringValue],
        *cftr = [[control cellAtIndex:1] stringValue],
        *rftr = [[control cellAtIndex:2] stringValue];

    NSString 
        *lstr = [NSString stringWithFormat:@"--left-footer=%@", lftr],
        *cstr = [NSString stringWithFormat:@"--footer=%@", cftr],
        *rstr = [NSString stringWithFormat:@"--right-footer=%@", rftr];

    return 
        [NSArray arrayWithObjects:lstr, cstr, rstr, nil];
}

- (NSString *)title
{
    return @"Footer";
}

@end

@implementation FontSizeOpt


- (NSArray *)entries
{
    return [NSArray arrayWithObjects:@"Font size", nil];
}

- (NSArray *)optionsForA2PS
{
    int size = [[control cellAtIndex:0] intValue];
    NSString 
        *fstr = [NSString stringWithFormat:@"--font-size=%d", size];

    return 
        [NSArray arrayWithObjects:fstr, nil];
}

- (NSString *)title
{
    return @"Font size";
}

@end

@implementation PagesOpt


- (NSArray *)entries
{
    return [NSArray arrayWithObjects:@"Pages", nil];
}

- (NSArray *)optionsForA2PS
{
    NSString *pgs = [[control cellAtIndex:0] stringValue];
    NSString 
        *pstr = [NSString stringWithFormat:@"--pages=%@", pgs];

    return 
        [NSArray arrayWithObjects:pstr, nil];
}

- (NSString *)title
{
    return @"Pages";
}

@end

@implementation MarginOpt


- (NSArray *)entries
{
    return [NSArray arrayWithObjects:@"Margin", nil];
}

- (NSArray *)optionsForA2PS
{
    NSString *mstr, *margin = [[control cellAtIndex:0] stringValue];

    if([margin length]){
        mstr = [NSString stringWithFormat:@"--margin=%@", margin];
    }
    else{
        mstr = @"--margin";
    }
    return 
        [NSArray arrayWithObjects:mstr, nil];
}

- (NSString *)title
{
    return @"Margin";
}

@end

@implementation TOCOpt


- (NSArray *)entries
{
    return [NSArray arrayWithObjects:@"TOC", nil];
}

- (NSArray *)optionsForA2PS
{
    NSString *toc = [[control cellAtIndex:0] stringValue];
    NSString *tstr;

    if([toc length]){
        tstr = [NSString stringWithFormat:@"--toc=%@", toc];
    }
    else{
        tstr = @"--toc";
    }

    return 
        [NSArray arrayWithObjects:tstr, nil];
}

- (NSString *)title
{
    return @"Table of contents";
}

@end

@implementation JobTitleOpt


- (NSArray *)entries
{
    return [NSArray arrayWithObjects:@"Job title", nil];
}

- (NSArray *)optionsForA2PS
{
    NSString *jt = [[control cellAtIndex:0] stringValue];
    NSString 
        *jstr = [NSString stringWithFormat:@"--title=%@", jt];

    return 
        [NSArray arrayWithObjects:jstr, nil];
}

- (NSString *)title
{
    return @"Job title";
}

@end


@implementation LinesPerPageOpt


- (NSArray *)entries
{
    return [NSArray arrayWithObjects:@"Lines", nil];
}

- (NSArray *)optionsForA2PS
{
    int lines = [[control cellAtIndex:0] intValue];
    NSString 
        *lstr = [NSString stringWithFormat:@"--lines-per-page=%d", lines];

    return 
        [NSArray arrayWithObjects:lstr, nil];
}

- (NSString *)title
{
    return @"Lines per page";
}

@end


@implementation CharsPerLineOpt


- (NSArray *)entries
{
    return [NSArray arrayWithObjects:@"Chars", nil];
}

- (NSArray *)optionsForA2PS
{
    int count = [[control cellAtIndex:0] intValue];
    NSString 
        *cstr = [NSString stringWithFormat:@"--chars-per-line=%d", count];

    return 
        [NSArray arrayWithObjects:cstr, nil];
}

- (NSString *)title
{
    return @"Chars per line";
}

@end

@implementation HeaderOpt


- (NSArray *)entries
{
    return [NSArray arrayWithObjects:@"Header", nil];
}

- (NSArray *)optionsForA2PS
{
    NSString *hdr = [[control cellAtIndex:0] stringValue], *hstr;
    
    if([hdr length]){
        hstr = [NSString stringWithFormat:@"--header=%@", hdr];
    }
    else{
        hstr = @"--no-header";
    }

    return 
        [NSArray arrayWithObjects:hstr, nil];
}

- (NSString *)title
{
    return @"Header";
}

@end

