; $Id: cw_animate.pro,v 1.32 1999/08/26 20:34:30 alan Exp $ ; ; Copyright (c) 1992-1999, Research Systems, Inc. All rights reserved. ; Unauthorized reproduction prohibited. ;+ ; NAME: ; CW_ANIMATE ; ; PURPOSE: ; This widget displays an animated sequence of images using ; X-windows Pixmaps. This is a compound widget, based on the ; XINTERANIMATE procedure, with the following advantages: ; - It can be included in other applications. ; - Multiple copies can be run simultaneously. ; ; The speed and direction of the display can be adjusted using ; the widget interface. ; ; CATEGORY: ; Image display, compound widgets. ; ; CALLING SEQUENCE: ; To initially create: ; widget = CW_ANIMATE(PARENT, SIZEX, SIZEY, NFRAMES) ; ; To reinitialize when another animation is loaded: ; CW_ANIMATE_INIT, ANIMATEBASE, SIZEX, SIZEY, NFRAMES ; ; To load a single image: ; CW_ANIMATE_LOAD, WIDGET, IMAGE = IMAGE, FRAME = FRAME_INDEX ; ; To load a single image that is already displayed in an existing window: ; ; CW_ANIMATE_LOAD, WIDGET, FRAME = FRAME_INDEX, $ ; WINDOW = [WINDOW_NUMBER [, X0, Y0, SX, SY]] ; ; (This technique is much faster than reading back from the window.) ; ; To display the animation after all the images have been loaded: ; ; CW_ANIMATE, WIDGET [, RATE] ; ; To get a copy of the vector of Pixmaps being used by the widget. ; If this routine is called, the widget does not destroy the pixmaps ; when it is destroyed. The user can then provide them to a later ; call to CW_ANIMATE to re-use them while skipping the Pixmap creation ; and rendering step: ; ; CW_ANIMATE_GETP, widget, PIXMAPS ; ; INPUTS: ; CW_ANIMATE: ; PARENT: The ID of the parent widget. ; SIZEX: The width of the displayed image. ; SIZEY: The height of the displayed image. ; NFRAMES: The number of frames in the animation sequence. ; ; CW_ANIMATE_INIT: ; ANIMATEBASE: The ID of the base animation widget. ; SIZEX: The width of the displayed image. ; SIZEY: The height of the displayed image. ; NFRAMES: The number of frames in the animation sequence. ; ; CW_ANIMATE_LOAD: ; WIDGET: The ID of the widget (as created with CW_ANIMATE) ; into which the image should be loaded. ; ; CW_ANIMATE_RUN: ; WIDGET: The ID of the widget (as created with CW_ANIMATE) ; into which the image should be loaded. ; RATE: A value between 0 and 100 that represents the ; speed of the animation as a percentage of the ; maximum display rate. The fastest animation has ; a value of 100 and the slowest has a value of 0. ; The default animation rate is 100. ; STOP: If this keyword is set, the animation is stopped. ; NFRAMES: Specifies the number of frames to animate, must ; <= the number specified in CW_ANIMATE(). ; ; KEYWORD PARAMETERS: ; CW_ANIMATE: ; PIXMAPS: This keyword provides the new widget with a vector ; of pre-existing pixmap (off screen window) IDs. ; This vector is usually obtained from a call to ; CW_ANIMATE_GETP applied to a previous animation ; widget. ; UVALUE: A user supplied value to be stored in the widget's ; user value field. ; UNAME: A user supplied string name to be stored in the ; widget's user name field. ; NO_KILL: If NOT set, an "End Animation" button is added to the ; animation base. If set the button is not added. ; OPEN_FUNC: A user supplied string that specifies a callback ; function name. When a value is specified for this ; keyword, an "Open..." pushbutton is added to the ; window. When the "Open..." pushbutton is clicked ; the OPEN_FUNC function is called to load new ; animation data. ; INFO_FILE: A filename containing text to be displayed by ; XDISPLAYFILE when user selects the help button. ; ; CW_ANIMATE_INIT: ; PIXMAPS: This keyword provides the new widget with a vector ; of pre-existing pixmap (off screen window) IDs. ; This vector is usually obtained from a call to ; CW_ANIMATE_GETP applied to a previous animation ; widget. ; ; CW_ANIMATE_LOAD: ; CYCLE: If set, cycle. Normally, frames are displayed ; going either forward or backwards. If CYCLE is ; set, reverse direction after the last frame in ; either direction is displayed. ; FRAME: The frame number to be loaded. This is a value ; between 0 and NFRAMES. If not supplied, frame 0 ; is loaded. ; IMAGE: The image to be loaded. ; ORDER: Set this keyword to display images from the top ; down instead of the default bottom up. This keyword ; is only used when loading images with the IMAGE ; keyword. ; TRACK: If set, the frame slider tracks the current frame. ; Default is not to track. ; WINDOW: When this keyword is specified, an image is copied ; from an existing window to the animation pixmap. ; When using X windows, this technique is much faster ; than reading from the display and then loading with ; the IMAGE keyword. ; ; The value of this parameter is either an IDL window ; number (in which case the entire window is copied), ; or a vector containing the window index and the ; rectangular bounds of the area to be copied. For ; example: ; WINDOW = [Window_Number, X0, Y0, Sx, Sy] ; ; XOFFSET: The horizontal offset, in pixels from the left of ; the frame, of the image in the destination window. ; ; YOFFSET: The vertical offset, in pixels from the bottom of ; the frame, of the image in the destination window. ; ; OUTPUTS: ; No explicit outputs. ; ; SIDE EFFECTS: ; If the widget is realized before calls to CW_ANIMATE_LOAD, the frames ; are displayed as they are loaded. This provides the user with an ; indication of how things are progressing. ; ; When the widget is destroyed, it destroys the pixmaps used in the ; animation, unless they were previously obtained via CW_ANIMATE_GETP ; and the KILL_ANYWAY keyword was not set. ; ; The only event returned by this widget indicates that the user ; has pressed the DONE button. The parent application should use ; this as a signal to kill the animation widget via WIDGET_CONTROL. ; ; RESTRICTIONS: ; If more than one animation widget is running at a time, they ; will fight for resources and run slower. ; ; PROCEDURE: ; When initialized, this procedure creates pixmaps containing the ; frames of the animation sequence. Once the images are loaded, ; they are displayed by copying the images from the pixmap or buffer ; to the visible draw widget. ; ; EXAMPLE: ; Assume the following event handler procedure exists: ; PRO EHANDLER, EV ; WIDGET_CONTROL, /DESTROY, EV.TOP ; end ; ; Enter the following commands to open the file ABNORM.DAT (a series ; of images of a human heart) and load the images it contains into ; an array H: ; ; OPENR, 1, FILEPATH('abnorm.dat', SUBDIR = 'images') ; H = BYTARR(64, 64, 16) ; READU, 1, H ; CLOSE, 1 ; H = REBIN(H, 128, 128, 16) ; ; Create an instance of the animation widget at load the frames: ; ; base = widget_base() ; animate = CW_ANIMATE(base, 128, 128, 16) ; WIDGET_CONTROL, /REALIZE, base ; for i=0,15 do CW_ANIMATE_LOAD, animate, FRAME=i, IMAGE=H(*,*,I) ; ; Start the animation: ; ; CW_ANIMATE_RUN, animate ; XMANAGER, "CW_ANIMATE Demo", base, EVENT_HANDLER = "EHANDLER" ; ; Pressing the DONE button kills the application. ; ; MODIFICATION HISTORY: ; AB, June 1992 Heavily based on the XINTERANIMATE procedure. ; SR, September 1992 Fixed a problem when a paused animation's ; frame selection was moved and the resulting ; frame change ended up in another animation. ; SR, November 1992 Fixed a problem when a single paused animation ; would fail when the frame selection slider ; event tried to set do a bad drawing window. ; DMS/AB, March, 1993 Got rid of state caching. Got rid of ; XMANAGER background tasks in favor of new ; "WIDGET_CONTROL,timer=" feature. ; ACY, October 1993 Set RETAIN=2 for draw widget to prevent ; clipping by an overlapping window when ; loading frames. ; DMS, Dec, 1993 Added STOP and NFRAMES keywords to CW_ANIMATE_RUN. ; Added KILL_ANYWAY keyword to CW_ANIMATE_GETP. ; WSO, Jan, 1995 Added OPEN_FUNC keyword and updated UI. ; ACY, Jan, 1997 Added INFO_FILE keyword to allow user-supplied ; files for help text ;- PRO SetBitmapButtons, state compile_opt hidden COMMON BitmapButtons, $ reversebutton, blk_reversebutton, $ pausebutton, blk_pausebutton, $ playbutton, blk_playbutton, $ cycleForwardBtn, blk_cycleForwardBtn WIDGET_CONTROL, state.currentAction, SET_VALUE = state.currentBitmap IF state.framedelta EQ 0 THEN BEGIN ; paused WIDGET_CONTROL, state.wPauseButton, SET_VALUE = blk_pausebutton state.currentAction = state.wPauseButton state.currentBitmap = pausebutton ENDIF ELSE BEGIN IF state.framedelta GT 0 THEN BEGIN ; animating forward IF state.cycle THEN BEGIN WIDGET_CONTROL, state.wCyclePlayButton, SET_VALUE = blk_cycleForwardBtn state.currentAction = state.wCyclePlayButton state.currentBitmap = cycleForwardBtn ENDIF ELSE BEGIN WIDGET_CONTROL, state.wPlayButton, SET_VALUE = blk_playbutton state.currentAction = state.wPlayButton state.currentBitmap = playbutton ENDELSE ENDIF ELSE BEGIN ; animating backwards WIDGET_CONTROL, state.wReversePlayButton, SET_VALUE = blk_reversebutton state.currentAction = state.wReversePlayButton state.currentBitmap = reversebutton ENDELSE ENDELSE END PRO CW_ANIMATE_CLN, widget ; When the widget dies, clean up here. Widget is the ID of the ; *child* actually holding the state in its UVALUE. compile_opt hidden ; kills state stored in widget WIDGET_CONTROL, widget, GET_UVALUE = state, /NO_COPY IF (N_ELEMENTS(state) GT 0) THEN BEGIN IF (state.dont_kill_pixmaps EQ 0) THEN BEGIN pwin = *state.pwinHdl FOR i=0, N_ELEMENTS(pwin)-1 DO BEGIN IF (pwin[i] GE 0) THEN $ WDELETE, pwin[i] ENDFOR PTR_FREE, state.pwinHdl ENDIF ; Restore the state WIDGET_CONTROL, widget, SET_UVALUE = state, /NO_COPY ENDIF END FUNCTION CW_ANIMATE_EV, event compile_opt hidden ; Retrieve the structure from the child that contains the sub ids wAnimateBase = event.handler wTopBase = WIDGET_INFO(wAnimateBase, /CHILD) ;This kills the old uvalue WIDGET_CONTROL, wTopBase, GET_UVALUE = state, /NO_COPY ret = 0 CASE event.id OF wAnimateBase: $ IF (state.framedelta NE 0) THEN BEGIN ; Animation WIDGET_CONTROL, wAnimateBase, TIMER=state.delay curframe = state.curframe nframes = state.nframes curframe = curframe + state.framedelta ; New frame r = 0.0 IF state.cycle THEN BEGIN IF curframe LT 0 THEN BEGIN state.framedelta = 1 curframe = 0 t = systime(1) r = 2 * nframes / float(t-state.loop_start_t) ENDIF IF curframe GE nframes THEN BEGIN state.framedelta = -1 curframe = nframes-1 ENDIF ENDIF ELSE BEGIN WHILE curframe LT 0 DO $ curframe = curframe + nframes ; Into range WHILE curframe GE nframes DO $ curframe = curframe - nframes IF curframe EQ 0 THEN BEGIN ; Display rate? t = systime(1) r = nframes / FLOAT(t-state.loop_start_t) ; Rate in Frames/Sec ENDIF ENDELSE state.curframe = curframe IF r NE 0.0 THEN BEGIN ;Update time? WIDGET_CONTROL, state.wFramesPerSecValue, SET_VALUE = $ STRING(r, FORMAT='(f6.1)') state.loop_start_t = t ENDIF SWIN = !d.window WSET, state.draw_win ;Set to the drawing window pwin = Temporary(*state.pwinHdl) IF pwin[curframe] GE 0 THEN $ ;Next frame DEVICE, COPY =[0, 0, state.sizex, state.sizey, 0, 0, pwin[curframe]] IF state.track THEN $ WIDGET_CONTROL, state.wFramesIndicatorSlider, SET_VALUE = curframe *state.pwinHdl = Temporary(pwin) EMPTY WSET, SWIN ENDIF state.wFramesSpeedSlider : BEGIN ;New rate WIDGET_CONTROL, state.wFramesSpeedSlider, GET_VALUE = temp IF temp EQ 100 THEN $ state.delay=0. $ ELSE $ state.delay= 2./(1.+temp) END state.wFramesIndicatorSlider : BEGIN WIDGET_CONTROL, state.wFramesIndicatorSlider, GET_VALUE = temp IF (temp NE state.curframe) THEN BEGIN SWIN = !d.window WSET, state.draw_win state.curframe = temp pwin = Temporary(*state.pwinHdl) IF (pwin[temp] GE 0) THEN $ DEVICE, COPY = [0, 0, state.sizex, state.sizey, 0, 0, pwin[temp]] *state.pwinHdl = Temporary(pwin) EMPTY WSET, SWIN ENDIF END state.wPauseButton : $ IF (state.framedelta NE 0) THEN BEGIN WIDGET_CONTROL, state.wFramesIndicatorSlider, SET_VALUE = state.curframe WIDGET_CONTROL, state.wFramesIndicatorSlider, SENSITIVE = 1 WIDGET_CONTROL, state.wFramesPerSecValue, SET_VALUE = $ STRING(0.0, FORMAT='(f6.1)') state.framedelta = 0 SetBitmapButtons, state ENDIF state.wPlayButton : BEGIN WIDGET_CONTROL, state.wFramesIndicatorSlider, SENSITIVE = 0 WIDGET_CONTROL, state.wFramesSpeedSlider, SENSITIVE = 1 IF (state.framedelta EQ 0) THEN $ WIDGET_CONTROL, wAnimateBase, TIMER=state.delay state.framedelta = 1 state.cycle = 0 SetBitmapButtons, state END state.wReversePlayButton : BEGIN WIDGET_CONTROL, state.wFramesIndicatorSlider, SENSITIVE = 0 WIDGET_CONTROL, state.wFramesSpeedSlider, SENSITIVE = 1 IF (state.framedelta EQ 0) THEN $ WIDGET_CONTROL, wAnimateBase, TIMER=state.delay state.framedelta = -1 state.cycle = 0 SetBitmapButtons, state END state.wCyclePlayButton : BEGIN WIDGET_CONTROL, state.wFramesIndicatorSlider, SENSITIVE = 0 WIDGET_CONTROL, state.wFramesSpeedSlider, SENSITIVE = 1 IF (state.framedelta EQ 0) THEN $ WIDGET_CONTROL, wAnimateBase, TIMER=state.delay state.framedelta = 1 state.cycle = 1 SetBitmapButtons, state END state.wActiveSliderCheck: BEGIN state.track = event.select END state.wMPEGButton : BEGIN mpegFilename=DIALOG_PICKFILE(/WRITE) IF STRLEN(mpegFilename) GT 0 THEN BEGIN tmpBase=WIDGET_BASE(TITLE='Animation: Creating MPEG file', $ /FLOATING, MODAL=WIDGET_INFO(event.top, /MODAL), $ GROUP=event.top, TLB_FRAME_ATTR=27, /FRAME) tmpMsg = 'Writing MPEG data to the file:' tmpText=WIDGET_TEXT(tmpBase, $ XSIZE= ( STRLEN(tmpMsg) > STRLEN(mpegFilename) )+5, $ YSIZE=4, $ /ALIGN_CENTER, $ VALUE=['', tmpMsg, mpegFilename]) WIDGET_CONTROL, tmpBase, /REALIZE WIDGET_CONTROL, /HOURGLASS mpegID = OBJ_NEW('IDLgrMPEG', FILENAME = mpegFilename) saveWindow = !D.WINDOW DEVICE, GET_VISUAL_NAME=visualName CASE visualName OF 'TrueColor': BEGIN FOR i=0, N_ELEMENTS((*state.pwinHdl))-1 DO BEGIN IF (*state.pwinHdl)[i] GT -1 THEN BEGIN WSET, (*state.pwinHdl)[i] image= TVRD(TRUE=1) MPEG_PUT, mpegID, FRAME=i, IMAGE=image, /ORDER ENDIF ENDFOR END 'DirectColor': BEGIN FOR i=0, N_ELEMENTS((*state.pwinHdl))-1 DO BEGIN IF (*state.pwinHdl)[i] GT -1 THEN BEGIN WSET, (*state.pwinHdl)[i] directImage= TVRD(TRUE=1) IF (i EQ 0) THEN BEGIN ; Initialize image to match dimensions. image = directImage TVLCT, red, green, blue, /GET ENDIF ; Convert Direct Color image to True Color image. image[0,*,*] = red[directImage[0,*,*]] image[1,*,*] = green[directImage[1,*,*]] image[2,*,*] = blue[directImage[2,*,*]] MPEG_PUT, mpegID, FRAME=i, IMAGE=image, /ORDER ENDIF ENDFOR END ELSE: BEGIN FOR i=0, N_ELEMENTS((*state.pwinHdl))-1 DO BEGIN IF (*state.pwinHdl)[i] GT -1 THEN BEGIN WSET, (*state.pwinHdl)[i] indexedImage= TVRD() IF (i EQ 0) THEN BEGIN iDims = SIZE(indexedImage, /DIMENSIONS) image = BYTARR(3,iDims[0],iDims[1]) TVLCT, red, green, blue, /GET ENDIF ; Convert indexed image to True Color image. image[0,*,*] = red[indexedImage] image[1,*,*] = green[indexedImage] image[2,*,*] = blue[indexedImage] MPEG_PUT, mpegID, FRAME=i, IMAGE=image, /ORDER ENDIF ENDFOR END ENDCASE IF saveWindow GT -1 THEN WSET, saveWindow MPEG_SAVE, mpegID MPEG_CLOSE, mpegID WIDGET_CONTROL, tmpBase, /DESTROY ENDIF END ; ; Added 3/27/03 btf: ; state.wPNGButton : BEGIN if state.PNGpath eq '' then begin print,'Using cwd as PNGpath..' pngRoot=dialog_pickfile(get_path=PNGpath,title=$ 'Enter root file name for PNG file series') print,'Saving PNGpath ',PNGpath endif else begin print,'Using PNGpath ',state.PNGpath pngRoot=dialog_pickfile(path=state.PNGpath,get_path=PNGpath,title=$ 'Enter root file name for PNG file series') if PNGpath ne state.PNGpath then print,'Saving new PNGpath ',PNGpath endelse state.PNGpath = PNGpath if strlen(pngRoot) gt 0 then begin print,'pngRoot = ',pngRoot,' nframes=',n_elements((*state.pwinHdl)) version = float(!version.release) savewindow = !d.window device, get_visual_name=visualName print,'VisualName = ',visualName nframes = n_elements((*state.pwinHdl)) case visualName of 'TrueColor': begin for i=0, nframes-1 do begin if (*state.pwinHdl)[i] GT -1 then begin wset, (*state.pwinHdl)[i] image = tvrd(true=1) filename = pngRoot+string(format="(i3.3)",i)+'.png' print,format="('Frame ',i4,' of ',i4,': PNG file ',a)",$ i,nframes,filename write_png,filename,image,/verbose endif endfor end ; TrueColor 'DirectColor': begin for i=0, nframes-1 do begin if (*state.pwinHdl)[i] GT -1 then begin wset, (*state.pwinHdl)[i] directimage= tvrd(true=1) if (i eq 0) then begin image = directimage tvlct, red, green, blue, /get endif ; Convert direct color image to True Color image. image[0,*,*] = red[directimage[0,*,*]] image[1,*,*] = green[directimage[1,*,*]] image[2,*,*] = blue[directimage[2,*,*]] if version le 5.3 then begin image[0,*,*] = reverse(temporary(image[0,*,*]),3) image[1,*,*] = reverse(temporary(image[1,*,*]),3) image[2,*,*] = reverse(temporary(image[2,*,*]),3) endif filename = pngRoot+string(format="(i3.3)",i)+'.png' print,format="('Frame ',i4,' of ',i4,': PNG file ',a)",$ i,nframes,filename write_png,filename,image,/verbose endif endfor end ; DirectColor else: begin for i=0, nframes-1 do begin if (*state.pwinHdl)[i] GT -1 then begin wset, (*state.pwinHdl)[i] indexedImage= tvrd() if (i eq 0) then begin idims = size(indexedImage, /dimensions) image = bytarr(3,idims[0],idims[1]) tvlct, red, green, blue, /get endif ; Convert indexed image to True Color image. image[0,*,*] = red[indexedImage] image[1,*,*] = green[indexedImage] image[2,*,*] = blue[indexedImage] if version le 5.3 then begin image[0,*,*] = reverse(temporary(image[0,*,*]),3) image[1,*,*] = reverse(temporary(image[1,*,*]),3) image[2,*,*] = reverse(temporary(image[2,*,*]),3) endif filename = pngRoot+string(format="(i3.3)",i)+'.png' print,format="('Frame ',i4,' of ',i4,': PNG file ',a)",$ i,nframes,filename write_png,filename,image,/verbose endif endfor end endcase print,'Done writing PNG file series of animation.' endif ; got pngRoot end ; make PNG's ; state.wHelpButton : BEGIN ; If the information file is given, display this file. ; Otherwise, display the text shown below. IF (state.InfoFile NE '') THEN BEGIN XDisplayFile, state.InfoFile, $ DONE_BUTTON='Done', $ Title="Animation Help", $ Group=event.top, MODAL=WIDGET_INFO(event.top, /MODAL), $ Width=55, Height=16 ENDIF ELSE BEGIN XDISPLAYFILE, "animatedemo.hlp", TITLE = "Animation Help", $ GROUP = event.top, MODAL=WIDGET_INFO(event.top, /MODAL), $ WIDTH = 90, HEIGHT = 50, $ TEXT = [ $ " ", $ " The animation widget is used for displaying", $ "a series of images created with IDL as an animation. The", $ "user can select the speed, direction or specific frames in", $ "the animation.", $ " The top slider is used to control the speed of", $ "the animation. Moving it to the far right is one hundred", $ "percent, as fast as the animation can go. If there are", $ "other IDL widget applications using background tasks,", $ "they can slow down the animation. Closing the other", $ "applications can speed up the animation.", $ " The four bitmap buttons are reverse play, pause, ", $ "forward play and cycle. Use them to select a direction or to", $ "pause the animation and view specific framestate.", $ " The bottom slider is used to view single frames", $ "from the animation. The animation must be paused to ", $ "use the frame selection slider.", $ " The animation may saved as an MPEG file by", $ "selecting the 'Write MPEG' button. Note that this", $ "option is not available if IDL is running in timed", $ "demonstration mode (unlicensed)."," "," ",$ ; "'Write PNG' button (added March 2003, B. Foster):"," ",$ "Save a series of PNG ('Portable Network Graphics') files of",$ "the animation, one file per frame."," ",$ "When the pickfile dialog appears, enter a root file name for",$ "the PNG files. For example, if you enter '/dir1/myanim',",$ "then the files made will be named 'myanim000.png, myanim001.png,...'",$ "in the /dir1 directory."," ",$ "PNG (pronounced 'ping') is Portable Network Graphics",$ " (see http://www.libpng.org/pub/png/)",$ "MNG (pronounced 'ming') is Multi-image Network Graphics",$ " (see http://www.libpng.org/pub/mng/)"," ",$ "Both image formats are supported by ImageMagick:"," ",$ "Individual PNG files can be displayed with ImageMagick's display command:",$ " 'display mymovie000.png'",$ "To animate the PNG file series, use ImageMagick's animate command:",$ " 'animate *.png'",$ "To concatenate a PNG file series to a single file in MNG format,",$ " use ImageMagick's convert command, e.g. 'convert *.png movie.mng'",$ " and animate with 'animate movie.mng'",$ "The convert command can also be used to create a single GIF",$ " file of the animation, e.g. 'convert *.png movie.gif'"] ENDELSE END ; state.wAbortButton : BEGIN ; END state.wColorsButton : $ XLOADCT, GROUP = event.top, MODAL=WIDGET_INFO(event.top, /MODAL) state.wOpenButton : BEGIN ; get a copy before the structure is killed open_func = state.open_func framedelta = state.framedelta wFramesSpeedSlider = state.wFramesSpeedSlider ; Need to restore state since the following routines use it WIDGET_CONTROL, wTopBase, SET_UVALUE = state, /NO_COPY CW_ANIMATE_RUN, wAnimateBase, /STOP ; Disable all controls until all frames are loaded WIDGET_CONTROL, wAnimateBase, SENSITIVE = 0 fileOK = CALL_FUNCTION(open_func, event.top, wAnimateBase) IF fileOK THEN BEGIN WIDGET_CONTROL, wFramesSpeedSlider, GET_VALUE = rate IF framedelta EQ 0 THEN $ framedelta = 1 CW_ANIMATE_RUN, wAnimateBase, rate, DELTA=framedelta, /LASTFRAME ENDIF ELSE $ ; Disable all controls until all frames are loaded WIDGET_CONTROL, wAnimateBase, SENSITIVE = 1 ; Need structure back - This kills the old uvalue WIDGET_CONTROL, wTopBase, GET_UVALUE = state, /NO_COPY END state.wEndAnimationButton: $ ret = {ID:wAnimateBase, TOP:event.top, HANDLER:0L, action:"DONE" } ELSE: ENDCASE WIDGET_CONTROL, wTopBase, SET_UVALUE = state, /NO_COPY ; Restore the state RETURN, ret END pro CW_ANIMATE_LOAD, widget, IMAGE = image, FRAME = frame, ORDER = order, $ WINDOW = window, XOFFSET = xoffset, YOFFSET = yoffset, $ TRACK = track, CYCLE = cycle wTopBase = WIDGET_INFO(widget, /CHILD) WIDGET_CONTROL, wTopBase, GET_UVALUE = state, /NO_COPY pwin = Temporary(*state.pwinHdl) old_window = !D.WINDOW displayload = WIDGET_INFO(widget, /REALIZED) IF (displayload NE 0) THEN BEGIN WIDGET_CONTROL, GET_VALUE=temp, state.wImageArea state.draw_win = temp WSET, state.draw_win ; In case the draw widget size is different than that requested. state.sizex = !D.X_VSIZE state.sizey = !D.Y_VSIZE ENDIF ; Default values and range checking IF (N_ELEMENTS(yoffset) EQ 0) THEN $ yoffset = 0 IF (N_ELEMENTS(xoffset) EQ 0) THEN $ xoffset = 0 IF (N_ELEMENTS(frame)) GT 0 THEN BEGIN IF (frame LT 0) OR (frame GE N_ELEMENTS(pwin)) THEN $ MESSAGE, "Frame number must be from 0 to nframes -1." ENDIF ELSE $ frame=0 j = N_ELEMENTS(window) ;check to see if WINDOW was set IF (j GT 0) THEN BEGIN ;Copy image from window? IF (j LT 5) THEN BEGIN ;If coords not spec, use all WSET, window[0] p = [ window[0], 0, 0, !D.X_VSIZE, !D.Y_VSIZE ] ;Get size of window ENDIF ELSE $ p = window IF pwin[frame] LT 0 THEN BEGIN ;Create pixwin? WINDOW, /FREE, XSIZE = state.sizex, YSIZE = state.sizey, /PIXMAP pwin[frame] = !D.WINDOW ENDIF IF (p[3] GT state.sizex) OR (p[4] GT state.sizey) THEN $ MESSAGE, "Window parameter larger than setup" IF displayload THEN BEGIN ;Load display window WSET, state.draw_win ;Show it? IF state.draw_win NE p[0] THEN $ ;Copy to show window? DEVICE, COPY = [ p[1], p[2], p[3], p[4], xoffset, yoffset, p[0]] WSET, pwin[frame] ;Pixmap destination ;Copy from display window to pixmap DEVICE, COPY = [ xoffset, yoffset, p[3], p[4], xoffset, yoffset, $ state.draw_win ] ENDIF ELSE BEGIN ;load / no show WSET, pwin[frame] DEVICE, COPY = [ p[1], p[2], p[3], p[4], xoffset, yoffset, p[0]] ENDELSE EMPTY IF (N_ELEMENTS(state.draw_win) EQ 0) THEN $ state.draw_win = -1 WSET, old_window ; When displayload is set, the frame slider should update to show frame num IF displayload THEN $ WIDGET_CONTROL, state.wFramesIndicatorSlider, SET_VALUE = frame GOTO, Done ENDIF ;So WINDOW was not set. IF N_ELEMENTS(image) NE 0 THEN BEGIN ;Make sure image was set. ; When displayload is set, the draw widget should be updated ; to show the new frame being loaded and the frame slider ; should be set correspondingly IF (displayload NE 0) THEN BEGIN WIDGET_CONTROL, state.wFramesIndicatorSlider, SET_VALUE = frame WSET, state.draw_win TV, image EMPTY ENDIF ;Make sure the image is of a valid size and report if not. sz = SIZE(image) IF ((sz[0] NE 2) OR (sz[1] GT state.sizex) OR (sz[2] GT state.sizey)) THEN $ MESSAGE, "Image parameter must be 2D of size" + $ STRING(state.sizex)+ STRING(state.sizey) IF N_ELEMENTS(order) EQ 0 THEN $ ORDER = 0 ;Default order IF pwin[frame] LT 0 THEN BEGIN WINDOW, /FREE, xsize = state.sizex, ysize = state.sizey, /pixmap pwin[frame] = !D.WINDOW ENDIF ELSE $ WSET, pwin[frame] TV, image, xoffset, yoffset, ORDER = order EMPTY WSET, old_window GOTO, Done ENDIF ;End of "if IMAGE was set". Done: *state.pwinHdl = Temporary(pwin) WIDGET_CONTROL, wTopBase, SET_UVALUE = state, /NO_COPY ;Restore uvalue END ;CW_ANIMATE_LOAD pro CW_ANIMATE_RUN, widget, rate, STOP = stop, NFRAMES = nframes, $ DELTA = delta, LASTFRAME=lastFrame wTopBase = WIDGET_INFO(widget, /CHILD) WIDGET_CONTROL, wTopBase, GET_UVALUE = state, /NO_COPY old_window = !D.WINDOW ;Save old window ; Refuse to run if the cluster isn't realized. IF (WIDGET_INFO(widget, /REALIZED) EQ 0) THEN $ MESSAGE,'Animation widget must be realized before it can run' IF KEYWORD_SET(stop) THEN BEGIN ;Stop the animation state.framedelta = 0 ;This shows we've stopped. WIDGET_CONTROL, state.wFramesIndicatorSlider, SET_VALUE = state.curframe WIDGET_CONTROL, state.wFramesIndicatorSlider, SENSITIVE = 1 SetBitmapButtons, state GOTO, done ENDIF ; It is realized now, so get the draw widget window ID. WIDGET_CONTROL, GET_VALUE=temp, state.wImageArea state.draw_win = temp WSET, temp EMPTY WIDGET_CONTROL, widget, /SENSITIVE WIDGET_CONTROL, state.wFramesIndicatorSlider, SENSITIVE = 0 IF N_ELEMENTS(nframes) GT 0 THEN BEGIN ;Nframes spec? pwin = Temporary(*state.pwinHdl) IF nframes GT N_ELEMENTS(pwin) THEN $ MESSAGE, 'Run called with too many frames' *state.pwinHdl = Temporary(pwin) state.nframes = nframes ENDIF ;Set up the initial values used by the background task IF N_ELEMENTS(lastFrame) NE 0 THEN $ state.curframe = state.nframes $ ELSE $ state.curframe = 0 IF N_ELEMENTS(delta) NE 0 THEN $ state.framedelta = -1 > delta < 1 $ ; In range? ELSE $ state.framedelta = 1 IF N_ELEMENTS(rate) NE 0 THEN BEGIN rate = 0 > rate < 100 ; In range? WIDGET_CONTROL, state.wFramesSpeedSlider, SET_VALUE = rate ENDIF ELSE $ rate = 100 IF rate EQ 100 THEN $ state.delay=0.0 $ ELSE $ state.delay = 2./(1.+rate) SetBitmapButtons, state state.loop_start_t = SYSTIME(1) ;Start of loop time WIDGET_CONTROL, widget, TIMER=state.delay, EVENT_FUNC = 'CW_ANIMATE_EV' done: WIDGET_CONTROL, wTopBase, SET_UVALUE = state, /NO_COPY ;Rewrite state WSET, old_window END pro CW_ANIMATE_GETP, widget, PIXMAPS, KILL_ANYWAY = kill_anyway ; Return the vector of pixmap ID's associated with the animation ; widget in named variable PIXMAPSTATE. Frames without a pixmap contain a -1. ; This routine should not be called until all the frames are loaded, or the ; vector will not be complete. It should be called before the call to ; CW_ANIMATE_RUN. ; ; Note: Normally, the animation widget destroys its pixmaps when it ; is destroyed. If this routine is called however, the pixmaps ; are not destroyed. Cleanup becomes the responsibility of the ; caller. wTopBase = WIDGET_INFO(widget, /CHILD) WIDGET_CONTROL, wTopBase, GET_UVALUE = state, /NO_COPY pwin = Temporary(*state.pwinHdl) pixmaps = pwin *state.pwinHdl = Temporary(pwin) IF KEYWORD_SET(kill_anyway) EQ 0 THEN $ state.dont_kill_pixmaps = 1 WIDGET_CONTROL, wTopBase, SET_UVALUE = state, /NO_COPY END PRO CW_ANIMATE_INIT, wAnimateBase, sizex, sizey, nframes, PIXMAPS=old_pixmaps ON_ERROR, 2 ;return to caller wTopBase = WIDGET_INFO(wAnimateBase, /CHILD) WIDGET_CONTROL, wTopBase, GET_UVALUE = state, /NO_COPY nparams = N_PARAMS() IF (nparams LT 3) OR (nparams GT 4) THEN $ MESSAGE, 'Incorrect number of arguments' IF NOT (KEYWORD_SET(uval)) THEN $ uval = 0 n = N_ELEMENTS(old_pixmaps) IF (n GT 0) THEN BEGIN nframes = n pwin = old_pixmaps ENDIF ELSE $ pwin = REPLICATE(-1, nframes) ;Array of window indices IF (nframes LE 1) THEN $ MESSAGE, "Animations must have 2 or more frames" ; save the number of frames to animate in the animation structure state.nframes = nframes ; save the Pixmap array to animate in the animation structure IF PTR_VALID(state.pwinHdl) THEN BEGIN ; Need to temporarily restore state since the following routine uses it WIDGET_CONTROL, wTopBase, SET_UVALUE = state, /NO_COPY CW_ANIMATE_CLN, wTopBase ; Need structure back - This kills the widget uvalue WIDGET_CONTROL, wTopBase, GET_UVALUE = state, /NO_COPY ENDIF state.pwinHdl = PTR_NEW(pwin, /NO_COPY) WIDGET_CONTROL, state.wFramesIndicatorSlider, SET_SLIDER_MAX = nframes - 1 IF state.wImageArea NE 0 THEN BEGIN ; to avoid flash - only set the size if it changes IF (state.sizex NE sizex OR state.sizey NE sizey) THEN $ WIDGET_CONTROL, state.wImageArea, XSIZE =sizex, YSIZE=sizey ENDIF ELSE BEGIN wImageBase = WIDGET_BASE(wTopBase, /COLUMN) ;To prevent stretching state.wImageArea = WIDGET_DRAW(wImageBase, XSIZE =sizex, YSIZE=sizey, $ XOFFSET = 280, YOFFSET = 20, RETAIN = 2) ENDELSE ; save the x dimensions of draw widget in the animation structure state.sizex = sizex ; save the y dimensions of draw widget in the animation structure state.sizey = sizey ; Disable all controls until all frames are loaded WIDGET_CONTROL, wAnimateBase, SENSITIVE = 0 WIDGET_CONTROL, wTopBase, SET_UVALUE = state, /NO_COPY END ; Setup the play reverse, pause, play forward and cycle pushbutton bitmaps. These ; variables reside in the "BitmapButtons" common block. ; Both a depressed (blk_) and a not-depressed version are needed for each button. PRO InitBitmapButtons compile_opt hidden COMMON BitmapButtons reversebutton = [[000B, 000B, 000B], [000B, 032B, 000B], [000B, 048B, 000B],$ [000B, 056B, 000B], [000B, 060B, 000B], [000B, 046B, 000B], $ [000B, 231B, 015B], [144B, 003B, 024B], [016B, 231B, 027B], $ [080B, 238B, 027B], [208B, 060B, 026B], [208B, 056B, 026B], $ [208B, 048B, 026B], [208B, 032B, 026B], [208B, 000B, 026B], $ [208B, 000B, 026B], [208B, 255B, 027B], [016B, 000B, 024B], $ [240B, 255B, 031B], [224B, 255B, 015B], [000B, 000B, 000B], $ [000B, 000B, 000B], [000B, 000B, 000B], [000B, 000B, 000B] ] blk_reversebutton = [[255B, 255B, 255B], [255B, 223B, 255B],[255B, 207B, 255B],$ [255B, 199B, 255B], [255B, 195B, 255B], [255B, 209B, 255B], $ [255B, 024B, 240B], [111B, 252B, 231B], [239B, 024B, 228B], $ [175B, 017B, 228B], [047B, 195B, 229B], [047B, 199B, 229B], $ [047B, 207B, 229B], [047B, 223B, 229B], [047B, 255B, 229B], $ [047B, 255B, 229B], [047B, 000B, 228B], [239B, 255B, 231B], $ [015B, 000B, 224B], [031B, 000B, 240B], [255B, 255B, 255B], $ [255B, 255B, 255B], [255B, 255B, 255B], [255B, 255B, 255B] ] pausebutton = [[000B, 000B, 000B], [000B, 000B, 000B], [000B, 000B, 000B], $ [192B, 195B, 003B], [192B, 194B, 002B], [192B, 194B, 002B], $ [192B, 194B, 002B], [192B, 194B, 002B], [192B, 194B, 002B], $ [192B, 194B, 002B], [192B, 194B, 002B], [192B, 194B, 002B], $ [192B, 194B, 002B], [192B, 194B, 002B], [192B, 194B, 002B], $ [192B, 194B, 002B], [192B, 194B, 002B], [192B, 194B, 002B], $ [192B, 194B, 002B], [192B, 195B, 003B], [192B, 195B, 003B], $ [000B, 000B, 000B], [000B, 000B, 000B], [000B, 000B, 000B] ] blk_pausebutton = [[255B, 255B, 255B], [255B, 255B, 255B], [255B, 255B, 255B],$ [063B, 060B, 252B], [063B, 061B, 253B], [063B, 061B, 253B], $ [063B, 061B, 253B], [063B, 061B, 253B], [063B, 061B, 253B], $ [063B, 061B, 253B], [063B, 061B, 253B], [063B, 061B, 253B], $ [063B, 061B, 253B], [063B, 061B, 253B], [063B, 061B, 253B], $ [063B, 061B, 253B], [063B, 061B, 253B], [063B, 061B, 253B], $ [063B, 061B, 253B], [063B, 060B, 252B], [063B, 060B, 252B], $ [255B, 255B, 255B], [255B, 255B, 255B], [255B, 255B, 255B] ] playbutton = [[000B, 000B, 000B], [000B, 004B, 000B], [000B, 012B, 000B], $ [000B, 028B, 000B], [000B, 060B, 000B], [000B, 116B, 000B], $ [240B, 231B, 000B], [024B, 192B, 009B], [216B, 231B, 008B], $ [216B, 119B, 010B], [088B, 060B, 011B], [088B, 028B, 011B], $ [088B, 012B, 011B], [088B, 004B, 011B], [088B, 000B, 011B], $ [088B, 000B, 011B], [216B, 255B, 011B], [024B, 000B, 008B], $ [248B, 255B, 015B], [240B, 255B, 007B], [000B, 000B, 000B], $ [000B, 000B, 000B], [000B, 000B, 000B], [000B, 000B, 000B] ] blk_playbutton = [[255B, 255B, 255B], [255B, 251B, 255B], [255B, 243B, 255B],$ [255B, 227B, 255B], [255B, 195B, 255B], [255B, 139B, 255B], $ [015B, 024B, 255B], [231B, 063B, 246B], [039B, 024B, 247B], $ [039B, 136B, 245B], [167B, 195B, 244B], [167B, 227B, 244B], $ [167B, 243B, 244B], [167B, 251B, 244B], [167B, 255B, 244B], $ [167B, 255B, 244B], [039B, 000B, 244B], [231B, 255B, 247B], $ [007B, 000B, 240B], [015B, 000B, 248B], [255B, 255B, 255B], $ [255B, 255B, 255B], [255B, 255B, 255B], [255B, 255B, 255B] ] cycleForwardBtn = [[000B, 000B, 000B], [000B, 000B, 000B], [000B, 128B, 000B], $ [000B, 128B, 001B], [000B, 128B, 003B], [248B, 255B, 006B], $ [008B, 000B, 012B], [008B, 000B, 024B], [248B, 255B, 012B], $ [248B, 255B, 006B], [000B, 128B, 003B], [000B, 129B, 001B], $ [128B, 129B, 000B], [192B, 001B, 000B], [096B, 255B, 015B], $ [048B, 000B, 008B], [024B, 000B, 008B], [048B, 255B, 015B], $ [096B, 255B, 015B], [192B, 001B, 000B], [128B, 001B, 000B], $ [000B, 001B, 000B], [000B, 000B, 000B], [000B, 000B, 000B] ] blk_cycleForwardBtn = [[255B, 255B, 255B], [255B, 255B, 255B], [255B, 127B, 255B], $ [255B, 127B, 254B], [255B, 127B, 252B], [007B, 000B, 249B], $ [247B, 255B, 243B], [247B, 255B, 231B], [007B, 000B, 243B], $ [007B, 000B, 249B], [255B, 127B, 252B], [255B, 126B, 254B], $ [127B, 126B, 255B], [063B, 254B, 255B], [159B, 000B, 240B], $ [207B, 255B, 247B], [231B, 255B, 247B], [207B, 000B, 240B], $ [159B, 000B, 240B], [063B, 254B, 255B], [127B, 254B, 255B], $ [255B, 254B, 255B], [255B, 255B, 255B], [255B, 255B, 255B] ] END function CW_ANIMATE, parent, sizex, sizey, nframes, $ UVALUE=uval, $ PIXMAPS=old_pixmaps, TRACK = track, CYCLE=cycle, DRAW = draw, $ NO_KILL = no_kill, OPEN_FUNC=open_func, $ INFO_FILE= infoFile, $ UNAME = uname COMMON BitmapButtons ON_ERROR, 2 ;return to caller ; Set the bitmaps for the bitmap buttons InitBitmapButtons nparams = N_PARAMS() IF (nparams LT 3) OR (nparams GT 4) THEN $ MESSAGE, 'Incorrect number of arguments' IF NOT (KEYWORD_SET(infoFile)) THEN $ infoFile = '' IF NOT (KEYWORD_SET(uval)) THEN $ uval = 0 IF NOT (KEYWORD_SET(open_func)) THEN $ open_func = 0 IF NOT (KEYWORD_SET(uname)) THEN $ uname = 'CW_ANIMATE_UNAME' wAnimateBase = WIDGET_BASE(parent, /COLUMN, UNAME=uname) wTopBase = WIDGET_BASE(wAnimateBase, /ROW) wControlBase = WIDGET_BASE(wTopBase, /COLUMN, /FRAME); XPAD=10, YPAD=10, SPACE=20) wVCRButtonBase = WIDGET_BASE(wControlBase, /ROW) wReversePlayButton = WIDGET_BUTTON(wVCRButtonBase, VALUE = reversebutton, $ UNAME=uname+'_REVERSE') wPauseButton = WIDGET_BUTTON(wVCRButtonBase, VALUE = blk_pausebutton, $ UNAME=uname+'_PAUSE') wPlayButton = WIDGET_BUTTON(wVCRButtonBase, VALUE = playbutton, $ UNAME=uname+'_PLAY') wCyclePlayButton = WIDGET_BUTTON(wVCRButtonBase, VALUE = cycleForwardBtn, $ UNAME=uname+'_CYCLEPLAY') currentAction = wPauseButton currentBitmap = pausebutton wSpeedBase = WIDGET_BASE(wControlBase, /COLUMN) wSpeedBaseLabel = WIDGET_LABEL(wSpeedBase, VALUE = "Animation Speed:", $ /ALIGN_LEFT) wFramesSpeedBase = WIDGET_BASE(wSpeedBase, TITLE = "Animation Speed", /COLUMN, /FRAME) wFramesPerSecBase = WIDGET_BASE(wFramesSpeedBase, /ROW) wFramesPerSecLabel = WIDGET_LABEL(wFramesPerSecBase, VALUE = "Frames/Sec: ") ; reserve space for value - format used for updating value is f6.1 wFramesPerSecValue = WIDGET_LABEL(wFramesPerSecBase, VALUE = '0.0000') wFramesSpeedSlider = WIDGET_SLIDER(wFramesSpeedBase, /DRAG, VALUE = 100, $ MAXIMUM = 100, MINIMUM = 0, /SUPPRESS_VALUE, UNAME=uname+'_FRAMESSPEED') wFrameBase = WIDGET_BASE(wControlBase, /COLUMN) wFrameBaseLabel = WIDGET_LABEL(wFrameBase, VALUE = "Animation Frame:", $ /ALIGN_LEFT) wFrameIndicatorBase = WIDGET_BASE(wFrameBase, TITLE = "Animation Frame", $ /COLUMN, /FRAME) wFramesIndicatorSlider = WIDGET_SLIDER(wFrameIndicatorBase, /DRAG, VALUE = 0, $ MAXIMUM = nframes - 1, MINIMUM = 0, UNAME=uname+'_FRAMESINDICATOR') wActiveSliderCheck = CW_BGROUP(wFrameIndicatorBase, ['Active Slider'], $ FRAME = 0, /NONEXCLUSIVE, /RETURN_INDEX, $ SET_VALUE=KEYWORD_SET(track),UNAME=uname+'_ACTIVESLIDER') wButtonBase = WIDGET_BASE(wControlBase, /COLUMN, /ALIGN_LEFT) IF KEYWORD_SET(no_kill) THEN $ wEndAnimationButton = 0L $ ELSE $ wEndAnimationButton = WIDGET_BUTTON(wButtonBase, VALUE='End Animation', $ UNAME=uname+'_ENDANIMATION') wColorsButton = WIDGET_BUTTON(wButtonBase, VALUE='Colors...', $ UNAME=uname+'_COLORS') ; desensitize the XLOADCT button for visual classes which don't support ; automatic updating of the graphics after changing the colormap cmapApplies=COLORMAP_APPLICABLE(redrawRequired) IF ((cmapApplies LE 0) OR $ ((cmapApplies GT 0) AND (redrawRequired GT 0))) THEN BEGIN WIDGET_CONTROL, wColorsButton, SENSITIVE=0 ENDIF IF (KEYWORD_SET(open_func)) THEN $ wOpenButton = WIDGET_BUTTON(wButtonBase, VALUE='Open...', $ UNAME=uname+'_OPEN') $ ELSE $ wOpenButton = 0 wMPEGButton = WIDGET_BUTTON(wButtonBase, VALUE='Write MPEG', $ UNAME=uname+'_MPEG') if (LMGR(/DEMO)) then WIDGET_CONTROL, wMPEGButton, SENS=0 wPNGButton = WIDGET_BUTTON(wButtonBase, VALUE='Write PNG', $ UNAME=uname+'_PNG') wHelpButton = WIDGET_BUTTON(wButtonBase, VALUE='Help', $ UNAME=uname+'_HELP') ;wAbortButton = WIDGET_BUTTON(wButtonBase, VALUE='Abort',$ ; UNAME=uname+'_ABORT') IF N_ELEMENTS(draw) EQ 1 THEN $ wImageArea = draw $ ELSE $ wImageArea = 0 ; Set the event handler function. This cluster does not get or set a value ; Make sure it lingers so the cleanup routine can get at its state. WIDGET_CONTROL, wAnimateBase, SET_UVALUE = uval, EVENT_FUNC = 'CW_ANIMATE_EV', $ /DELAY_DESTROY ;pwin = REPLICATE(-1, nframes) ;Array of window indices ; This structure gets stuffed into the uval. of the first child ; of wAnimateBase WIDGET_CONTROL, wTopBase, SET_UVALUE = $ { wEndAnimationButton: wEndAnimationButton, $ ; End button wColorsButton: wColorsButton, $ ; Adjust color palette button wOpenButton: wOpenButton, $ ; Open file button open_func: open_func, $ ; Open file function wMPEGButton: wMPEGButton, $ ; MPEG button wPNGButton: wPNGButton, $ ; PNG button PNGpath:'', $ ; dir path for PNG files wHelpButton: wHelpButton, $ ; Help button ; wAbortButtton: wAbortButton, $ ; Abort button wActiveSliderCheck: wActiveSliderCheck, $ ; button group widget wFramesSpeedSlider: wFramesSpeedSlider, $ ; Speed selection slider wReversePlayButton: wReversePlayButton, $ ; Reverse button wPauseButton: wPauseButton, $ ; Stop (pause) button wPlayButton: wPlayButton, $ ; Forward button wCyclePlayButton: wCyclePlayButton, $ ; Cycle forward button currentAction : currentAction, $ ; current action button id currentBitmap : currentBitmap, $ ; current button bitmap wFramesIndicatorSlider: wFramesIndicatorSlider, $; Frame selection slider wFramesPerSecValue: wFramesPerSecValue, $ ; Animation rate display wImageArea: wImageArea, $ ; Draw widget for animation draw_win: -1, $ ; Window # of draw widget sizex: 0, sizey: 0, $ ; Dimensions of draw widget nframes: 0, $ ; # of frames in animation curframe: 0, $ cycle: KEYWORD_SET(cycle), $ ; Ne 0 to cycle track: KEYWORD_SET(track), $ ; Ne 0 to track with slider framedelta: 0, $ ; # frames to step. delay: 0.0D, $ ; Delay between frames loop_start_t: 0.0D, $ ; System time at start dont_kill_pixmaps: 0, $ ; TRUE if pixmaps preserved on kill infoFile: infoFile, $ ; Information text file name pwinHdl: PTR_NEW() } ; handle to the Pixmap array ; When the child holding the state gets killed, have a cleanup ; procedure called to mop up WIDGET_CONTROL, wTopBase, KILL_NOTIFY = 'CW_ANIMATE_CLN' CW_ANIMATE_INIT, wAnimateBase, sizex, sizey, nframes, PIXMAPS=old_pixmaps RETURN, wAnimateBase END