
#import <time.h>
#import <unistd.h>
#import <fcntl.h>


#import "Preview.h"

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


@implementation PrevImageView

- (id)initWithFrame:(NSRect)frameRect
{
    image = nil;
    return [super initWithFrame:frameRect];
}

- (NSImage *)image
{
    return image;
}

- setImage:(NSImage *)anImage
{
    if(image!=nil){
        [image release];
    }
    image = anImage; 
    if(image!=nil){
        [image retain];

        NSSize newSize = [image size];
        NSRect oldFrame = [self frame],
            newFrame = 
            NSMakeRect(0, 0, newSize.width, newSize.height);

        if(!NSEqualSizes(oldFrame.size, newFrame.size)){
            [self setFrame:newFrame];
        }
    }

    
    [self setNeedsDisplay:YES];
}

- (void)drawRect:(NSRect)aRect
{
    // idea for this method by A. Malmberg
    if(image!=nil){
        NSRectClip(aRect);
        [[[image representations] objectAtIndex:0] draw];
    }
}

- (void)mouseDown:(NSEvent *)theEvent
{
    NSPoint startp = [theEvent locationInWindow], curp;
    startp = [self convertPoint:startp fromView: nil];
    
    NSWindow *win = [self window];
    NSEvent *curEvent = theEvent;

    unsigned int mflags = [theEvent modifierFlags],
        cmask = NSLeftMouseUpMask | NSLeftMouseDraggedMask;

    NSSize fsize = [self frame].size;
    NSRect box, bbox, cachebox;
    BOOL first = YES;

    do {
        float bminx, bmaxx, bminy, bmaxy;

        if(first==NO){
            [win restoreCachedImage];
        }
        first = NO;       

        curp = [curEvent locationInWindow];
        curp = [self convertPoint:curp fromView: nil];
        
        if(startp.x < curp.x){
            bminx = startp.x;
            bmaxx = curp.x;
        }
        else{
            bminx = curp.x;
            bmaxx = startp.x;
        }

        if(startp.y < curp.y){
            bminy = startp.y;
            bmaxy = curp.y;
        }
        else{
            bminy = curp.y;
            bmaxy = startp.y;
        }

        if(bminx<0){
            bminx = 0;
        }
        if(bminy<0){
            bminy = 0;
        }

        if(bmaxx>fsize.width){
            bmaxx = fsize.width;
        }
        if(bmaxy>fsize.height){
            bmaxy = fsize.height;
        }

        box.origin.x = bminx; 
        box.origin.y = bminy;
        box.size.width = bmaxx-bminx; 
        box.size.height = bmaxy-bminy;

        bbox = box;
        bbox.origin.x += 1;
        bbox.origin.y += 1;
        bbox.size.width -= 2;
        bbox.size.height -= 2;

        cachebox = box;
        cachebox.origin.x -= 2;
        cachebox.origin.y -= 2;
        cachebox.size.width += 4;
        cachebox.size.height += 4;        

        cachebox = [self convertRect:cachebox toView:nil];
        [win cacheImageInRect:cachebox];

        [self lockFocus];

        [[NSColor redColor] set];
        [NSBezierPath strokeRect:box];

        [[NSColor greenColor] set];
        [NSBezierPath strokeRect:bbox];

        [self unlockFocus];

        [win flushWindow];

        curEvent = [[self window] nextEventMatchingMask:cmask];
    } while([curEvent type] != NSLeftMouseUp);

    [win restoreCachedImage];
    [win flushWindow];
        
    if(NSIsEmptyRect(box)){
        NSBeep();
        return;
    }

    NSDate *markdate =
        [NSDate dateWithTimeIntervalSinceNow:MARKDELAY];
    int cflag = 0;
    NSColor *cols[2] = {
        [NSColor blueColor],
        [NSColor yellowColor]
    };
    
    [win cacheImageInRect:cachebox];

    while([markdate timeIntervalSinceNow]>0){
        [self lockFocus];

        [cols[cflag] set];
        [NSBezierPath strokeRect:box];

        [cols[1-cflag] set];
        [NSBezierPath strokeRect:bbox];

        [self unlockFocus];

        [win flushWindow];

        NSDate *mpause =
            [NSDate dateWithTimeIntervalSinceNow:MARKPAUSE];
        [NSThread sleepUntilDate:mpause];

        cflag = 1-cflag;
    }

    [win restoreCachedImage];
    [win flushWindow];    

    [self lockFocus];

    NSBitmapImageRep 
        *boxRep =
        [[NSBitmapImageRep alloc]
            initWithFocusedViewRect:box];
    AUTORELEASE(boxRep);
    
    [self unlockFocus];

    NSPasteboard *pb = [NSPasteboard generalPasteboard];
    NSArray *types = [NSArray arrayWithObject:NSTIFFPboardType];

    [pb declareTypes:types owner:self];

    [pb setData:[boxRep TIFFRepresentation]
        forType:NSTIFFPboardType];

    return;
}


