/*
        File name: Gif.m

        Written by Georges CHAN

        Purpose: demonstrate how to use the GifDecode class
                 with NXBitmapImageRep/NXImage as receiver.
                 Since the purpose of this program is to show
                 how to use the GifDecode class therefore, users
                 may found that some of the techniques used
                 here may not be the most efficient ones.

        Example showing on how to use the GifDecode class is mainly
        in the demo method.

        Copyright, All right reserved.

        Feel free to use or distribute this application.


        Date: July 92.

        Note: The author disclaims all warranties with regard to this
              software, including all implied warranties or merchantability,
              in no event shall the author be liable for any special,
              indirect or consequential damages or any damages whatsoever
              resulting from loss of use, data or profits, whether in an
              action of contract, negligence or other tortuous action,
              arising out of or in connection with the use of this software.

              GIF is the copyright property of CompuServe Inc.,
              GIF (sm) is a service mark property of CompuServe Inc.

*/


/*
    The DEMOBITMAP definition tells the compiler to compile
    codes for demonstration with NXBitmapImageRep as a recipient
    of the images in the GIF file.
    If you want to run the demo with NXImage as recipient then
    simply comment the following def line
*/
#define DEMOBITMAP 1


/* Generated by Interface Builder */

#import "GifDecode.h"               // lib for getting GIF files
#import "Gif.h"                     // header file of example prog

#import <appkit/NXBitmapImageRep.h> // lib for bitmaps
#import <appkit/NXImage.h>          // lib for images
#import <string.h>                  // lib for string manipulations
#import <libc.h>                    // lib for unix command, e.g. getwd()
#import <appkit/Window.h>           // lib for windows
#import <appkit/OpenPanel.h>        // lib for open panel
#import <appkit/SavePanel.h>        // lib for save panel
#import <streams/streams.h>         // lib for streams output
#import <appkit/Application.h>      // lib for application
#import <appkit/tiff.h>             // lib for tiff constant, e.g. NX_TIFF_COMPRESSION_LZW
#import <appkit/View.h>             // lib for view
#import <dpsclient/wraps.h>         // lib for displaying PS command
#import <appkit/Matrix.h>           // lib for matrix of button


@implementation Gif

/*
 *
 * demo method demonstrate how to use the GifDecode class, i.e.
 * how to load a series of bitmaps which are the decompressed
 * images obtained from a specified GIF file.
 *
*/
- demo:(char *) filename
{
    id view,                        // view in a window to display images of a GIF file
       gif;                         // GIF object currently being read
    NXSize windowSize;              // minimum size of the display window
    NXRect bounds;                  // bounds of the displayed window
    int counter,                    // loop counter
        noImage = 0;                // number of images obtained from decompression of GIF file

#ifndef DEMOBITMAP
    NXPoint place = {0., 0.};        // hold position to composite picture in demo window

    gif = [[GifDecode alloc] initFromFile :filename
                                imageList :picture[winNum]
                                   maxiNo :MAXIPERGIF
                                noOfFrame :&noImage
                                  winSize :&windowSize
                               useOrCoord :useOrigin];
#else
    gif = [[GifDecode alloc] initFromFile :filename
                                  bitList :picture[winNum]
                                   maxiNo :MAXIPERGIF
                                noOfFrame :&noImage
                                  winSize :&windowSize
                               useOrCoord :useOrigin];
#endif

    if (gif == nil) // error in reading or decoding GIF file
       {
         if (!NXRunAlertPanel("WARNING",
                             "ERROR OCURRED: do you still want to see it?",
                             "YES",
                             "NO",
                             NULL))
            return self; // return to editor without doing anything
       }

    // adjust origin of the display window so that the window will be visible
    if (winX + windowSize.width >= screamSize.width)
       winX = 100;

    if (winY + windowSize.height >= screamSize.height)
       winY = 150;

    if (useScreenSize)
       NXSetRect(&bounds, winX, winY,windowSize.width,windowSize.height);
    else
        { // use size of the image
          [picture[winNum][0] getSize:&windowSize];
          NXSetRect(&bounds, winX, winY,windowSize.width,windowSize.height);
        }

    // allocate the display window
    win[winNum] = [Window newContent: &bounds
                               style: NX_TITLEDSTYLE
                             backing: NX_BUFFERED
                          buttonMask: NX_CLOSEBUTTONMASK
                               defer: NO];

    view = [win[winNum] contentView];

    [win[winNum] setTitle: filename];   // put file name as title of window

    [win[winNum] display];

    [win[winNum] setDelegate:self];

    [view lockFocus];

    // clears the demo window
    [view getBounds: &bounds];
    PSsetgray(NX_BLACK);
    NXRectFill(&bounds);
    [view display];

    [win[winNum] orderFront:nil];       // move demo window up to front of screen

    for (counter = 0; counter < noImage; counter++)
        { // display picture
#ifdef DEMOBITMAP
          [picture[winNum][counter] draw];
          NXPing();    // flush display buffer
#else
          [picture[winNum][counter] composite:command toPoint: &place];
          NXPing();    // flush display buffer
          sleep(1);    // composite is too fast! slow it down
#endif
          [view display];
        }

    [view unlockFocus];


    cuNo[winNum] = noImage-1;

    // adjust origin position so that next time, pictures won't totally overlap
    winX += 50;
    winY += 50;

    noIm[winNum] = noImage;

    if (winNum+1 < MAXWIN)
       winNum++;

    return self;
}

