home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / Graphics / Multimedia / Movie3.0 / Source / MovieView.m < prev    next >
Encoding:
Text File  |  1995-07-31  |  24.7 KB  |  1,014 lines

  1.  
  2. #include <objc/NXBundle.h>
  3. #include <dpsclient/psops.h>
  4. #include "wraps.h"
  5.  
  6. #import "MovieView.h"
  7.  
  8. /*
  9.  *    Added xanim support
  10.  *    (c) 1995 Andreas Windemuth
  11.  */
  12.  
  13. #define VERBOSE 0
  14.  
  15. #define XANIM 1
  16. #if XANIM
  17. #import "Animation.h"
  18. #endif
  19.  
  20. /*
  21.  * Movie 2.51 - 5/7/92 pjf
  22.  *
  23.  * Differences between 2.5 and 2.51:
  24.  * - the save: method actually has a prayer of working when the
  25.  *   user saves on top of an existing movie and the old copy can't be renamed.
  26.  *
  27.  * Differences between 2.0 and 2.5:
  28.  * - buttons to control cache depth
  29.  *
  30.  * - turn off multiframe .tiffs by default (define BC_VERSION_1 if you
  31.  *   have a .tiff movie and are too lazy to use tiffutil to turn
  32.  *   it into an .anim directory)
  33.  *
  34.  * - now able to save movie (currently-selected depth)
  35.  *
  36.  */
  37.  
  38. #define maxFrames 1024
  39.  
  40. @implementation MovieView
  41.  
  42. //
  43. - initFrame:(const NXRect *) frameRect
  44.  {
  45.     const char *x;
  46.     [(self = [super initFrame:frameRect]) allocateGState];
  47.     state = STOPPED;
  48.     mode = LOOP;
  49.     maxSize.width = maxSize.height = -1.0;
  50.     moviePath = NULL;
  51.     movieFrame = NULL;
  52.     frameCount = 0;
  53.     anim = nil;
  54.     pingDuringDisplay=NO;
  55.     x=NXGetDefaultValue("Movie","DefaultDepth");
  56.     if (!x) dmode=D_DEF; /* use default depth */
  57.     else switch(atoi(x)) {
  58.         default:
  59.         case 0: dmode=D_DEF; break;
  60.         case 2: dmode=D_2; break;
  61.         case 8: dmode=D_8; break;
  62.         case 12: dmode=D_12; break;
  63.         case 24: dmode=D_24; break;
  64.     };
  65.     updateControls = NO;
  66.     showFrameNumber = YES;
  67.     noOriginals = YES;
  68.     fromStream = YES;
  69.     frameRate = 15.0;
  70.     loading = NO;
  71.     willClose = NO;
  72.     numFrames = 0;
  73.     return self;
  74.  }
  75.  
  76. - updateControls
  77. {
  78.     if (loading) {
  79.     [[fNumSlider setMaxValue:numFrames-1] setEnabled:YES];
  80.     [nFramesText setIntValue:numFrames-1];
  81.     }
  82.     [fNumSlider setIntValue:currentFrameIndex];
  83.     [fNumText setIntValue:currentFrameIndex];
  84.     [fNumSlider setEnabled:YES];
  85.     return self;
  86. }
  87.  
  88. - drawSelf:(const NXRect *) rects :(int)count
  89.  {
  90.     NXImage *image;
  91.     NXPoint origin = {0.0,0.0};
  92.     
  93.     if (!movieFrame) return nil;
  94.     image = movieFrame[currentFrameIndex].image;
  95.     if (!image) return self;
  96.     [image composite:NX_SOVER toPoint:&origin];
  97.     if ((!fromStream && loading) || showFrameNumber) {
  98.     NXRect r;
  99.     [self getBounds:&r];
  100.     if (!fromStream && loading)
  101.         PSWtext(r.size.width, r.size.height, "Loading ...", currentFrameIndex);
  102.     else
  103.         PSWframe(r.size.width, r.size.height, currentFrameIndex);
  104.     }
  105.     if (pingDuringDisplay) NXPing();
  106.     if ((frameCount >= (int)frameRate)) {
  107.     if (state != STOPPED) {
  108.         double t=[anim getDoubleRealTime]+[anim getSyncTime];
  109.         double afps=frameCount/(t-oldt);
  110.         [actualFpsText setDoubleValue:afps];
  111.         oldt=t;
  112.     }
  113.     frameCount=0;
  114.     }
  115.     if (updateControls || !anim) [self updateControls];
  116.     return self;
  117.  }
  118.  
  119.  
  120. //
  121. - (BOOL)open:sender
  122.  {
  123. #if XANIM
  124.     const char *const types[] = {
  125.     "tiff", "anim", "mpg", "mpeg", "iff", "gif", "txt", "fli", "dl", "pfx", "rle", "avi", "qt", "mov",
  126.     NULL };
  127. #else
  128.     const char *const types[] = { "tiff", "anim", "mpg", "mpeg", (const char *) NULL };
  129. #endif
  130.     id pan = [OpenPanel new];
  131.     const char *const *filenames;
  132.     char filename[FILENAME_MAX];
  133.  
  134.     if (![pan runModalForTypes:types]) return NO;
  135.     if ((filenames = [pan filenames]) == NULL) return NO;
  136.     sprintf(filename,"%s/%s", [pan directory], filenames[0]);
  137.     return [self openFile:filename];
  138.  }
  139.  
  140. - (BOOL)load:sender
  141. {
  142.      fromStream = NO;
  143.      noOriginals = NO;
  144.      return [self open:sender];
  145. }
  146.  
  147. - makeWindow;
  148.  {
  149.     Window *w=[self window];
  150.     
  151.     [w sizeWindow:maxSize.width:maxSize.height]; /* will recache images */
  152.     [w setMiniwindowIcon:"movieDoc.tiff"];
  153.     [w makeKeyAndOrderFront:self];
  154.     [w display];
  155.     return self;
  156.  }
  157.  
  158. - makePanel:(char *)filename;
  159.  {
  160.     char ptitle[FILENAME_MAX];
  161.     char *ptr=rindex(filename,'/')+1;
  162.     
  163.     if (ptr == (char *)1) ptr=filename;
  164.     sprintf(ptitle,"Controls for %s",ptr);
  165.     [panel setTitle:ptitle];
  166.     [panel setNextResponder:[self window]];
  167.     [depthButtons selectCellAt:(int)dmode:0];
  168.     [self setFps:frameRate];
  169.     [self updateControls];
  170.     [panel orderFront:self];
  171.     return self;
  172.  }
  173.  
  174. - setFps:(float)fps;
  175.  {
  176.      frameRate = fps;
  177.     [fpsSlider setFloatValue:fps];
  178.     [fpsText setFloatValue:fps];
  179.     switch(state) {
  180.     case FORWARD: 
  181.     case REVERSE: if (anim) {
  182.         double period = 1.0/frameRate;
  183.         [anim free];
  184.         anim = [[Animator alloc] initChronon:period adaptation:0.05
  185.                     target:self action:@selector(tick:)
  186.                     autoStart:YES eventMask:0];
  187.         break;
  188.         };
  189.     case STOPPED: break;
  190.     };
  191.     return self;
  192.  }
  193.  
  194. - setNoOriginals;
  195.  {
  196.     int i;
  197.     Window *w=[self window];
  198.  
  199.     if (noOriginals) return self;
  200.     if (movieFrame) for (i=0; i<numFrames; i++) if (movieFrame[i].original) {
  201.     [movieFrame[i].original free];
  202.     movieFrame[i].original = nil;
  203.     }
  204.     noOriginals = TRUE;
  205.     [w setMinSize:&maxSize];
  206.     [w setMaxSize:&maxSize];
  207.     return self;
  208.  }
  209.  
  210. //
  211. //    Terrible kludge to be able to accept event from both 
  212. //    the panel and the window (to close). Why can there
  213. //    be no two parallel sessions? It seems to crash, then ...
  214. //
  215.  
  216. - beginModal;
  217. {
  218.     [NXApp beginModalSession:&panelSession for:panel];
  219.     return self;
  220. }
  221.  
  222. #define EVENT 0
  223.  
  224. - runModal;
  225. {
  226. #if EVENT
  227.     NXEvent ev;
  228.     
  229.     if ([NXApp peekNextEvent:[[self window] eventMask] into:&ev]) {
  230.         if (ev.window==[[self window] windowNum]) {
  231.         [NXApp endModalSession:&panelSession];
  232.         [NXApp beginModalSession:&windowSession for:[self window]];
  233.         [NXApp runModalSession:&windowSession];
  234.         [NXApp endModalSession:&windowSession];
  235.         [NXApp beginModalSession:&panelSession for:panel];
  236.     }
  237.     }
  238.     if ([NXApp peekNextEvent:[panel eventMask] into:&ev]) {
  239.         if (ev.window==[panel windowNum]) {
  240. #endif
  241.         [NXApp runModalSession:&panelSession];
  242. #if EVENT
  243.     }
  244.     }
  245. #endif
  246.     return self;
  247. }
  248.  
  249. - endModal;
  250. {
  251.     [NXApp endModalSession:&panelSession];
  252.     return self;
  253. }
  254.  
  255.  
  256. - (BOOL)openFile:(char *)filename
  257.  {
  258.     if (VERBOSE) debug("openFile: %s\n", filename);
  259.     [self makePanel:filename];
  260.     [[self window] setTitleAsFilename:filename];
  261.     moviePath = copy(filename);
  262.     numFrames = 0;
  263.     loading = YES;
  264.     [self fwd:self];
  265.     return YES;
  266. }
  267.  
  268.  
  269.  
  270.  
  271. - addBitmap:bm copy:(BOOL)copyFlag;
  272.  {
  273.     NXSize sz;
  274.     NXRect r;
  275.     int flen, flag=0;
  276.     double period;
  277.  
  278. //    if (VERBOSE) debug("Add bitmap %d/%d (%d)\n", currentFrameIndex, numFrames, stopFrame);
  279.     [bm getSize:&sz];
  280.     if (sz.width > maxSize.width) maxSize.width=sz.width, flag=1;
  281.     if (sz.height > maxSize.height) maxSize.height=sz.height, flag=1;
  282.     if (flag || !numFrames) [[self window] sizeWindow:maxSize.width:maxSize.height];
  283.     if (!numFrames) [self makeWindow];
  284.     flen = ([bm pixelsWide]*[bm pixelsHigh]*[bm bitsPerPixel])>>3;
  285.     if (numFrames*flen>20000000) [self setNoOriginals];
  286.     if (!movieFrame) {
  287.     currentFrameIndex = 0;
  288.     NX_MALLOC(movieFrame, movieFrameStruct, currentFrameIndex+1);
  289.     } else {
  290.     currentFrameIndex++;
  291.     NX_REALLOC(movieFrame, movieFrameStruct, currentFrameIndex+1);
  292.     }
  293.     if (numFrames<=currentFrameIndex) numFrames = currentFrameIndex+1;
  294.     if (!noOriginals)
  295.     movieFrame[currentFrameIndex].original = copyFlag?[bm copy]:bm;
  296.     else movieFrame[currentFrameIndex].original=nil;
  297.  //    printf("Frame # %d, Size: %8.3f %8.3f\n", currentFrameIndex, sz.width, sz.height);
  298.     if (!fromStream || !currentFrameIndex) {
  299.     movieFrame[currentFrameIndex].image=[[NXImage alloc] initSize:&sz];
  300.     [movieFrame[currentFrameIndex].image setUnique:YES]; /* make caches disjoint */
  301.     [movieFrame[currentFrameIndex].image setBackgroundColor:NX_COLORBLACK];
  302.     } else {
  303.     movieFrame[currentFrameIndex].image=movieFrame[currentFrameIndex-1].image;
  304.     }
  305.     [self getBounds:&r];
  306.     if ([movieFrame[currentFrameIndex].image lockFocus]) {
  307.     [bm drawIn:&r];
  308.     [movieFrame[currentFrameIndex].image unlockFocus];
  309.     } else error("Could not lock focus on image");
  310.     [self display];
  311.     if (noOriginals && !copyFlag) [bm free];
  312.     period = 1.0/frameRate;
  313.     [self runModal];
  314.     while (currentFrameIndex>=stopFrame || state==STOPPED) {
  315.     if (state==STOPPED) stopFrame = currentFrameIndex;
  316.     [self runModal];
  317.     usleep(10000);
  318.     if (willClose) return self;
  319.     }
  320.     frameCount++;
  321.     NXPing();
  322.     return self;
  323.  }
  324.  
  325. - loadFrames
  326. {
  327.     char *ptr, *p1, *p2;
  328.     int n, i;
  329.  
  330.     if (VERBOSE) debug("loadFrames: %d %s\n", state, moviePath);
  331.     if (!moviePath) error("Cannot load frames: no path\n");
  332.     stopFrame = 0;
  333.     currentFrameIndex = 0;
  334.  
  335.   /* get the bitmaps */
  336.     ptr = rindex(moviePath,'.');
  337.     if (ptr && !strcmp(ptr, ".anim")) {  /* the file is an Icon-style .anim directory */
  338.     p1 = rindex(moviePath,'/');
  339.     p1 = p1?(p1+1):moviePath;
  340.     p2 = stringf("%s", p1);
  341.     p2[ptr-p1] = '\0';
  342.     [self openAnimDirectory: stringf("%s/%s", moviePath, p2)];
  343.     }
  344.     else if (ptr && (!strcmp(ptr,".mpg")||!strcmp(ptr,".mpeg"))) { /* an MPEG file */
  345.     [self openMPEGfile:moviePath];
  346.     }
  347.     else if (ptr && !strcmp(ptr,".tiff")) { /* a slew o' TIFFs in one file */
  348.     List *bitmaps;
  349.     
  350.     bitmaps = [NXBitmapImageRep newListFromFile:moviePath];
  351.     if (!bitmaps) {
  352.         NXRunAlertPanel(NULL,"Couldn't get bitmaps from %s",
  353.         NULL,NULL,NULL, moviePath);
  354.         return self;
  355.     };
  356.     n = [bitmaps count];
  357.     for (i=0; i<n; i++) [self addBitmap:[bitmaps objectAt:i] copy:NO];
  358.     [bitmaps free]; /* does not free elements */
  359.     }
  360. #if XANIM
  361.     else if ([self openAnimation:moviePath]) {}
  362. #endif
  363.     else { /* this shouldn't happen */
  364.     [self stop:self];
  365.     NXRunAlertPanel(NULL,"Unknown movie type %s",NULL,NULL,NULL,moviePath);
  366.     return self;
  367.     };
  368.     if (willClose) return self;
  369.     if (fromStream) { 
  370.     [self freeFrames];
  371.     movieFrame = NULL;
  372.     [self updateControls];
  373.     if (numFrames>1 && mode == LOOP) [self loadFrames];
  374.     if (willClose) return self;
  375.     if (mode == BOUNCE) 
  376.         NXRunAlertPanel(NULL, "Cannot bounce while loading",NULL,NULL,NULL);
  377.     }
  378.     [self endModal];
  379.     [self updateControls];
  380.     loading = NO;
  381.     [self fwd:self];
  382.     return self;
  383.  }
  384.  
  385.  
  386. - (List *)listAnimDirectory:(char *)filenameRoot
  387.  {
  388.   List *bitmaps = [[List alloc] init];
  389.   int i=0;
  390.   
  391.   while (1) {
  392.     char buf[FILENAME_MAX];
  393.     NXBitmapImageRep *newbitmap;
  394.     sprintf(buf,"%s.%d.tiff",filenameRoot,i++);
  395.     if ((access(buf,R_OK)) == -1) break;
  396.     newbitmap = [[NXBitmapImageRep alloc] initFromFile:buf];
  397.     if (!newbitmap) {
  398.       NXRunAlertPanel(NULL,"Couldn't get bitmap from %s",NULL,NULL,NULL,
  399.                     buf);
  400.       [[bitmaps freeObjects] free];
  401.       return nil;
  402.       }
  403.     else
  404.       [bitmaps addObject:newbitmap];
  405.     };
  406.   return bitmaps;
  407.  }
  408.  
  409. - openAnimDirectory:(char *)rfn;
  410.  {
  411.     int i;
  412.     NXBitmapImageRep *newbitmap;
  413.     char *fn;
  414.     
  415.     for (i=1; TRUE; i++) {
  416.     fn = stringf("%s.%d.tiff", rfn, i);
  417.     if ((access(fn, R_OK)) == -1) break;
  418.     newbitmap = [[NXBitmapImageRep alloc] initFromFile:fn];
  419.     if (!newbitmap) {
  420.         NXRunAlertPanel(NULL,"Couldn't get bitmap from %s",NULL,NULL,NULL, fn);
  421.         continue;
  422.     }
  423.     [self addBitmap:newbitmap copy:NO];
  424.     if (willClose) break;
  425.     }
  426.     return self;
  427.  }
  428.  
  429. #if XANIM
  430. - (BOOL)openAnimation:(const char *)filename
  431.  {
  432.     int i;
  433.     NXBitmapImageRep *bm;
  434.     Animation *a;
  435.     
  436.     a = [[Animation alloc] initFrom:filename];
  437.     if (!a) {
  438.     [a free];
  439.     return NO;
  440.     }
  441.     for (i=0; TRUE; i++) {
  442.     bm = [a next];
  443.     if (bm) [self addBitmap:bm copy:YES];
  444.     if ([a isLast] || willClose) break;
  445.     }
  446.     [a getMaxSize:&maxSize];
  447.     return YES;
  448.  }
  449.  #endif
  450.  
  451. - openMPEGfile:(char *)filename
  452.  {
  453.     int i, n, flen;
  454.     char command[256];
  455.     FILE *fd;
  456.     mpegInfo *pInfo;
  457.     long data;
  458.     BOOL swab;
  459.     NXStream *pStream;
  460.     NXBitmapImageRep *bm;
  461.     
  462.     if (!(fd = fopen(filename, "r"))) error("Could not open %s", filename);
  463.     fread(&data, 4, 1, fd);
  464.     if (data!=0x000001b3 && data!=0xb3010000)
  465.     error("%s does not contain an mpeg stream: %x", filename, data);
  466.     if (data==0xb3010000) swab=YES; else swab=NO;
  467.     if (!(pInfo = calloc(1, sizeof(mpegInfo)))) error("Could not allocate pInfo.");
  468.     // Get horizontal and vertical size of image space
  469.     // as two 12 bit words, respectively
  470.     // then aspect ratio and picture rate
  471.     // as two 4 bit words.
  472.     fread(&data, 4, 1, fd); if (swab) data = NXSwapLong(data);
  473.     pInfo->picture_rate = 0x0F & data;
  474.     data >>= 4;
  475.     pInfo->aspect_ratio = 0x0F & data;
  476.     data >>= 4;
  477.     // In Motorola format, least significant bits come last
  478.     // v_size is actually the second value in the file
  479.     // i.e. h:12,v:12,a:4,p:4
  480.     pInfo->v_size = 0x0FFF & data;
  481.     pInfo->h_size = 0x0FFF & data >> 12;
  482.     maxSize.width = ((pInfo->h_size + 15) / 16) * 16.0;
  483.     maxSize.height = ((pInfo->v_size + 15) / 16) * 16.0;
  484.     // Get bit rate, vbv buffer size, and constrained parameter flag
  485.     fread(&data, 4, 1, fd); if (swab) data = NXSwapLong(data);
  486.     // throw away (non) intra quant matrix flags
  487.     data >>= 2;
  488.     pInfo->const_param_flag = 1 & data;
  489.     data >>= 1;
  490.     pInfo->vbv_buffer_size = 0x03FF & data;
  491.     data >>= 10 + 1;    // 1 marker bit
  492.     pInfo->bit_rate = 0x03FFFF & data;
  493.     fclose(fd);
  494.     pInfo->fps = pInfo->picture_rate;
  495.     flen = 3 * (int)maxSize.width * (int)maxSize.height;
  496.     
  497.  //    printf("Dimensions: %d %d, buffer size:%d\n", (int)maxSize.width, (int)maxSize.height, flen);
  498.     sprintf(command, "exec %s/mpegDecode %s",
  499.     [[NXBundle mainBundle] directory], filename);
  500.     if (!(fd = popen(command, "r")))
  501.     error("Could not create MPEG process:\n   %s", command);
  502.     pStream = NXOpenFile(fileno(fd), O_RDONLY);
  503.  
  504.  //    printf("Reading frames:\n");
  505.     if (!numFrames) [self setFps:pInfo->fps];
  506.     bm = [[NXBitmapImageRep alloc] initData:NULL
  507.         pixelsWide:(int)maxSize.width
  508.         pixelsHigh:(int)maxSize.height
  509.         bitsPerSample:8
  510.         samplesPerPixel:3    // (cSpace == RGB_COLOR) ? 3 : 1
  511.         hasAlpha:NO
  512.         isPlanar:NO
  513.         colorSpace:NX_RGBColorSpace
  514.         bytesPerRow:0
  515.         bitsPerPixel:0
  516.     ];
  517.     for (i=0; TRUE; i++) {
  518.     if (4!=NXRead(pStream, &data, 4)) break;
  519.  //    printf("Frame # %d.\n", data);
  520.     n = NXRead(pStream, [bm data], flen);
  521.     if (n!=flen) error("Error reading image data (%d/%d bytes read).", n, flen);
  522.     [self addBitmap:bm copy:YES];
  523.     if (willClose) break;
  524.     }
  525.     [bm free];
  526.     if (numFrames<2) {
  527.     NXRunAlertPanel("Read MPEG",
  528.         "Problem reading mpeg stream, no frames found",NULL,NULL,NULL);
  529.     }
  530.     return self;
  531.  }
  532.  
  533. NXWindowDepth deps[] = {
  534.   NX_DefaultDepth, NX_TwoBitGrayDepth,
  535.   NX_EightBitGrayDepth, NX_TwelveBitRGBDepth,
  536.   NX_TwentyFourBitRGBDepth /*,NX_PurinaCatChow__ChowChowChowDepth*/
  537.   };
  538.  
  539. // set up Frame data structures and find max frame size
  540. - allocateFrames:(List *)frames
  541.  {
  542.   int i;
  543.   numFrames=[frames count];
  544.   NX_MALLOC(movieFrame,movieFrameStruct,numFrames);
  545.   for(i=0;i<numFrames;i++) {
  546.     NXImage *nxi;
  547.     NXBitmapImageRep *bm=[frames objectAt:i];
  548.     NXSize sz;
  549.     [bm getSize:&sz];
  550.     movieFrame[i].original=bm;
  551.     nxi=movieFrame[i].image=[[NXImage alloc] initSize:&sz];
  552.     [nxi setUnique:YES]; /* make caches disjoint */
  553.     [nxi setBackgroundColor:NX_COLORBLACK];
  554.     /* keep track of largest frame */
  555.     if (sz.width > maxSize.width) maxSize.width=sz.width;
  556.     if (sz.height > maxSize.height) maxSize.height=sz.height;
  557.     };
  558.   return self;
  559.  }
  560.  
  561. /*****************************************************************
  562.  *****************************************************************/
  563. - superviewSizeChanged:(NXSize *)old
  564.  {
  565.     [anim stopEntry];
  566.     [super superviewSizeChanged:old];
  567.     if (noOriginals) {
  568.     if (!fromStream)
  569.         NXRunAlertPanel("Resize","Can't resize, no originals.",NULL,NULL,NULL);
  570.     }
  571.     else if (!loading) {
  572.     [self recache];
  573.     [self renderFrames];
  574.     }
  575.     if (movieFrame) [[self window] display];
  576.     NXPing();
  577.     [anim resetRealTime];
  578.     [anim startEntry];
  579.     return self;
  580.  }
  581.  
  582. - renderFrames
  583.  {
  584.     int cfi;
  585.     NXRect r;
  586.  
  587.     error("Fix renderFrames!\n");
  588.     [self getBounds:&r];
  589.     cfi = currentFrameIndex;
  590.     for(currentFrameIndex=0; currentFrameIndex<numFrames; currentFrameIndex++) {
  591.     if ([movieFrame[currentFrameIndex].image lockFocus]) {
  592.         [movieFrame[currentFrameIndex].original drawIn:&r];
  593.         [movieFrame[currentFrameIndex].image unlockFocus];
  594.     } else {
  595.         fprintf(stderr,"Barf: couldn't lockFocus on image %d\n",
  596.             (int)movieFrame[currentFrameIndex].image);
  597.         abort();
  598.     }
  599.     [self display];
  600.     NXPing();
  601.     }
  602.     currentFrameIndex = cfi;
  603.     return self;
  604.  }
  605.  
  606. - recache
  607.  // assume depth & size both changed 
  608.  //
  609.  // Appkit bug?  Can one render down from 24 bit color to 2 bit gray?
  610.  //
  611.  {
  612.     NXRect r;
  613.     int i;
  614.     [self getBounds:&r];
  615.     [self freeCaches];
  616.     for(i=0; i<numFrames; i++) {
  617.     movieFrame[i].image=[[NXImage alloc] initSize:&r.size];
  618.     [movieFrame[i].image useCacheWithDepth:deps[(int)dmode]];
  619.     };
  620.     return self;
  621.  }
  622.  
  623. - save:sender
  624.  {
  625.   const char *type = "anim"; // will only save in .anim format.
  626.   SavePanel *sp = [SavePanel new];
  627.   [sp setDelegate:self];
  628.   [sp setRequiredFileType:type];
  629.   if ([sp runModal]) { // OK was hit
  630.     int i;
  631.     char cwd[MAXPATHLEN];
  632.     /* if directory exists, rename it with a wiggle in back. */
  633.     if (access([sp filename],F_OK) == 0) {
  634.       /* I could do this with a couple of calls to system(), but noooo,
  635.        * I had to do it the had way. yeccch. */
  636.       char *buf=malloc(strlen([sp filename]+2));
  637.       sprintf(buf,"%s~",[sp filename]);
  638.       if (!getwd(cwd)) {
  639.         NXRunAlertPanel("FATAL","Couldn't get current directory.",NULL,NULL,NULL);
  640.         abort();
  641.         };
  642.       if (rename([sp filename],buf) == -1) {
  643.         // sledgehammer time.
  644.         struct direct *de;
  645.         DIR *dp;
  646.         chdir([sp filename]);
  647.         dp=opendir(".");
  648.         while(de=readdir(dp)) unlink(de->d_name);
  649.         closedir(dp);
  650.         chdir(cwd);
  651.         unlink([sp filename]);
  652.         };
  653.       };
  654.     mkdir([sp filename],0755);
  655.     chdir([sp filename]);
  656.     for(i=0;i<numFrames;i++) {
  657.       char buf3[MAXPATHLEN];
  658.       char buf2[MAXPATHLEN];
  659.       char *ptr;
  660.       int fd;
  661.       NXStream *s;
  662.       strcpy(buf3,[sp filename]);
  663.       ptr=rindex(buf3,'/')+1;
  664.       *(rindex(ptr,'.'))='\0';
  665.       sprintf(buf2,"./%s.%d.tiff",ptr,i+1);
  666.       fd=open(buf2,O_WRONLY|O_CREAT,0644);
  667.       s=NXOpenFile(fd,NX_WRITEONLY);
  668.       [movieFrame[i].image writeTIFF:s];
  669.       NXClose(s);
  670.       close(fd);
  671.       };
  672.     chdir(cwd);
  673.     };
  674.   return self;
  675.  }
  676.  
  677. - (BOOL) panelValidateFilenames:sender
  678.  {
  679.   if (!strcmp([sender filename],moviePath)) {
  680.     NXRunAlertPanel("Save","Cannot overwrite original movie",NULL,NULL,NULL);
  681.     return NO;
  682.     };
  683.   return YES;
  684.  }
  685.  
  686. - freeCaches
  687.  {
  688.     int i;
  689.  
  690.     if (fromStream) [movieFrame[currentFrameIndex].image free];
  691.     else for(i=0;i<numFrames;i++) [movieFrame[i].image free];
  692.     return self;
  693.  }
  694.  
  695. - freeOriginals
  696.  {
  697.     int i;
  698.     if (!noOriginals) for(i=0;i<numFrames;i++) {
  699.     [movieFrame[i].original free];
  700.     movieFrame[i].original = nil;
  701.     }
  702.     return self;
  703.  }
  704.  
  705. - freeFrames
  706. {
  707.     [self freeCaches];
  708.     if (!noOriginals) [self freeOriginals];
  709.     NX_FREE(movieFrame);
  710.     return self;
  711. }
  712.  
  713. - free
  714. {
  715.     cfree(moviePath);
  716.     [self freeGState];
  717.     if (anim) [anim free];
  718.     return [super free];
  719.  }
  720.  
  721. - copy:sender
  722.  {
  723.     char *buffer;
  724.     NXStream *stream;
  725.     int length, maxLength;
  726.     Pasteboard *pasteboard = [Pasteboard new];
  727.     runState s=state;
  728.  
  729.     [anim stopEntry];
  730.     if (state!=STOPPED) [self stop:self];
  731.     [pasteboard declareTypes:&NXPostScriptPboardType num:1 owner:self];
  732.     stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  733.     [self copyPSCodeInside:&bounds to:stream];
  734.     NXFlush(stream);
  735.     NXGetMemoryBuffer(stream, &buffer, &length, &maxLength);
  736.     [pasteboard writeType:NXPostScriptPboard data:buffer length:length];
  737.     NXCloseMemory(stream, NX_FREEBUFFER);
  738.     switch(s) {
  739.     case STOPPED: break;
  740.     case FORWARD: 
  741.     case REVERSE: [anim startEntry]; break;
  742.     };
  743.     return self;
  744.  }
  745.  
  746. - tick:sender
  747.  {
  748.     int next, end;
  749.  
  750. //    if (VERBOSE) debug("tick: %d %d\n", state, loading);
  751.     if (state==STOPPED) error("Orphaned tick\n");
  752.     if (loading) {
  753.     if (movieFrame) {
  754.         if (stopFrame<=currentFrameIndex) stopFrame++;
  755.         return self;
  756.     }
  757.     if (state!=FORWARD) error("Wrong loading direction: %d\n", state);
  758.     [self beginModal];
  759.     [self loadFrames];
  760.     [self endModal];
  761.     if (willClose) [window performClose:self];
  762.     return self;
  763.     }
  764.     end = (state == FORWARD) ? numFrames-1 : 0;
  765.     switch(mode) {
  766.     case ONCE:
  767.         if (currentFrameIndex == end) {
  768.         [self stop:self];
  769.         return self;
  770.         } else currentFrameIndex += (int)state;
  771.         break;
  772.     case LOOP:
  773.         next = currentFrameIndex + (int)state;
  774.         if (((state == FORWARD)&&(next>end)) ||
  775.         ((state == REVERSE)&&(next<end))) {
  776.         currentFrameIndex = (state < 0) ? numFrames-1 : 0;
  777.         } else currentFrameIndex = next;
  778.         break;
  779.     case BOUNCE:
  780.         next = currentFrameIndex + (int)state;
  781.         if (((state == FORWARD)&&(next>end)) ||
  782.         ((state == REVERSE)&&(next<end))) {
  783.         if (state == FORWARD) [self selectStateButton:REV];
  784.         if (state == REVERSE) [self selectStateButton:FWD];
  785.         state *= -1;
  786.         currentFrameIndex += (int)state;
  787.         } else currentFrameIndex = next;
  788.         break;
  789.     };
  790.     frameCount++;
  791.     [self display];
  792.     return self;
  793.  }
  794.  
  795. /*****************************************************************
  796.  *****************************************************************/
  797.  
  798. - fwd:sender
  799.  {
  800.     if (VERBOSE) debug("fwd: \n");
  801.     if (state != STOPPED) [self stop:self];
  802.     state = FORWARD;
  803.     [self move:sender];
  804.     return self;
  805.  }
  806.  
  807. - rev:sender
  808.  {
  809.     if (VERBOSE) debug("rev: \n");
  810.    if (loading) {
  811.     NXRunAlertPanel(NULL,"Cannot reverse while loading.",NULL,NULL,NULL);
  812.     return self;
  813.     }
  814.     if (state != STOPPED) [self stop:self];
  815.     state = REVERSE;
  816.     [self move:sender];
  817.     return self;
  818.  }
  819.  
  820. - move:sender
  821. {
  822.     double period = 1.0/frameRate;
  823.  
  824.     if (VERBOSE) debug("move: %d\n", state);
  825.     anim = [[Animator alloc] 
  826.     initChronon:period adaptation:0.05 /*?*/
  827.     target:self action:@selector(tick:)
  828.     autoStart:YES eventMask:0
  829.     ];
  830.     if (state == FORWARD) [self selectStateButton:FWD];
  831.     if (state == REVERSE) [self selectStateButton:REV];
  832.     [fNumText setStringValue:""];
  833.     [fNumSlider setEnabled:NO];
  834.     oldt=[anim getSyncTime];
  835.     frameCount = 0;
  836.     return self;
  837. }
  838.  
  839. - stop:sender
  840.  {
  841.     if (loading && state==STOPPED) {  /* Quit this movie even while loading  */
  842.     willClose = YES;   /* Has to propagate through recursive loadFrames calls */
  843.     return self;
  844.     }
  845.     switch(state) {
  846.     case FORWARD:
  847.     case REVERSE: if (anim) [anim free]; anim=nil;
  848.     case STOPPED: break;
  849.     }
  850.     state = STOPPED;
  851.     [self selectStateButton:STOP];
  852.     [self updateControls];
  853. //    [self display];
  854.     return self;
  855.  }
  856.  
  857. - fwdStep:sender
  858.  {
  859.     [self step:(int) FORWARD];
  860.     return self;
  861.  }
  862.  
  863. - revStep:sender
  864.  {
  865.     if (loading) {
  866.     NXRunAlertPanel(NULL, "Cannot reverse while loading.",NULL,NULL,NULL);
  867.     return self;
  868.     }
  869.     [self step:(int) REVERSE];
  870.     return self;
  871.  }
  872.  
  873. - step:(int) direction
  874.  {
  875.     if (VERBOSE) debug("Step: %d    %d %d %d\n", direction,
  876.     currentFrameIndex, stopFrame, numFrames);
  877.     if (state != STOPPED) [self stop:self];
  878.     if (loading) {
  879.     stopFrame += direction;
  880.     state = direction;
  881.     } else {
  882.     if (((currentFrameIndex = currentFrameIndex + direction) % numFrames) < 0)
  883.     currentFrameIndex = numFrames + currentFrameIndex;
  884.     }
  885.     [self selectStateButton:STOP];
  886.     return [self display];
  887.  }
  888.  
  889. - reSize:(NXSize *)s
  890. {
  891.     if (noOriginals)
  892.     NXRunAlertPanel("Resize","Resize disabled for lack of memory.",NULL,NULL,NULL);
  893.     else [[self window] sizeWindow:s->width :s->height];
  894.     return self;
  895. }
  896.  
  897. - expand2x:sender
  898.  {
  899.   NXRect r;
  900.   [self getBounds:&r];
  901.   r.size.width *= 2.0;
  902.   r.size.height *= 2.0;
  903.   [self reSize:&r.size];
  904.   return self;
  905.  }
  906.  
  907. - reduce50pct:sender
  908.  {
  909.   NXRect r;
  910.   [self getBounds:&r];
  911.   r.size.width *= 0.5;
  912.   r.size.height *= 0.5;
  913.   [self reSize:&r.size];
  914.   return self;
  915.  }
  916.  
  917. - restore:sender
  918.  {
  919.   [self reSize:&maxSize];
  920.   return self;
  921.  }
  922.  
  923.  
  924. - modeButtonsChanged:sender
  925.  {
  926.     mode = (runMode)[sender selectedRow];
  927.     return self;
  928.  }
  929.  
  930. - fNumSliderChanged:sender
  931.  {
  932.     stopFrame = [sender intValue];
  933.     if (loading || currentFrameIndex == stopFrame) return self;
  934.     currentFrameIndex = stopFrame;
  935.     [self stop:self];
  936.     [self display];
  937.     return self;
  938.  }
  939.  
  940. - fpsSliderChanged:sender
  941.  {
  942.     [self setFps:[sender floatValue]];
  943.     return self;
  944.  }
  945.  
  946. - pingButtonChanged:sender
  947.  {
  948.   switch([sender selectedRow]) {
  949.     case 0: pingDuringDisplay=NO; break;
  950.     case 1: pingDuringDisplay=YES; break;
  951.     };
  952.   return self;
  953.  }
  954.  
  955. - selectStateButton:(runState)b
  956.  {
  957.   [stateButtons selectCellAt:0:((int)b)];
  958.   return self;
  959.  }
  960.  
  961. - depthButtonsChanged:sender
  962.  {
  963.     if (noOriginals) {
  964.     NXRunAlertPanel("Depth","Can't change depth, no originals.",NULL,NULL,NULL);
  965.     return self;
  966.     }
  967.     dmode=(depthMode)[sender selectedRow];
  968.     [anim stopEntry];
  969.     [self recache];
  970.     [self renderFrames];
  971.     [self display];
  972.     [anim resetRealTime];
  973.     [anim startEntry];
  974.     return self;
  975.  }
  976.  
  977. - updateCheckBoxChanged:sender
  978.  {
  979.   updateControls = !updateControls;
  980.   return self;
  981.  }
  982.  
  983. - frameCheckBoxChanged:sender
  984.  {
  985.     showFrameNumber = !showFrameNumber;
  986.     return self;
  987.  }
  988.  
  989.  
  990. // Window's delegate methods
  991.  
  992. - windowWillClose:sender
  993.  {
  994.   [panel close];
  995.   [self free];
  996.   return self;
  997.  }
  998.  
  999. -windowDidMiniaturize:sender
  1000.  {
  1001.   [panel orderOut:sender];
  1002.   [anim stopEntry];
  1003.   return self;
  1004.  }
  1005.  
  1006. -windowDidDeminiaturize:sender
  1007.  {
  1008.   [panel orderFront:sender];
  1009.   [anim resetRealTime];
  1010.   [anim startEntry];
  1011.   return self;
  1012.  }
  1013. @end
  1014.