@end

@implementation Preview

#define PRWIDTH  (36*17/2)
#define PRHEIGHT (36*11)

#define PRBASEX 200
#define PRBASEY 200

#define RESKEY @"Resolution"


static int count = 0;

- initWithController:(id)theCon
             andPath:(NSString *)path
          resolution:(int)rval
{
    NSRect matRect = {0, 0, 50, 20},
        scrollRect = {0, 0, {PRWIDTH, PRHEIGHT}}, 
        pageRect, winRect;
    unsigned int style = NSTitledWindowMask | NSClosableWindowMask |
        NSMiniaturizableWindowMask | NSResizableWindowMask;
    NSButtonCell *proto;
    NSView *contentView;

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    int resdefault = [defaults integerForKey:RESKEY];

    NSString *resAndPath =
        [NSString stringWithFormat:@"%@ %@", RESKEY, path]; // and not imgPath
    int respath = [defaults integerForKey:resAndPath];

    resolution =
        (rval!=-1 ? rval :
         (respath>0 ? respath :
          (resdefault>0 ? resdefault :
           DEFAULT_RESOLUTION)));

    useContentDimensions = NO;

    pageInv = nil; pageTimer = nil;
    tasks = nil; readPipes = nil; readData = nil;

    proto = [[NSButtonCell alloc] initImageCell:nil];
    [proto setButtonType:NSRadioButton];
    [proto setBordered:NO];
    [proto setTag:0];
    [proto setTitle:@" 1 "];
    [proto setTarget:self];
    [proto setAction:@selector(a2psGoto:)];
    
    pageMatrix = [[NSMatrix alloc] initWithFrame:matRect
                                   mode:NSRadioModeMatrix
                                   prototype:proto
                                   numberOfRows:1
                                   numberOfColumns:1];
    [pageMatrix sizeToFit];

    pageRect.size = [NSScrollView 
                        frameSizeForContentSize:[pageMatrix frame].size
                        hasHorizontalScroller:YES
                        hasVerticalScroller:NO
                        borderType:NSBezelBorder];
    pageRect.size.width = PRWIDTH;
    pageRect.origin.x = 0;
    pageRect.origin.y = PRHEIGHT;

    pageScroller = [[NSScrollView alloc] initWithFrame:pageRect];
    [pageScroller setHasVerticalScroller:NO];
    [pageScroller setHasHorizontalScroller:YES];
    [pageScroller setDocumentView:pageMatrix];
    [pageScroller setAutoresizingMask:
                      NSViewWidthSizable | NSViewMinYMargin];


    winRect = NSMakeRect(PRBASEX+(count%10)*24, PRBASEY-(count%10)*24,
                         PRWIDTH, PRHEIGHT+pageRect.size.height);
    count++;

    [self initWithContentRect:winRect
          styleMask:style
          backing:NSBackingStoreRetained
          defer:NO];
    [self setMinSize:winRect.size];
    [self setReleasedWhenClosed:YES];

    con = theCon;

    imgPath = path; [imgPath retain];
    imgFmt = [path pathExtension]; [imgFmt retain];

    imgDir = nil;

    current = pages = -1;

    imgView  = [[PrevImageView alloc] initWithFrame:scrollRect];
    [imgView setImage:nil];

    scrollView = [[NSScrollView alloc] initWithFrame:scrollRect];
    [scrollView setHasVerticalScroller:YES];
    [scrollView setHasHorizontalScroller:YES];
    [scrollView setDocumentView:imgView];
    [scrollView setScrollsDynamically:NO];
    [scrollView setAutoresizingMask:
                    NSViewWidthSizable | NSViewHeightSizable];
    
    [scrollView setBackgroundColor:[NSColor whiteColor]];

    contentView  = [self contentView];
    [contentView addSubview:pageScroller];
    [contentView addSubview:scrollView];

    [self setTitleWithRepresentedFilename:path]; 

    [self setDelegate:con];

    [self setFrameUsingName:path];
    [self a2psPreview:nil];

    [self display];
    [self makeKeyAndOrderFront:nil];

    return self;
}

- (NSString *)path
{
    return imgPath;
}


- (NSString *)imgFmt
{
    return imgFmt;
}

- setImgFmt:(NSString *)fmt
{
    if(imgFmt!=nil){
        [imgFmt release];
    }

    imgFmt = fmt;
    [imgFmt retain];

    return self;
}