#ifdef DEMOBITMAP
/*
    disable the pop up list button for composite mode
    since using NXBitmapImageRep only.
*/
- setComposeBut:sender
{
    return [sender setEnabled:NO];
}

#endif

/*
 *
 * init method will initialize the window. Initialization is
 * achieved by overriding the superclass's initFrame method
 *
*/
- init
{
    [super init];             // run superclass init method from class object

    getwd(home);         // get current working directory

    // first window is stuck at (100,200)
    winX = 100;
    winY = 150;

    // get limit of screen size
    [NXApp getScreenSize: &screamSize];
    screamSize.width -= 100;
    screamSize.height -= 100;

    useOrigin = 1;
    useScreenSize = 1;

    command = NX_COPY;

    return self;
}

/*
 *
 * method for getting the name of the input data file through the use of the open panel
 *
*/
- getlfname:(char *) fname :(int *) goon
{
    char **files,
         *typo[] = {"gif", "GIF", NULL};  // get only files with "map" extension
    id openwin = [OpenPanel new];

    [openwin allowMultipleFiles:YES];
    *goon = 1;
    if (![openwin runModalForDirectory:home file:NULL types:typo])
       { // cancel action selected
         *goon = 0;
         return self;
       }

    *goon = 1;                    // yes! user selected an input file
    files = (char **) [openwin filenames];

    strcpy(home, [openwin directory]);
    strcpy(fname, files[0]);

    return self;
}


/*
 *
 * method for performing a retrievement of a data file for mapping
 *
*/
- load:sender
{
    char fname[FNAMELEN],      // hold name of the input file
         full[MAXLEN];         // hold full path name + file name
    int goon;                  // flag for continue loading action

    [self getlfname:fname :&goon];    // get the name of the input file
    if (goon && strlen(fname))
       { // have selected a file
         strcpy(full,home);    // find the full path to the selected file
         strcat(full,"/");
         strcat(full,fname);   // stick the name of the file at the end
         [self demo: full];
       }

    return self;

}