#define WAITSECS 1

- countPages:(NSString *)path done:(BOOL)dflag
{
    NSFileManager *fm = [NSFileManager defaultManager];
    NSString *page;
    int newPages;

    for(newPages = (pages==-1 ? 0 : pages); 
        page = [NSString stringWithFormat:@"%@/%d.%@", 
                         path, newPages+1, imgFmt],
            [fm fileExistsAtPath:page]==YES;
        newPages++){
        if(dflag==NO){
            NSDate *mdate =
                [[fm fileAttributesAtPath:page traverseLink:NO]
                    fileModificationDate];
            if(!([mdate timeIntervalSinceNow]<-WAITSECS)){
                break;
            }
        }
    }

    pages = (newPages>0 ? newPages : -1);
    return self;
}


- (NSString *)gsDimensionArg
{
    if(useContentDimensions==YES){
        NSSize msize = [con mediumSizeFor:self];
        return [NSString stringWithFormat:@"-r%dx%d",
                         (int)(72*csize.width/msize.width), 
                         (int)(72*csize.height/msize.height)];
    }

    return [NSString stringWithFormat:@"-r%dx%d", 
                     resolution, resolution]; 
}

- setResolution:(int)res
{
    if(useContentDimensions==NO && resolution==res){
        return self;
    }

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setInteger:res forKey:RESKEY];

    NSString *resAndPath =
        [NSString stringWithFormat:@"%@ %@", RESKEY, imgPath];
    [defaults setInteger:res forKey:resAndPath];

    resolution = res;
    useContentDimensions = NO;
    [self a2psPreview:nil];

    return self;
}

- useContentDimensions:(id)sender
{
    NSSize size = [[scrollView contentView] frame].size;

    if(useContentDimensions==YES && 
       csize.width==size.width && csize.height==size.height){
        return self;
    }

    useContentDimensions = YES;
    csize = size;
    [self a2psPreview:nil];

    return self;
}

- a2psResolution:(id)sender
{
    return [self setResolution:[sender tag]];
}


- setPage:(int)page
{
    NSString *nextImagePath;
    NSImage *nextImage;

    if(page==current || page<0 || page>=pages){
        return self;
    }

    nextImagePath =  
        [NSString stringWithFormat:@"%@/%d.%@", 
                  imgDir, page+1, imgFmt];
    if((nextImage = [[NSImage alloc]
                        initWithContentsOfFile:nextImagePath])==nil){
        NSRunAlertPanel(@"Error", 
                        @"Couldn't read image:\n%@",
                        @"Ok", NULL, NULL, nextImagePath);
        return self;
    }
    if(![[nextImage representations] count]){
        [nextImage release];
        NSBeep();
        return self;
    }

    nextImage = [self imageLoadCompleteHook:nextImage];

    current = page;
    [imgView setImage:nextImage]; [nextImage release];

    [pageMatrix selectCellAtRow:0 column:page];
    
    [pageMatrix display];
    [scrollView display];

    return self;
}

- (NSImage *)imageLoadCompleteHook:(NSImage *)img
{
    return img;
}


extern int errno;

- tiffSave:(id)sender
{
    NSImage *img = [imgView image];
    if(img==nil){
        NSBeep();
        return self;
    }

    NSArray *reps = [img representations];
    if(![reps count]){
        NSBeep();
        return self;
    }

    NSBitmapImageRep *rep = [reps objectAtIndex:0];

    NSSavePanel *savePanel = [NSSavePanel savePanel];
    [savePanel setRequiredFileType:@"tiff"];

    if([savePanel runModal] == NSOKButton){
        NSString *fname = [savePanel filename];
        NSData *data = [rep TIFFRepresentation];

        if([data writeToFile:fname atomically:YES]==NO){
            NSString *msg 
                = [NSString 
                      stringWithFormat:@"Couldn't write %@: %s",
                      fname, strerror(errno)];
            NSRunAlertPanel(@"Alert", msg, @"Ok", nil, nil);
        }
    }

    [[NSCursor arrowCursor] set];
    return self;
}

- a2psFirst:(id)sender
{
    return [self setPage:0];
}

- a2psLast:(id)sender
{
    return [self setPage:pages-1];
}

- a2psPage:(id)sender;
{
    return [self setPage:current+[sender tag]];
}

- a2psGoto:(id)sender;
{
    return [self setPage:[[sender selectedCell] tag]];
}


#define MAXERR 4096
- a2psPreview:(id)sender
{
    [self subclassResponsibility:_cmd];
    return nil;
}

#define READINTERVAL 0.2

- prepareForRead
{
    int pgcount, col, fd;
    NSEnumerator *pipeEn = [readPipes objectEnumerator];
    NSPipe *pipe;

    while((pipe = [pipeEn nextObject])!=nil){
        fd = [[pipe fileHandleForReading] fileDescriptor];
        fcntl(fd, F_SETFL, O_NONBLOCK);
    }

    pgcount = [pageMatrix numberOfColumns];
    for(col=pgcount-1; col>0; col--){
        [pageMatrix removeColumn:col];
    }
    [pageMatrix display];

    pages = current = -1;

    pageInv = [NSInvocation 
                  invocationWithMethodSignature: 
                      [self methodSignatureForSelector: 
                                @selector(a2psRead)]];
    [pageInv setSelector:@selector(a2psRead)];
    [pageInv setTarget:self];
    [pageInv retain];

    pageTimer = 
        [NSTimer scheduledTimerWithTimeInterval:READINTERVAL
                 invocation:pageInv
                 repeats:YES];
    [pageTimer retain];

    return self;
}

#define BUFSIZE    4096

- a2psRead
{
    int index, fd, len, atEOF = 0, atEND = 0;
    char *buf[BUFSIZE];
    NSPipe *pipe;

    int pgcount, col;
    id cell;

    BOOL dflag = NO;

    for(index=0; index<[readPipes count]; index++){
        pipe = [readPipes objectAtIndex:index];
        fd = [[pipe fileHandleForReading] fileDescriptor];

        while((len = read(fd, buf, BUFSIZE))>0){
            [[readData objectAtIndex:index]
                appendBytes:buf length:len];
        }

        if(!len){
            atEOF++;
        }
    }

    for(index=0; index<[tasks count]; index++){
        if([[tasks objectAtIndex:index] isRunning]==NO){
            atEND++;
        }
    }    

    dflag = (atEOF==[readPipes count] && atEND==[tasks count] ? 
             YES : NO);

    [self countPages:imgDir done:dflag];
    pgcount = [pageMatrix numberOfColumns];
    if(pgcount<pages){
        for(col=pgcount; col<pages; col++){
            NSString *title = 
                [NSString stringWithFormat:@" %d ", col+1];
            [pageMatrix addColumn];

            cell = [pageMatrix cellAtRow:0 column:col];
            [cell setTag:col];
            [cell setTitle:title];
        }

        [pageMatrix sizeToFit];
        [pageScroller display];
    }
    
    if(pages>0 && current==-1){
        NSString *firstImagePath =
            [NSString stringWithFormat:@"%@/1.%@", imgDir, imgFmt];

        NSImage *img;

        if((img = [[NSImage alloc]
                      initWithContentsOfFile:firstImagePath])==nil){
            int result =
                NSRunAlertPanel(@"Error", 
                                @"%@: Couldn't read first image:\n%@",
                                @"Ok", nil, nil,
                                NSStringFromClass([self class]),
                                firstImagePath);

            if(result==NSAlertAlternateReturn){
                [[NSApplication sharedApplication]
                    terminate:self];
            }

            pages = -1;
            current = -1;
            return self;
        }

        NSArray *reps;
        NSImageRep *rep;

        reps = [img representations];
        if(![reps count]){
            // try again
            [img release];
            return self;
        }

        rep = [reps objectAtIndex:0];

        NSRect newFrame = 
            NSMakeRect(0, 0, [rep pixelsWide], [rep pixelsHigh]);

        [imgView setFrame:newFrame];
        [imgView setImage:img]; [img release];
            
        current = 0;
        [pageMatrix selectCellAtRow:0 column:0];
    }

    if(dflag==YES){
        [pageTimer invalidate];
        [self allPagesDone];
    }

    // [scrollView display];

    return self;
}

- (BOOL)printFile:(NSString *)path
{
    NSFileManager *fm = [NSFileManager defaultManager];

    NSTask *lprTask = [NSTask new];
    NSMutableArray *args;

    BOOL interrupted;

    NSPipe *err = [NSPipe new];

    NSMutableArray *lprCmd = [con lprCommand];
    NSString *lpr = [lprCmd objectAtIndex:0];

    [lprCmd removeObjectAtIndex:0];
    [lprCmd addObject:path];

    [lprTask setLaunchPath:lpr];
    [lprTask setArguments:lprCmd];

    [lprTask setStandardOutput:err];
    [lprTask setStandardError:err];

    NS_DURING
    NSLog(@"%@ %@", [lprTask launchPath], [lprTask arguments]);
    [lprTask launch];

    NS_HANDLER
    NSString *exMsg = 
        [NSString stringWithFormat:@"EXCEPTION: %@ %@",
                  [localException name],
                  [localException reason]];
    NSRunAlertPanel(@"Print failed (lpr)", @"%@\n%@", 
                    @"Ok", NULL, NULL, path, exMsg);
    if([lprTask isRunning]==YES){
        [lprTask terminate];
    }
    NS_VALUERETURN(NO, BOOL);

    NS_ENDHANDLER

    if((interrupted = [con waitForTaskToFinish:lprTask
                           description:@"lpr"])==NO){
        NSString *msg;
        int len;
        NSData *errData;
        
        errData = [[err fileHandleForReading]
                      readDataToEndOfFile];

        len = [errData length];
        if(len>MAXERR){
            len = MAXERR;
        }
        msg = [NSString stringWithCString:[errData bytes]
                        length:len];
        if(len){
            ShowDiagnostics(@"Output from print command", msg);
        }
    }
    else{
        [lprTask terminate];
        return NO;
    }

    return YES;
}