/*
 *
 * save method will save the content of a window to
 * a file by using TIFF format.
 * Note: ONLY save the LAST bitmap displayed in the 
 * specified display window.
 *
*/
- save:sender
{
    NXRect rect;          // hold size of screen rectangle
    id bitmap,            // hold bitmap of screen
       view;              // view to save
    char fname[MAXLEN];   // hold name of the output file

    SavePanel *saveWin = [SavePanel new];   // open save panel
    [saveWin setRequiredFileType: "tiff"];  // add tiff extension to file name
    if (![saveWin runModalForDirectory: home file:NULL])
       return self;     // nothing selected
    strcpy(fname, [saveWin filename]); // stick the name of the file

    view = [[NXApp keyWindow] contentView];
    [view lockFocus];
    [view getBounds: &rect];  // get size of the map
    bitmap = [[NXBitmapImageRep alloc] initData:NULL fromRect:&rect]; // get bitmap 
    [view unlockFocus];

     if (bitmap)
        { // save bitmap as TIFF file in /tmp directory
          NXStream *s = NXOpenMemory(NULL, 0, NX_READWRITE);
          if (s)
             { // save bitmap screen to tiff file
               [bitmap writeTIFF:s usingCompression: NX_TIFF_COMPRESSION_LZW];
               NXFlush(s);
               if (NXSaveToFile (s, fname))
                  fprintf(stderr, "Cannot save to %s\n", fname);
               NXCloseMemory (s, NX_FREEBUFFER);
             }
          [bitmap free];
        }

    return self;
}

/*
 *
 * forward method will display the next picture (if exists)
 * of the GIF file in the key window.
 *
*/
- forward:sender
{
    id view;              // view of the key window
    int counter = 0;      // window number of the key window

#ifndef DEMOBITMAP
    NXPoint place = {0., 0.};        // hold position to composite picture in demo window
    NXRect bounds;                  // bounds of the displayed window
#endif

    // find key window window number
    while (counter < MAXWIN && counter < winNum && [win[counter] isKeyWindow] == NO)
          counter++;

    if (counter >= MAXWIN || counter >= winNum) // can't find key window's number
       return self;

    if ([win[counter] isKeyWindow])
       { // go forward
#ifdef DEMOBITMAP
         if (noIm[counter] < 2) // only one picture
            return self;
#else
         if (noIm[counter] < 2) // only one picture
            {
              // clears the demo window
              view = [win[counter] contentView];
              [view lockFocus];
              [view getBounds: &bounds];
              PSsetgray(NX_BLACK);
              NXRectFill(&bounds);
              [picture[counter][0] composite:command toPoint: &place];
              [view unlockFocus];
              return [view display];
            }
#endif

         // show next picture
         view = [win[counter] contentView];
         [view lockFocus];
         if (cuNo[counter]+1 >= noIm[counter])
            cuNo[counter] = 0;
         else
             cuNo[counter]++;
#ifdef DEMOBITMAP
         [picture[counter][cuNo[counter]] draw];
#else
         [picture[counter][cuNo[counter]] composite:command toPoint: &place];
#endif
         [view display];
         [view unlockFocus];
       }

    return self;
}

/*
 *
 * backward method will display the previous picture (if exists)
 * of the GIF file in the key window.
 *
*/
- backward:sender
{
    id view;
    int counter = 0;

#ifndef DEMOBITMAP
    NXPoint place = {0., 0.};        // hold position to composite picture in demo window
    NXRect bounds;                  // bounds of the displayed window
#endif

    // find key window window number
    while (counter < MAXWIN && counter < winNum && [win[counter] isKeyWindow] == NO)
          counter++;

    if (counter >= MAXWIN || counter >= winNum) // can't find key window's number
       return self;

    if ([win[counter] isKeyWindow])
       { // go backward
#ifdef DEMOBITMAP
         if (noIm[counter] < 2) // only one picture
            return self;
#else
         if (noIm[counter] < 2) // only one picture
            {
              // clears the demo window
              view = [win[counter] contentView];
              [view lockFocus];
              [view getBounds: &bounds];
              PSsetgray(NX_BLACK);
              NXRectFill(&bounds);
              [picture[counter][0] composite:command toPoint: &place];
              [view unlockFocus];
              return [view display];
            }
#endif

         // show previous picture
         view = [win[counter] contentView];
         [view lockFocus];
         if (cuNo[counter]-1 < 0)
            cuNo[counter] = noIm[counter]-1;
         else
             cuNo[counter]--;
#ifdef DEMOBITMAP
         [picture[counter][cuNo[counter]] draw];
#else
         [picture[counter][cuNo[counter]] composite:command toPoint: &place];
#endif
         [view display];
         [view unlockFocus];
       }

    return self;
}