- (BOOL)validateMenuItem:(id <NSMenuItem>)menuItem
{
    NSString *title = [menuItem title];
    
    SEL pageSEL = @selector(a2psPage:);
    IMP pSEL1 = [self methodForSelector:pageSEL],
        pSEL2 = [self methodForSelector:[menuItem action]];

    if(current==-1){
        if([title isEqual:@"Current"]){
            return YES;
        }
        return NO;
    }

    if([title isEqual:@"First"] || [title isEqual:@"Last"]){
        return YES;
    }

    if(pSEL1==pSEL2){
        int next = current + [menuItem tag];
        if(next<0 || next>=pages){
            return NO;
        }
    }

    return YES;
}

- allPagesDone
{
    [self subclassResponsibility:_cmd];
    return nil;
}

Class renderWinClass = NULL, taskClass = NULL;

- cleanupPreview
{
    NSFileManager *fm = [NSFileManager defaultManager];
    int page;

    if(pageTimer!=nil){
        if([pageTimer isValid]==YES){
            NSLog(@"invalidating timer %@: %@", self, pageTimer);
            [pageTimer invalidate];
        }
        [pageTimer release];
        [pageInv release];
    }
     
    if(taskClass==NULL){
        taskClass = [NSTask class];
    }
    if(renderWinClass==NULL){
        renderWinClass = [NSWindow class];
    }

    if(tasks!=nil && [tasks count]>0){
        taskClass = [[tasks objectAtIndex:0] class];

        NSLog(@"cleanup %@; %d page(s); %d", self, pages, current);

        [tasks makeObjectsPerformSelector:@selector(terminate)];
        [tasks makeObjectsPerformSelector:@selector(release)];
        [tasks release];
        tasks = nil;

        [readPipes makeObjectsPerformSelector:@selector(release)];
        [readPipes release];
        readPipes = nil;

        [readData release];
        readData = nil;
    }

    if(imgDir!=nil){
        BOOL isDir;

        if([fm fileExistsAtPath:imgDir isDirectory:&isDir]==YES 
           && isDir==YES){
           if([fm removeFileAtPath:imgDir handler:nil]==NO){
               NSRunAlertPanel(@"Error", 
                               @"Couldn't remove temp dir:\n%@",
                               @"Ok", nil, nil, imgDir);
           }
           else{
               NSLog(@"removed %@", imgDir);
           }
        }
    }

    [imgPath release]; [imgDir release];
    [imgView setImage:nil];


    current = pages = -1;

#ifdef _GPSText_DEBUG
    {
        Class todebug[] = { 
            renderWinClass,
            [NSImage class], 
            [NSBitmapImageRep class],
            [NSCachedImageRep class],
            taskClass, [NSPipe class],
            [NSTimer class],
            nil
        }, *currentClass;

        currentClass=todebug;
        while(*currentClass!=nil){
            id cl=*currentClass;
            NSLog(@"%@ count %u total %u peak %u\n",
                  NSStringFromClass(cl),
                  GSDebugAllocationCount(cl),
                  GSDebugAllocationTotal(cl),
                  GSDebugAllocationPeak(cl));
            currentClass++;
        }
    }
#endif

    return self;
}

@end


@implementation TextPreview

- a2psPreview:(id)sender
{
    NSFileManager *fm = [NSFileManager defaultManager];
    NSString *tempName = [con tempNameExt:nil];
    
    NSTask *a2psTask = [NSTask new], *gsTask = [NSTask new];
    NSMutableArray *args;

    BOOL interrupted;

    NSPipe *fromA2PStoGS = [NSPipe new], 
        *err1 = [NSPipe new], *err2 = [NSPipe new];

    [imgPath retain]; [self cleanupPreview];

    if([fm createDirectoryAtPath:tempName attributes:nil]==NO){
        NSRunAlertPanel(@"Error", 
                        @"Couldn't create temp dir:\n%@",
                        @"Ok", NULL, NULL, tempName);
        return self;
    }

    args = [con getA2PSArgs];
    [args addObject:@"-o"];
    [args addObject:@"-"];
    [args addObject:imgPath];
    [a2psTask setLaunchPath:[con a2psStr]];
    [a2psTask setArguments:args];

    args = [con getGSArgs];
    [self setImgFmt:[con imgFmt]];
    [args addObject:
              [NSString stringWithFormat:@"-sOutputFile=%@/%%d.%@",
                        tempName, imgFmt]];
    [args addObject:[self gsDimensionArg]];
    [args addObject:@"-"];
    [gsTask setLaunchPath:[con gsStr]];
    [gsTask setArguments:args];

    [a2psTask setStandardOutput:fromA2PStoGS];
    [gsTask setStandardInput:fromA2PStoGS];

    [a2psTask setStandardError:err1];
    [gsTask setStandardOutput:err2];
    [gsTask setStandardError:err2];

    NS_DURING
    NSLog(@"%@ %@", [a2psTask launchPath], [a2psTask arguments]);
    [a2psTask launch];

    NSLog(@"%@ %@", [gsTask launchPath], [gsTask arguments]);
    [gsTask launch];

    NS_HANDLER
    NSString *exMsg = 
        [NSString stringWithFormat:@"EXCEPTION: %@ %@",
                  [localException name],
                  [localException reason]];
    NSRunAlertPanel(@"Preview failed", @"%@\n%@", 
                    @"Ok", NULL, NULL, imgPath, exMsg);
    if([a2psTask isRunning]==YES){
        [a2psTask terminate];
    }
    if([gsTask isRunning]==YES){
        [gsTask terminate];
    }
    NS_VALUERETURN(self, id);

    NS_ENDHANDLER

    imgDir = tempName; [imgDir retain];

    tasks = [NSArray arrayWithObjects:a2psTask, gsTask, nil]; 
    [tasks retain];

    readPipes = [NSArray arrayWithObjects:err1, err2, nil]; 
    [readPipes retain];

    readData = [NSArray arrayWithObjects:
                            [NSMutableData dataWithCapacity:4096],
                            [NSMutableData dataWithCapacity:4096],
                        nil];
    [readData retain];

    [self prepareForRead];

    return self;
}

- (BOOL)saveToFile:(NSString *)path opTitle:(NSString *)ot
{
    NSFileManager *fm = [NSFileManager defaultManager];
    NSString *errTitle = 
        [NSString stringWithFormat:@"%@ failed (a2ps)", ot];

    NSTask *a2psTask = [NSTask new];
    NSMutableArray *args;

    BOOL interrupted;

    NSPipe *err = [NSPipe new];

    args = [con getA2PSArgs];
    [args addObject:
              [NSString stringWithFormat:@"--output=%@", path]];
    [args addObject:imgPath];
                        
    [a2psTask setLaunchPath:[con a2psStr]];
    [a2psTask setArguments:args];

    [a2psTask setStandardOutput:err];
    [a2psTask setStandardError:err];

    NS_DURING
    NSLog(@"%@ %@", [a2psTask launchPath], [a2psTask arguments]);
    [a2psTask launch];

    NS_HANDLER
    NSString *exMsg = 
        [NSString stringWithFormat:@"EXCEPTION: %@ %@",
                  [localException name],
                  [localException reason]];
    NSRunAlertPanel(errTitle, @"%@\n%@", 
                    @"Ok", NULL, NULL, path, exMsg);
    if([a2psTask isRunning]==YES){
        [a2psTask terminate];
    }
    NS_VALUERETURN(NO, BOOL);

    NS_ENDHANDLER

    if((interrupted = [con waitForTaskToFinish:a2psTask
                           description:@"a2ps"])==NO){
        if([fm isReadableFileAtPath:path]==NO){
            NSString *msg;
            int len;
            NSData *errData;

            errData = [[err fileHandleForReading]
                          readDataToEndOfFile];

            len = [errData length];
            if(len>MAXERR){
                len = MAXERR;
            }
            msg = [NSString stringWithCString:[errData bytes]
                            length:len];
            ShowDiagnostics(errTitle, msg);
            return NO;
        }
    }
    else{
        [a2psTask terminate];
        return NO;
    }

    return YES;
}