/*
 *
 * animate method will display all the pictures (if exist)
 * of the GIF file in the key window.
 *
*/
- animate:sender
{
    id view;
    int counter = 0,
        i;

#ifndef DEMOBITMAP
    NXPoint place = {0., 0.};        // hold position to composite picture in demo window
    NXRect bounds;                  // bounds of the displayed window
#endif

    // find key window window number
    while (counter < MAXWIN && counter < winNum && [win[counter] isKeyWindow] == NO)
          counter++;

    if (counter >= MAXWIN || counter >= winNum) // can't find key window's number
       return self;

    if ([win[counter] isKeyWindow])
       { // animate
#ifdef DEMOBITMAP
         if (noIm[counter] < 2) // only one picture
            return self;
#else
         if (noIm[counter] < 2) // only one picture
            {
              // clears the demo window
              view = [win[counter] contentView];
              [view lockFocus];
              [view getBounds: &bounds];
              PSsetgray(NX_BLACK);
              NXRectFill(&bounds);
              [picture[counter][0] composite:command toPoint: &place];
              [view unlockFocus];
              return [view display];
            }
#endif

         // animate it!
         view = [win[counter] contentView];
         [view lockFocus];
         for (i = 0; i < noIm[counter]; i++)
             { // display picture
#ifdef DEMOBITMAP
               [picture[counter][i] draw];
               NXPing();    // flush display buffer
#else
               [picture[counter][i] composite:command toPoint: &place];
               NXPing();    // flush display buffer
               sleep(1);    // composite is too fast, slow it down!
#endif
               [view display];
             }
         [view unlockFocus];
       }

    return self;
}

/*
 *
 * changeOrigin will switch between forced origin mode (i.e.
 * origin of picture is set at (0,0)) and image origin
 * mode (i.e. origin is set the same as the ones described in the
 * picture). Note that the switch has effect only on the next
 * gif files opened.
 *
*/
- changeOrigin:sender
{
    if (useOrigin)
       useOrigin = NO;
    else
        useOrigin = YES;
    return self;
}

/*
    changeMode method will change the new composite mode
    that the user has selected.
    All subsequent pictures will be composited with the new
    mode.
*/
- changeMode:sender
{
#ifndef DEMOBITMAP
    int option[] = { NX_COPY, NX_SOVER, NX_DOVER, NX_SIN,
                     NX_DIN, NX_SOUT, NX_DOUT, NX_SATOP,
                     NX_DATOP, NX_XOR, NX_PLUSD, NX_PLUSL };

    command = option[[sender selectedRow]];
#endif

    return self;
}

/*
    windowWillClose method will released the window closed
    by the user. All NXBitmapImageRep (NXImage) are shifted down.
*/
- windowWillClose:sender
{
    int counter = 0,
        i,
        j;

    // find key window window number
    while (counter < MAXWIN && counter < winNum && win[counter] != sender)
          counter++;

    if (counter >= MAXWIN || counter >= winNum) // can't find key window's number
       return self;

    if (sender == win[counter]) // check if the window to be closed is right one
       { // yes! shift array down
         for (i = 0; i < noIm[counter]; i++)
             [picture[counter][i] free];

         for (i = counter; i < winNum; i++)
             {
               win[i] = win[i+1];
               cuNo[i] = cuNo[i+1];
               for (j = 0; j < noIm[i+1]; j++)
                   picture[i][j] = picture[i+1][j];
               noIm[i] = noIm[i+1];
             }
         --winNum;
       }

    return self;               // kill the window
}

/*
    changeSize will swicth between screen size (as described in
    the GIF file) and image size (the exact size as described
    in the GIF file).
*/
- changeSize:sender
{
    useScreenSize = [sender selectedRow];

    return self;
}

@end