- a2psSave:(id)sender;
{
    NSSavePanel *savePanel = [NSSavePanel savePanel];
    NSString *dirPath = 
        [[NSFileManager defaultManager] currentDirectoryPath],
        *item = [imgPath lastPathComponent],
        *suggest = [[item stringByDeletingPathExtension]
                       stringByAppendingPathExtension:@"ps"];

    [savePanel 
        setTitle:
            [NSString stringWithFormat:@"Save %@", item]];
                      
    [savePanel setPrompt:NSStringFromClass([self class])];
    [savePanel setRequiredFileType:@"ps"];

    if([savePanel runModalForDirectory:dirPath file:suggest]
       ==NSOKButton){
        [self saveToFile:[savePanel filename] opTitle:@"Save"];
    }

    return self;
}

- a2psPrint:(id)sender;
{
    NSFileManager *fm = [NSFileManager defaultManager];
    NSString *tempName = [con tempNameExt:@"ps"];

    if([self saveToFile:tempName opTitle:@"Print"]==YES){
        [self printFile:tempName];
        if([fm removeFileAtPath:tempName handler:nil]==NO){
            NSRunAlertPanel(@"Error", 
                            @"Couldn't remove temp file:\n%@",
                            @"Ok", NULL, NULL, tempName);
        }
    }

    return self;
}

- allPagesDone
{
    if(pages==-1){
        NSString *title, *msg, *errSrc = nil;
        int len;

        NSData *rd;
        NSMutableData *errData = [NSMutableData data];

        rd = [readData objectAtIndex:0];
        if([rd length]){
            [errData appendData:rd];
            errSrc = @"a2ps";
        }

        rd = [readData objectAtIndex:1];
        if([rd length]){
            [errData appendData:rd];
            errSrc = (errSrc==nil ? @"gs" : @"a2ps/gs"); 
                
        }
        
        len = [errData length];
        if(len>MAXERR){
            len = MAXERR;
        }

        title = 
            [NSString 
                stringWithFormat:
                    @"Couldn't read image(s) (%@)", errSrc];
        msg = [NSString stringWithCString:[errData bytes]
                        length:len];
        msg = [msg stringByAppendingFormat:@"\n%@\n", imgDir];
        ShowDiagnostics(title, msg);
    }
    
    return self;
}

@end


@implementation PostScriptOrPDFPreview

- a2psPreview:(id)sender
{
    NSFileManager *fm = [NSFileManager defaultManager];
    NSString *tempName = [con tempNameExt:nil];
    
    NSTask *gsTask = [NSTask new];
    NSMutableArray *args;

    NSPipe *err = [NSPipe new];

    [imgPath retain]; [self cleanupPreview];

    if([fm createDirectoryAtPath:tempName attributes:nil]==NO){
        NSRunAlertPanel(@"Error", 
                        @"Couldn't create temp dir:\n%@",
                        @"Ok", NULL, NULL, tempName);
        return self;
    }

    args = [con getGSArgs];
    [args addObject:@"-dBATCH"];
    [self setImgFmt:[con imgFmt]];
    [args addObject:
              [NSString stringWithFormat:@"-sOutputFile=%@/%%d.%@",
                        tempName, imgFmt]];
    [args addObject:[self gsDimensionArg]];
    [args addObject:imgPath];
    [gsTask setLaunchPath:[con gsStr]];
    [gsTask setArguments:args];

    [gsTask setStandardOutput:err];
    [gsTask setStandardError:err];

    NS_DURING
    NSLog(@"%@ %@", [gsTask launchPath], [gsTask arguments]);
    [gsTask launch];

    NS_HANDLER
    NSString *exMsg = 
        [NSString stringWithFormat:@"EXCEPTION: %@ %@",
                  [localException name],
                  [localException reason]];
    NSRunAlertPanel(@"Preview failed", @"%@\n%@", 
                    @"Ok", NULL, NULL, imgPath, exMsg);
    if([gsTask isRunning]==YES){
        [gsTask terminate];
    }
    NS_VALUERETURN(self, id);

    NS_ENDHANDLER

    imgDir = tempName; [imgDir retain];

    tasks = [NSArray arrayWithObjects:gsTask, nil]; 
    [tasks retain];

    readPipes = [NSArray arrayWithObjects:err, nil]; 
    [readPipes retain];

    readData = [NSArray arrayWithObjects:
                            [NSMutableData dataWithCapacity:4096], nil];
    [readData retain];

    [self prepareForRead];

    return self;
}

- a2psPrint:(id)sender;
{
    NSString *ext = [imgPath pathExtension];

    if([ext isEqualToString:@"pdf"]==YES ||
       [ext isEqualToString:@"PDF"]==YES){
        int result = 
            NSRunAlertPanel(@"Warning", 
                             @"This will send PDF to the print command.\n%@",
                             @"Ok", @"Cancel", nil, imgPath);
        if(result==NSAlertAlternateReturn){
            return self;
        }
    }

    [self printFile:imgPath];
    return self;
}

- allPagesDone
{
    if(pages==-1){
        NSString *title, *msg;
        int len;
        NSData *errData = [readData objectAtIndex:0];

        len = [errData length];
        if(len>MAXERR){
            len = MAXERR;
        }

        title = 
            [NSString 
                stringWithFormat:
                    @"Couldn't read image(s) (gs)"];
        msg = [NSString stringWithCString:[errData bytes]
                        length:len];
        ShowDiagnostics(title, msg);
    }
    
    return self;
}
@end

@implementation ImagePreview

- a2psPreview:(id)sender
{
    current = pages = -1;

    NSFileManager *fm = [NSFileManager defaultManager];

    if(imgDir==nil){
        NSString *tempName = [con tempNameExt:nil];

        if([fm createDirectoryAtPath:tempName attributes:nil]==NO){
            NSRunAlertPanel(@"Error", 
                            @"Couldn't create temp dir:\n%@",
                            @"Ok", nil, nil, tempName);
            return self;
        }

        if([pageMatrix numberOfColumns]!=1){
            [pageMatrix addColumn];

            id cell = [pageMatrix cellAtRow:0 column:0];
            [cell setTag:1];
            [cell setTitle:@" 1 "];

            [pageMatrix sizeToFit];
            [pageScroller display];
        }

        imgDir = tempName; [imgDir retain];
    }

    NSString *firstImagePath =
        [NSString stringWithFormat:@"%@/1.%@", imgDir, imgFmt];

    if([fm fileExistsAtPath:firstImagePath]==NO &&
       [fm copyPath:imgPath toPath:firstImagePath handler:nil]==NO){
        NSRunAlertPanel(@"Error",
                        @"Couldn't copy\n%@\nto\n%@",
                        @"Ok", nil, nil, imgDir);
        return self;
    }



    NSImage *img;

    if((img = [[NSImage alloc]
                  initWithContentsOfFile:firstImagePath])==nil){
        int result =
            NSRunAlertPanel(@"Error", 
                            @"%@: Couldn't read first image:\n%@",
                            @"Ok", @"Abort", nil,
                            NSStringFromClass([self class]),
                            firstImagePath);
        if(result==NSAlertAlternateReturn){
            [[NSApplication sharedApplication]
                terminate:self];
        }

        pages = -1;
        current = -1;
        return self;
    }


    NSArray *reps;
    NSImageRep *rep;

    reps = [img representations];
    if(![reps count]){
        // try again
        [img release];
        return self;
    }

    img = [self imageLoadCompleteHook:img];
    rep = [reps objectAtIndex:0];

    NSRect newFrame = 
        NSMakeRect(0, 0, [rep pixelsWide], [rep pixelsHigh]);

    [imgView setImage:img]; [img release];
            
    pages = 1; current = 0;
    [pageMatrix selectCellAtRow:0 column:0];

    return self;
}

- (NSImage *)imageLoadCompleteHook:(NSImage *)img
{
    if(resolution!=IMAGE_DEFAULT_DPI){
        float scale = (float)resolution/(float)IMAGE_DEFAULT_DPI;

        NSSize size = [img size],
            newSize = NSMakeSize(scale*size.width, scale*size.height);
        NSLog(@"scaling %@ from %@ to %@", imgPath,
              NSStringFromSize(size),
              NSStringFromSize(newSize));

        NSBitmapImageRep
            *oldRep = [[img representations] objectAtIndex:0], *newRep;

        NSRect winRect = NSMakeRect(0, 0, newSize.width, newSize.height);
        NSWindow *pad = 
            [[NSWindow alloc] 
                initWithContentRect:winRect
                styleMask:NSBorderlessWindowMask
                backing:NSBackingStoreBuffered
                defer:NO];
        NSView *padView = [pad contentView];

        [padView lockFocus];
        
        [[NSColor whiteColor] set];
        [NSBezierPath fillRect:winRect];

        [NSGraphicsContext saveGraphicsState];

        PSscale(scale, scale);
        [oldRep draw];

        [NSGraphicsContext restoreGraphicsState];

        [pad flushWindow];

        newRep =
            [[NSBitmapImageRep alloc]
                initWithFocusedViewRect:[padView bounds]];

        [padView unlockFocus];

        renderWinClass = [pad class];
        AUTORELEASE(pad);

        AUTORELEASE(newRep);

        NSImage *newImage = [[NSImage alloc] initWithSize:newSize];
        [newImage addRepresentation:newRep];
        
        AUTORELEASE(img);
        return newImage;
    }

    return img;
}

@end
