Viewing contents of file '../idllib/contrib/fanning/slice.pro'
;+
; NAME:
;	SLICE
;
; PURPOSE:
;
;	This is a routine to display a 3D data set and slice through it
;       at arbitrary orthographic angles. The display window (when run
;       with the GUI) is resizeable. The output can be sent either to
;       the display, to a GUI, or to a PostScript printer (as true
;       PostScript output, not a screen dump.) When run with the GUI,
;       slices can be animated and displays can be saved and restored
;       for later processing. The program works on 8-bit, 16-bit, and
;       24-bit displays.
;
; AUTHOR:
;       FANNING SOFTWARE CONSULTING
;       David Fanning, Ph.D.
;       2642 Bradbury Court
;       Fort Collins, CO 80521 USA
;       Phone: 970-221-0438  
;       E-mail: davidf@dfanning.com
;       Coyote's Guide to IDL Programming: http://www.dfanning.com
;
; CATEGORY:
;
;	3D Graphics, Widgets.
;
; CALLING SEQUENCE:
;
;       For display in a regular IDL graphics window:
;
;          SLICE, data
;
;       For display with a built-in GUI:
;
;          SLICE, data, /GUI
;
;       To send display to a PostScript printer:
;
;          deviceKeywords = PS_FORM(/INITIALIZE)
;          SLICE, data, POSTSCRIPT=deviceKeywords
;
; INPUTS:
;
;	data:	The 3D data set to be displayed. The data is assumed to be 
;          scaled into the number of colors (keyword NCOLORS). 
;
; KEYWORD PARAMETERS:
;
;       AX      The rotation of the 3D coordinate system about the X axis.
;               Must be between 0 and 90 degrees. Default is 30 degrees.
;
;       AZ      The rotation of the 3D coordinate system about the Z axis.
;               Must be between -30 and 210 degrees. Default is 30 degrees.
;
;       BACKGROUND   The color index of the plot background. Default is BOTTOM. 
;
;       BOTTOM   The starting index of the color table. Default is 0.
;
;       COLOR    The color index of the plot axes and annotation. Default is
;                (NCOLORS-1) + BOTTOM.
;
;       CHARSIZE   The size of the plot annotations. Default is 1.0.
;
;       _EXTRA   This keyword allows other undefined keywords to be passed
;                to the SURFACE command. For example: XTITLE, YTITLE, ZTITLE,
;                and TITLE keywords.
;
;       GROUP    The group leader of the GUI. Normally used with GUI.
;
;       GUI      Set this keyword if you want a graphical user interface to the
;                program functionality.
;
;       NCOLORS  The number of colors used in the color table. The default is
;                !D.N_COLORS.
;
;       NOAXES   Set this keyword to draw the plot without axes.
;
;       POSTSCRIPT   This will be a strucuture of the type returned by PS_FORM.
;                The fields of the structure are used to congfigue the PostScript device.
;
;       THISINSTANCE   Multiple versions of the GUI program can be running concurrently.
;                This keyword is used to make sure they can be managed by XMANAGER
;                concurrently. The keyword is NOT normally set by the user.
;
;       TRANSPARENT   This keyword sets the transparency threshold for the 3D volume.
;                allowed values go from 0 to 100 percent. The default is 0.
;
;       WID      The window index number of the window you wish the display to go into.
;                In non-GUI mode, the window does not have to currently be open. This
;                keyword is ignored in GUI mode.
;
;       WTITLE   The window title. Used in conjunction with THISINSTANCE to label
;                control and graphics windows. It can be set by the user.
;
;       XCOORD   The X coordinate of the 3D data set. The data will be sliced at this 
;                coordinate in the X direction. The default is XSIZE / 2.
; 
;       XSLICE   Set this keyword to draw an slice through the data in the X direction.
;
;       YCOORD   The Y coordinate of the 3D data set. The data will be sliced at this 
;                coordinate in the Y direction. The default is YSIZE / 2.
; 
;       YSLICE   Set this keyword to draw an slice through the data in the Y direction.
;
;       ZCOORD   The Z coordinate of the 3D data set. The data will be sliced at this 
;                coordinate in the Z direction. The default is ZSIZE / 2.
; 
;       ZSLICE   Set this keyword to draw an slice through the data in the Z direction.
;
;       
; OUTPUTS:
;
;	None.
;
; COMMON BLOCKS:
;	
;       None.
;
; RESTRICTIONS:
;
;	Requires modified IDL library routines CW_ANIMATE and XINTERANIMATE
;       which have been added to this code. Requires the programs PS_FORM,
;       XCOLORS, and CHGCOLOR from the Coyote Software Library.
;
;         ftp://ftp.frii.com/pub/dfanning/outgoing/idl_examples
;
; EXAMPLE:
;
;      To run this program with a GUI, a charcoal background color, axes
;      drawn in yellow and with the X and Y axes shown, type:
;	
;      filename = FILEPATH(SUBDIR=['examples', 'data'], 'abnorm.dat')
;      OPENR, lun, filename, /GET_LUN
;      heart = BYTARR(64,64,15)
;      READU, lun, heart
;      FREE_LUN, lun
;      LOADCT, 5, NCOLORS=150, BOTTOM=0
;      TVLCT, [70, 255], [70, 255], [70, 0], 150
;      SLICE, heart, /GUI, BACKGROUND=150, COLOR=151, NCOLORS=150, $
;         BOTTOM=0, /XSLICE, /YSLICE
;
;      The program code comes with its own example program. Type:
;
;          IDL> .Compile SLICE
;          IDL> example
;
; MODIFICATION HISTORY:
; 	Written by:	David Fanning, Sept 1996.
;       31 Oct 96: Made slight changes in how PostScript output is handled.
;       23 April 1997. Fixed a bug that prevented PostScript output when
;          the size was expressed in centimeters. DWF.
;       23 April 1997. Added support for 24-bit color displays. DWF
;-

FORWARD_FUNCTION SLICE_CW_ANIMATE

PRO SLICE_XINTAnim_kill_pix
; If there are pixmaps currently open, free them.

  COMMON SLICE_XINTERAnimate_com, topbase, animatebase, pwin

  i = size(pwin)
  if (i(0) ne 0) then begin		; Not scalar, so contains valid IDs
    i = i(i(0) + 2)			; # of elements in pwin
    FOR j=0, i-1 DO IF pwin(j) GE 0 THEN WDELETE, pwin(j) ;Delete the windows
  pwin = -1		;Show nothing there by setting to scalar value
  endif

end








PRO SLICE_XINTAnim_event, ev
; The only event that can be seen by this application is the "DONE"
; event from the CW_ANIMATION cluster.

  widget_control, /destroy, ev.top
END







PRO SLICE_XINTERAnimate, RATE, SET = SET, IMAGE = IMAGE, FRAME = FRAME, $
	ORDER = ORDER, CLOSE = CLOSE, TITLE = TITLE, $
	SHOWLOAD = SHOWLOAD, GROUP = GROUP, WINDOW = WINDOW, $
	XOFFSET = XOFFSET, YOFFSET = YOFFSET, KEEP_PIXMAPS=KEEP_PIXMAPS, $
	CYCLE = cycle, TRACK = track, BOTTOM=bottom, NCOLORS=ncolors

COMMON SLICE_XINTERAnimate_com, topbase, animatebase, pwin
IF N_ELEMENTS(bottom) EQ 0 THEN bottom = 0
IF N_ELEMENTS(ncolors) EQ 0 THEN ncolors = !D.N_COLORS


;--------------------- CLOSE Portion of SLICE_XINTERanimate -------------------------

  IF KEYWORD_SET(CLOSE) THEN BEGIN
    if (widget_info(topbase, /valid)) THEN $
	  WIDGET_CONTROL, topbase, /DESTROY
    SLICE_XINTAnim_kill_pix
    RETURN
  ENDIF

  ;Don't allow two copies of SLICE_XINTERnimate to run at once
  IF xregistered("SLICE_XINTERAnimate") THEN begin
    XANNOUNCE, 'SLICE_XINTERANIMATE', 'Only one animation at a time is allowed.'
    return
  endif



;----------------------  SET Portion of SLICE_XINTERanimate ------------------------

  IF KEYWORD_SET(SET) THEN BEGIN

    ;This is the first call to SLICE_XINTERanimate.  Here the pixmap is
    ;	created and the widgets are initialized.

    SLICE_XINTAnim_kill_pix				;If old pixmap exists, delete 

    IF NOT(KEYWORD_SET(TITLE)) THEN TITLE = "SLICE_XINTERAnimate"
    topbase = WIDGET_BASE(TITLE = TITLE)
    animatebase = SLICE_CW_ANIMATE(topbase, set(0), set(1), set(2), $
		TRACK = KEYWORD_SET(track), CYCLE=KEYWORD_SET(cycle), $
		BOTTOM=bottom, NCOLORS=ncolors)

    ; If the SHOWLOAD keyword is set, realize things now so the load is seen
    IF KEYWORD_SET(SHOWLOAD) THEN $
      WIDGET_CONTROL, topbase, /REALIZE, /HOURGLASS

    RETURN
  ENDIF

;-----------------  IMAGE Loading Portion of SLICE_XINTERanimate --------------------

  nwindow = N_ELEMENTS(WINDOW)
  nimage = N_ELEMENTS(image)
  if (nwindow gt 0) or (nimage gt 0) then begin
    old_window = !D.WINDOW				;Save old window

    ; Make sure a widget has been created before trying to load it.
    if (not widget_info(topbase, /valid)) then MESSAGE, 'Not initialized'

    IF (N_ELEMENTS(YOFFSET) EQ 0) THEN YOFFSET = 0
    IF (N_ELEMENTS(XOFFSET) EQ 0) THEN XOFFSET = 0

    if (N_ELEMENTS(WINDOW) gt 0) then begin
      SLICE_CW_ANIMATE_LOAD, animatebase, frame=frame, window=window, $
		XOFFSET = XOFFSET, YOFFSET = YOFFSET
    endif else begin
      IF (N_ELEMENTS(ORDER) EQ 0) THEN ORDER = 0
      SLICE_CW_ANIMATE_LOAD, animatebase, frame=frame, image=image, $
		XOFFSET = XOFFSET, YOFFSET = YOFFSET, ORDER = ORDER
    endelse
    IF (old_window GE 0) THEN WSET, old_window
    RETURN
  ENDIF

;---------------  Register and Run Portion of SLICE_XINTERanimate -------------------

  ; If the base is not valid, it means that we have skipped
  ; calling this routine with the SET keyword. In this case, we can restart
  ; the last animation if the KEEP_PIXMAP keyword was used to preserve them
  ; in the previous call.

  if (not widget_info(topbase, /valid)) then begin
    s = size(pwin)
    if (s(0) eq 0) then message, 'No image frames loaded'
    ; Scan the pixmaps to figure out the image size
    n = s(s(0) + 2)
    xs = 0
    for i = 0, n-1 do begin
      if pwin(i) ne -1 then begin
        old_window = !D.WINDOW
	wset, pwin(i)
        xs = !d.x_vsize
        ys = !d.y_vsize
	IF (old_window GE 0) THEN WSET, old_window
        goto, found		; Like a "break" in C
      endif
    endfor
  found:
    if (xs ne 0) then begin
      IF NOT(KEYWORD_SET(TITLE)) THEN TITLE = 'SLICE_XINTERAnimate'
      topbase = WIDGET_BASE(TITLE = TITLE)
      animatebase = SLICE_CW_ANIMATE(topbase, xs, ys, 0, PIXMAPS=pwin, $
         NCOLORS=ncolors, BOTTOM=bottom)
      ; The pixmaps are no longer our responsibility. They will get destroyed
      ; by SLICE_CW_ANIMATE as usual unless this invocation of SLICE_XINTERANIMATE
      ; specifies the KEEP_PIXMAP keyword. 
      pwin = 0			; Indicate that we aren't saving them anymore.
    endif else message, 'No image frames loaded'
  endif


  ; At this point, the application must be realized if it isn't already
  if (not widget_info(topbase, /realized)) then $
    WIDGET_CONTROL, topbase, /REALIZE

  ; Save the pixmaps for later restart
  if keyword_set(keep_pixmaps) then SLICE_CW_ANIMATE_GETP, animatebase, pwin

  if N_ELEMENTS(RATE) EQ 0 THEN RATE = 100
  SLICE_CW_ANIMATE_run, animatebase, rate

  Xmanager, "SLICE_XINTERAnimate", topbase, EVENT_HANDLER = "SLICE_XINTAnim_event", $
	  GROUP_LEADER = GROUP
END


PRO SLICE_SETBITmapButtons, state

  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 SLICE_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.

     ; 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
         HANDLE_VALUE, state.pwinHdl, pwin
         FOR i=0, N_ELEMENTS(pwin)-1 DO BEGIN
            IF (pwin(i) GE 0) THEN $
               WDELETE, pwin(i)
         ENDFOR
         HANDLE_FREE, state.pwinHdl
      ENDIF
        ; Restore the state
      WIDGET_CONTROL, widget, SET_UVALUE = state, /NO_COPY
   ENDIF
END





FUNCTION SLICE_CW_ANIMATE_EV, event

     ; 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
            WSET, state.draw_win                  ;Set to the drawing window
            HANDLE_VALUE, state.pwinHdl, pwin, /NO_COPY
            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
            HANDLE_VALUE, state.pwinHdl, pwin, /SET, /NO_COPY
            EMPTY
         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
            WSET, state.draw_win
            state.curframe = temp
            HANDLE_VALUE, state.pwinHdl, pwin, /NO_COPY
            IF (pwin(temp) GE 0) THEN $
               DEVICE, COPY = [0, 0, state.sizex, state.sizey, 0, 0, pwin(temp)]
            HANDLE_VALUE, state.pwinHdl, pwin, /SET, /NO_COPY
            EMPTY
         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
            SLICE_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
         SLICE_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
         SLICE_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
         SLICE_SETBITmapButtons, state
         END

      state.wActiveSliderCheck: BEGIN
         state.track = event.select
         END

      state.wHelpButton : $
         XDISPLAYFILE, "animatedemo.hlp", TITLE = "Animation Help", $
            GROUP = event.top, WIDTH = 55, HEIGHT = 16, $
            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."   ]

      state.wColorsButton : $
         XCOLORS, GROUP = event.top, NCOLORS=state.ncolors, BOTTOM=state.bottom

      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
         SLICE_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
            SLICE_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 SLICE_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
   HANDLE_VALUE, state.pwinHdl, pwin, /NO_COPY

   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

      IF (old_window GE 0) THEN $
         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
      IF (old_window GE 0) THEN $
         WSET, old_window
      GOTO, Done
   ENDIF                                    ;End of "if IMAGE was set".

  Done: HANDLE_VALUE, state.pwinHdl, pwin, /SET, /NO_COPY

   WIDGET_CONTROL, wTopBase, SET_UVALUE = state, /NO_COPY  ;Restore uvalue

END ;SLICE_CW_ANIMATE_LOAD




pro SLICE_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
      SLICE_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?
      HANDLE_VALUE, state.pwinHdl, pwin, /NO_COPY
      IF nframes GT N_ELEMENTS(pwin) THEN $
         MESSAGE, 'Run called with too many frames'
      HANDLE_VALUE, state.pwinHdl, pwin, /SET, /NO_COPY
      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)

   SLICE_SETBITmapButtons, state

   state.loop_start_t = SYSTIME(1)            ;Start of loop time
   WIDGET_CONTROL, widget, TIMER=state.delay, EVENT_FUNC = 'SLICE_CW_ANIMATE_EV'
 done:
   WIDGET_CONTROL, wTopBase, SET_UVALUE = state, /NO_COPY      ;Rewrite state
END


pro SLICE_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
; SLICE_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
   HANDLE_VALUE, state.pwinHdl, pwin, /NO_COPY
   pixmaps = pwin
   HANDLE_VALUE, state.pwinHdl, pwin, /SET, /NO_COPY
   IF KEYWORD_SET(kill_anyway) EQ 0 THEN $
      state.dont_kill_pixmaps = 1
   WIDGET_CONTROL, wTopBase, SET_UVALUE = state, /NO_COPY
END


PRO SLICE_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 HANDLE_INFO(state.pwinHdl) THEN BEGIN
       ; Need to temporarily restore state since the following routine uses it
      WIDGET_CONTROL, wTopBase, SET_UVALUE = state, /NO_COPY
      SLICE_CW_ANIMATE_CLN, wTopBase
       ; Need structure back - This kills the widget uvalue
      WIDGET_CONTROL, wTopBase, GET_UVALUE = state, /NO_COPY
   ENDIF

   state.pwinHdl = HANDLE_CREATE()
   HANDLE_VALUE, state.pwinHdl, pwin, /SET, /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 SLICE_INITBITmapButtons

  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 SLICE_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, NCOLORS=ncolors, BOTTOM=bottom

  COMMON BitmapButtons

   ON_ERROR, 2                                    ;return to caller

      ; Set the bitmaps for the bitmap buttons
   SLICE_INITBITmapButtons

   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
   IF NOT (KEYWORD_SET(open_func)) THEN $
      open_func = 0
   IF N_ELEMENTS(ncolors) EQ 0 THEN ncolors = !D.N_COLORS
   IF N_ELEMENTS(bottom) EQ 0 THEN bottom = 0
   wAnimateBase = WIDGET_BASE(parent, /COLUMN)
   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)
   wPauseButton = WIDGET_BUTTON(wVCRButtonBase, VALUE = blk_pausebutton)
   wPlayButton = WIDGET_BUTTON(wVCRButtonBase, VALUE = playbutton)
   wCyclePlayButton = WIDGET_BUTTON(wVCRButtonBase, VALUE = cycleForwardBtn)
   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:")
   wFramesPerSecValue = WIDGET_LABEL(wFramesPerSecBase, VALUE = '0.000')
   wFramesSpeedSlider = WIDGET_SLIDER(wFramesSpeedBase, /DRAG, VALUE = 100, $
             MAXIMUM = 100, MINIMUM = 0, /SUPPRESS_VALUE)

   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)
   wActiveSliderCheck = CW_BGROUP(wFrameIndicatorBase, ['Active Slider'], $
           FRAME = 0, /NONEXCLUSIVE, /RETURN_INDEX, $
           SET_VALUE=KEYWORD_SET(track))

   wButtonBase = WIDGET_BASE(wAnimateBase, /ROW, /ALIGN_LEFT)
   IF KEYWORD_SET(no_kill) THEN $
      wEndAnimationButton = 0L $
   ELSE $
      wEndAnimationButton = WIDGET_BUTTON(wButtonBase, VALUE='End Animation')
  ; wColorsButton = WIDGET_BUTTON(wButtonBase, VALUE='Colors...')
  wcolorsbutton = -1L
   IF (KEYWORD_SET(open_func)) THEN $
      wOpenButton = WIDGET_BUTTON(wButtonBase, VALUE='Open...') $
   ELSE $
      wOpenButton = 0
   wHelpButton = WIDGET_BUTTON(wButtonBase, VALUE='Help')

   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 = 'SLICE_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
        wHelpButton: wHelpButton, $                      ; Help 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, $
        ncolors:ncolors, $                               ; Number of colors to load
        bottom:bottom, $                                 ; Starting index of colors
        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
        pwinHdl: 0L }                                    ; 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 = 'SLICE_CW_ANIMATE_CLN'

   SLICE_CW_ANIMATE_INIT, wAnimateBase, sizex, sizey, nframes, PIXMAPS=old_pixmaps

   RETURN, wAnimateBase

END


PRO SLICE_CHARSIZE_EVENT, event
WIDGET_CONTROL, event.top, GET_UVALUE=info
CASE event.id OF
   info.cancel: BEGIN
      HANDLE_VALUE, info.ptr, -1, /SET
      WIDGET_CONTROL, event.top, /DESTROY
      END
    info.accept: BEGIN
      WIDGET_CONTROL, info.slider, GET_VALUE=thisValue
      val = (thisValue / 100.0) * 5.0
      HANDLE_VALUE, info.ptr, val, /SET
      WIDGET_CONTROL, event.top, /DESTROY
      END
    ELSE: BEGIN
      WIDGET_CONTROL, info.slider, GET_VALUE=thisValue
      val = (thisValue / 100.0) * 5.0
      val = STRTRIM(val, 2)
      val = STRING(val, FORMAT='(F4.2)')
     WIDGET_CONTROL, info.labelID, SET_VALUE=val
    END
ENDCASE 
END


FUNCTION SLICE_CHARSIZE, initValue, LABEL=label, XOFFSET=xoffset, $
   YOFFSET=yoffset

IF N_PARAMS() EQ 0 THEN initValue = 1.0
IF N_ELEMENTS(label) EQ 0 THEN label='Change Character Size'

   ; Find the center of the display.
   
DEVICE, GET_SCREEN_SIZE=screenSize
xCenter = FIX(screenSize(0) / 2.0)
yCenter = FIX(screenSize(1) / 2.0)

IF N_ELEMENTS(xoffset) EQ 0 THEN xoffset = xCenter - 75
IF N_ELEMENTS(yoffset) EQ 0 THEN yoffset = yCenter - 50

tlb = WIDGET_BASE(COLUMN=1, TLB_FRAME_ATTR=1, XOFFSET=xoffset, $
   YOFFSET=yoffset)
labelID = WIDGET_LABEL(tlb, VALUE=label)
sliderValue = FIX((initValue / 5.0) * 100)
title = STRTRIM(initValue, 2)
title = STRING(title, FORMAT='(F4.2)')
slider = WIDGET_SLIDER(tlb, VALUE=sliderValue, MAX=100, MIN=0, $
   /SUPPRESS_VALUE)
labelID = WIDGET_LABEL(tlb, VALUE=title, /DYNAMIC_RESIZE)

buttonBase = WIDGET_BASE(tlb, ROW=1, FRAME=1)
cancel = WIDGET_BUTTON(buttonBase, Value='Cancel')
accept = WIDGET_BUTTON(buttonBase, Value='Accept')

ptr = HANDLE_CREATE()

info = { ptr:ptr, $
         slider:slider, $
         cancel:cancel, $
         accept:accept, $
         labelID:labelID}
        
WIDGET_CONTROL, tlb, /REALIZE, SET_UVALUE=info

XMANAGER, 'SLICE_CHARSIZE', tlb, /MODAL
HANDLE_VALUE, ptr, value
HANDLE_FREE, ptr
IF value LT 0 OR N_ELEMENTS(value) EQ 0 THEN RETURN, -1 ELSE RETURN, value
END



PRO SLICE_Integer_Only, event

; This event handler for text widgets only allows integers to be entered
; in the text widget. A maximum of four digits are allowed.

   ; Deal with simple one-character insertion events.
   
IF event.type EQ 0 THEN BEGIN

      ; Get the current text in the widget and find its length.
   
   Widget_Control, event.id, Get_Value=text
   text = text(0)
   length = StrLen(text)

      ; Only react if the insertion character is a number and there
      ; are less than four characters already in the widget.
   
   IF Byte(event.ch) GE 48B AND Byte(event.ch) LE 57B AND length LT 4 THEN BEGIN
   
         ; Get the current text selection.
   
      selection = Widget_Info(event.id, /Text_Select)

         ; Insert the character at the proper location.
      
      Widget_Control, event.id, /Use_Text_Select, Set_Value=String(event.ch)
   
         ; Update the current insertion point in the text widget.
      
      Widget_Control, event.id, Set_Text_Select=event.offset + 1
   
   ENDIF
   
ENDIF ; of insertion event

   ; Deal with deletion events.
   
IF event.type EQ 2 THEN BEGIN

      ; Get the current text in widget.
   
   Widget_Control, event.id, Get_Value=text
   text = text(0)
   length = StrLen(text)
   
      ; Put it back with the deletion subtracted.
      
   Widget_Control, event.id, Set_Value=StrMid(text, 0, length-event.length)
   
      ; Reset the text insertion point in the text widget.
      
   Widget_Control, event.id, Set_Text_Select=event.offset 
ENDIF

END ; of SLICE_Integer_Only event handler *************************************************



PRO SLICE_NULL_EVENTS, event

   ; The purpose of this event handler is to do nothing and ignore all events that
   ; come to it.

END ; of SLICE_NULL_EVENTS event handler ***************************************************



PRO SLICE_GETIMAGE_EVENT, event

   ; The only events that can come here are button events.
   
   ; Get the info structure out of the user value of the top-level base.
   
Widget_Control, event.top, Get_UValue=info

   ; There may be errors we can't anticipate. Catch them here, alert the
   ; user as to what the error was, and exit the event handler without 
   ; doing any damage.

Catch, error
IF error NE 0 THEN BEGIN
   ok = Widget_Message(!Err_String)
   RETURN
ENDIF

   ; Which button caused this event?
   
Widget_Control, event.id, Get_Value=buttonValue

CASE buttonValue OF

   'Pick Filename': BEGIN
   
         ; Start in the directory listed in the directory text widget.
         ; Convert the text value to a scalar.
                       
      Widget_Control, info.dirnameID, Get_Value=startDirectory
      startDirectory = startDirectory(0)
                    
         ; If this directory doesn't exist, use the current directory.
                       
      test = Findfile(startDirectory, Count=foundfile)
      IF foundfile NE 1 THEN CD, Current=startDirectory
            
         ; Use PICKFILE to pick a name.
                   
      pick = Pickfile(Path=startDirectory, /NoConfirm, Get_Path=path, Filter='*.dat')
      
         ; Set the directory text widget with the name of the directory.
         ; Make sure the user didn't cancel out of PICKFILE.
         
      IF pick NE '' THEN BEGIN
      
            ; Find the lengths of the PICK and the PATH.

         pathLen = StrLen(path)
         picklen = StrLen(pick)

           ; Shorten the PATH to take off last file separator.
                
         path = StrMid(path,0,pathLen-1)
            
            ; Put the PATH in the directory location.

         Widget_Control, info.dirnameID, Set_Value=path
                    
            ; Set the filename text widget with the name of the file.
                       
         filename = StrMid(pick, pathlen, picklen-pathlen)
         Widget_Control, info.filenameID, Set_Value=filename
         
      ENDIF
         
      END ; of the Pick Filename button case
                    
    'Cancel': BEGIN
    
         ; Have to exit here gracefully. Null filename means "CANCEL".
         
      formdata = {filename:''}
      Handle_Value, info.ptrToFormData, formdata, /Set
      
         ; Out of here!
         
      Widget_Control, event.top, /Destroy
      END ; of the Cancel button case
                    
    'Accept': BEGIN  ; Gather the form information.
    
          ; Put the directory and filename together to make a path.
    
       Widget_Control, info.dirnameID, Get_Value=directory
       Widget_Control, info.filenameID, Get_Value=file
       
       filename = Filepath(Root_Dir=directory(0),file(0))
       
          ; Get the size and header info. Remember these are STRINGS!
       
       Widget_Control, info.headerID, Get_Value=header
       Widget_Control, info.xsizeID, Get_Value=xsize
       Widget_Control, info.ysizeID, Get_Value=ysize
       Widget_Control, info.frameID, Get_Value=frames
       
       header = Fix(header(0))
       xsize =  Fix(xsize(0))
       ysize =  Fix(ysize(0))
       frames =  Fix(frames(0))
       
          ; Get the data type from the droplist widget.
       
       listIndex = Widget_Info(info.droplistID, /Droplist_Select)
       datatype = info.datatypes(listIndex)
       
          ; Get the format index from the formatlist widget.
       
       formatIndex = Widget_Info(info.formatlistID, /Droplist_Select)
        
       
          ; Create the formdata structure from the information you collected.
       
       formdata = {header:header, xsize:xsize, ysize:ysize, frames:frames, $
          filename:filename, datatype:datatype, formatIndex:formatIndex}
       
          ; Store the formdata in the pointer location.
          
       Handle_Value, info.ptrToFormData, formdata, /Set
    
         ; Out of here!
         
      Widget_Control, event.top, /Destroy
      END ; of the Accept button case
                    
ENDCASE
   
END ; of SLICE_GETIMAGE_EVENT event handler ************************************************




FUNCTION SLICE_GETIMAGE, filename, Directory=directory, XSize=xsize, YSize=ysize, $
   Frames=frames, Header=header, XDR=xdr, XOffSet=xoffset, YOffSet=yoffset

   ; This is a function to specify the size, data type, and header information
   ; about an image that you would like to read. It reads the data and returns
   ; it as the result of the function. If an error occurs, the function returns a 0.
   
   ; Check for parameters and keywords.
   
IF N_Params() EQ 0 THEN filename='ctscan.dat'

   ; If DIRECTORY keyword is not used, use the "training" directory.
   ; If that is not found, use the current directory.
   
IF N_Elements(directory) EQ 0 THEN BEGIN

   startDirectory = Filepath('training')
   junk = Findfile(startDirectory, Count=foundfile)
   IF foundfile EQ 0 THEN CD, Current=startDirectory
   
ENDIF ELSE startDirectory = directory

   ; Check for size and header keywords. These probably come in as 
   ; numbers and you need strings to put them into text widgets.

IF N_Elements(xsize) EQ 0 THEN xsize='256' ELSE xsize=StrTrim(xsize,2)
IF N_Elements(ysize) EQ 0 THEN ysize='256' ELSE ysize=StrTrim(ysize,2)
IF N_Elements(frames) EQ 0 THEN frames='0' ELSE frames=StrTrim(frames,2)
IF N_Elements(header) EQ 0 THEN header='0' ELSE header=StrTrim(header,2)

   ; Find the center of the display.
   
Device, Get_Screen_Size=screenSize
xCenter = Fix(screenSize(0) / 2.0)
yCenter = Fix(screenSize(1) / 2.0)

IF N_Elements(xoffset) EQ 0 THEN xoffset = xCenter - 275
IF N_Elements(yoffset) EQ 0 THEN yoffset = yCenter - 150


   ; Create a top-level base.

tlb = Widget_Base(Column=1, Title='Read Image Data', XOffSet=xoffset, $
   YOffSet=yoffset)

   ; Create the directory widgets.
   
dirnamebase = Widget_Base(tlb, Row=1)
   dirnamelabel = Widget_Label(dirnamebase, Value='Directory:')
   dirnameID = Widget_Text(dirnamebase, Value=startDirectory, /Editable, $
      Event_Pro='SLICE_NULL_EVENTS', XSize=Fix(2.0*StrLen(startDirectory) > 50))
      
   ; Create the filename widgets.
         
filenamebase = Widget_Base(tlb, Row=1)
   filenamelabel = Widget_Label(filenamebase, Value='Filename:')
   filenameID = Widget_Text(filenamebase, Value=filename, /Editable, $
      Event_Pro='SLICE_NULL_EVENTS', XSize=2*StrLen(filename) > 20)
      
   ; Create a button to allow user to pick a filename.

pickbutton = Widget_Button(filenamebase, Value='Pick Filename')

   ; Create a droplist widget to select file data types.
 
database = Widget_Base(tlb, Row=1)
   datatypes = ['Byte', 'Integer', 'Long', 'Float']
   droplistID = Widget_Droplist(database, Value=datatypes, $
      Title='Data Type: ', Event_Pro='SLICE_NULL_EVENTS')
      
   ; Create a droplist widget to select file formats.
   
   formatlistID = Widget_Droplist(database, Value=['None', 'XDR'], $
      Title='File Format: ', Event_Pro='SLICE_NULL_EVENTS')
      
   ; Create a text widget to accept a header size.
   
   headlabel = Widget_Label(database, Value='Header Size:')
   headerID = Widget_Text(database, Value=header, All_Events=1, Editable=0, $
      Event_Pro='SLICE_Integer_Only', XSize=8)
      
   ; Create widgets to gather the required file sizes.
   
sizebase = Widget_Base(tlb, Row=1)
   xlabel = Widget_Label(sizebase, Value='X Size:')
   xsizeID = Widget_Text(sizebase, Value=xsize, All_Events=1, Editable=0, $
      Event_Pro='SLICE_Integer_Only', XSize=8)
   ylabel = Widget_Label(sizebase, Value='Y Size:')
   ysizeID = Widget_Text(sizebase, Value=ysize, All_Events=1, Editable=0, $
      Event_Pro='SLICE_Integer_Only', XSize=8)
   zlabel = Widget_Label(sizebase, Value='Frames:')
   frameID = Widget_Text(sizebase, Value=frames, All_Events=1, Editable=0, $
      Event_Pro='SLICE_Integer_Only', XSize=8)
      
   ; Create cancel and accept buttons.
      
cancelbase = Widget_Base(tlb, Column=2, Frame=1)
   cancel = Widget_Button(cancelbase, Value='Cancel')
   accept = Widget_Button(cancelbase, Value='Accept')
  
   ; Realize the program.
  
Widget_Control, tlb, /Realize

   ; Create a pointer to store the information collected from the form.
  
ptrToFormData = Handle_Create()

   ; Set the correct file format in the format droplist widget.
   
Widget_Control, formatlistID, Set_Droplist_Select=Keyword_Set(xdr)

   ; Set the text insertion point at the end of the filename text widget.
   
tip = [StrLen(filename),0]
Widget_Control, filenameID, Input_Focus=1
Widget_Control, filenameID, Set_Text_Select=tip

   ; Create an info structure with program information.

info = { filenameID:filenameID, $
         dirnameID:dirnameID, $
         xsizeID:xsizeID, $
         ysizeID:ysizeID, $
         frameID:frameID, $
         headerID:headerID, $
         droplistID:droplistID, $
         formatlistID:formatlistID, $
         datatypes:datatypes, $
         ptrToFormData:ptrToFormData}
         
  ; Store the info structure in the user value of the top-level base.
         
Widget_Control, tlb, Set_UValue=info

   ; The form will be a MODAL widget. This means we STOP at the
   ; following XManager call until the widget is destroyed.
         
XManager, 'SLICE_GETIMAGE', tlb, Event_Handler='SLICE_GETIMAGE_EVENT', /Modal

   ; Get the form data that was collected by the form.
   
Handle_Value, ptrToFormData, formdata

   ; If there is nothing here. Free the pointer and return.
   
IF N_Elements(formdata) EQ 0 THEN BEGIN
   Handle_Free, ptrToFormData
   RETURN, 0
ENDIF

   ; Did the user cancel out of the form? If so, return a 0.
   
IF formdata.filename EQ '' THEN BEGIN
   Handle_Free, ptrToFormData
   RETURN, 0
ENDIF

   ; Make the proper sized image array. Check for success.

image = 0
IF formdata.frames EQ 0 THEN $
   command = 'image = Make_Array(formdata.xsize, formdata.ysize, ' + $
      formdata.datatype + '=1)' ELSE $
   command = 'image = Make_Array(formdata.xsize, ' + $
      'formdata.ysize, formdata.frames, ' + formdata.datatype + '=1)'
      
check = Execute(command)
IF check EQ 0 THEN BEGIN
   ok = Widget_Message("Problem making image array. Returning 0.")
   RETURN, 0
ENDIF

   ; We can have all kinds of trouble reading data. Let's catch all
   ; input and output errors and alert user without crashing the program!

Catch, error
IF error NE 0 THEN BEGIN

      ; If we can't read the file for some reason, let the user know
      ; why, free the pointer and its information, check the logical
      ; unit number back in if it is checked out, and return a 0.

   ok = Widget_Message(!Err_String)
   Handle_Free, ptrToFormData
   IF N_ELements(lun) NE 0 THEN Free_Lun, lun
   RETURN, 0
ENDIF

   ; Read the data file.
   
IF formdata.header GT 0 THEN header = BytArr(formdata.header)
Get_Lun, lun
OpenR, lun, formdata.filename, XDR=formdata.formatIndex
IF formdata.header EQ 0 THEN ReadU, lun, image $
   ELSE ReadU, lun, header, image
Free_Lun, lun
   
   ; Free the pointer.
  
Handle_Free, ptrToFormData

RETURN, image

END ; of SLICE_GETIMAGE program ******************************************************



FUNCTION SLICE_WHAT_BUTTON_TYPE, event

   ; Checks event.type to find out what kind of button
   ; was clicked in a draw widget. This is NOT an event handler.

type = ['DOWN', 'UP', 'MOTION', 'SCROLL']
RETURN, type(event.type)
END ;*******************************************************************




FUNCTION SLICE_WHAT_BUTTON_PRESSED, event

   ; Checks event.press to find out what kind of button
   ; was pressed in a draw widget.  This is NOT an event handler.

button = ['NONE', 'LEFT', 'MIDDLE', 'NONE', 'RIGHT']
RETURN, button(event.press)
END ;*******************************************************************



FUNCTION SLICE_DELAY_NOTICE, question

   ; Default question.
   
IF N_PARAMS() EQ 0 THEN question = 'There will be a short delay ...'

   ; Put up in the middle of the display. NOT a modal widget.
   
DEVICE, GET_SCREEN_SIZE=screenSize

tlb = WIDGET_BASE(XOFFSET=screenSize(0)/2.0 - 75, YOFFSET=screenSize(1)/ 3.0)
label = WIDGET_LABEL(tlb, VALUE=question)

WIDGET_CONTROL, tlb, /REALIZE
RETURN, tlb
END ; of SLICE_DELAY_NOTICE utility routine **********************************



PRO SLICE_DRAW_MOTION_EVENTS, event

   ; Get info structure.

WIDGET_CONTROL, event.top, GET_UVALUE=tlb
WIDGET_CONTROL, tlb, GET_UVALUE=info, /NO_COPY

   ; What type of an event is this?

thisButtonType = SLICE_WHAT_BUTTON_TYPE(event)

   ; Is this a button UP event?
   
IF thisButtonType EQ 'UP' THEN BEGIN

      ; If this is an UP event, you need to erase the slice, turn motion 
      ; events OFF, set thedraw widget's event handler back to 
      ; SLICE_DRAW_BUTTON_EVENTS, and redraw the display with the new
      ; slice.
      
      ; Erase the slice one final time by copying from the pixmap.
     
   WSET, info.wid
   DEVICE, COPY = [0, 0, info.pixXsize, info.pixYsize, 0, 0, info.pixmapID]
   
      ; Delete the pixmap.
      
   WDELETE, info.pixmapID
   info.pixmapID = -1

      ; Turn motion events off.
   
    WIDGET_CONTROL, event.id, DRAW_MOTION_EVENTS=0, $
       EVENT_PRO='SLICE_DRAW_BUTTON_EVENTS'
 
   ; Set correct transformation matrix.
   
!P.T = info.pt

   ; Get the current coodinates in 3D space.
     
coords = CONVERT_COORD(event.x, event.y, /DEVICE, /TO_DATA, /T3D)
x=coords(0)
y=coords(1)

   ; Which slice are we talking about. Set the coordinates.
   
CASE info.interactive OF

   'LEFT': BEGIN
      x = FIX(x)
      info.xcoord = x > 0
      info.xcoord = info.xcoord < info.maxx
      WIDGET_CONTROL, info.xcoordID, SET_VALUE=STRTRIM(x,2)
      END
            
   'RIGHT': BEGIN
      y = FIX(y)
      info.ycoord = y > 0
      info.ycoord = info.ycoord < info.maxy
      WIDGET_CONTROL, info.ycoordID, SET_VALUE=STRTRIM(y,2)
      END
      
   ELSE:  
ENDCASE

   ; Call drawing procedure.
   
   IF NOT WIDGET_INFO(info.drawID, /VALID_ID) THEN $
      SLICE_GUI_MAKE_WINDOW, info, XSIZE=400, YSIZE=400
   WSET, info.wid

   HANDLE_VALUE, info.dataptr, data, /NO_COPY
   SLICE, data, XCOORD=info.xcoord, YCOORD=info.ycoord, $
      ZCOORD=info.zcoord, XSLICE=info.xslice, $
      YSLICE=info.yslice, ZSLICE=info.zslice, AX=info.ax, $
      AZ=info.az, WID=info.wid, NCOLORS=info.ncolors, $
      BOTTOM=info.bottom, TRANSPARENT=info.transparent, $
      BACKGROUND=info.background, COLOR=info.color, $
      CHARSIZE=info.charscale, NOAXES=info.noaxes, $
      _EXTRA=info.extra
   HANDLE_VALUE, info.dataptr, data, /SET, /NO_COPY  

      ; Put the info structure back into its storage location and then, out of here!
      
   WIDGET_CONTROL, tlb, SET_UVALUE=info, /NO_COPY
   RETURN
ENDIF ; thisEvent = UP

   ; Most of the action in this event handler occurs here while we are waiting
   ; for an UP event to occur. As long as we don't get it, keep erasing the
   ; old slice and drawing a new one.
   
   ; Erase the old slice.
   
WSET, info.wid
DEVICE, COPY = [0, 0, info.pixXsize, info.pixYsize, 0, 0, info.pixmapID]

   ; Set correct transformation matrix.
   
!P.T = info.pt

   ; Get the current coodinates in 3D space.
   
coords = CONVERT_COORD(event.x, event.y, /DEVICE, /TO_DATA, /T3D)
x=coords(0)
y=coords(1)

   ; Which button was pushed down? Draw the proper slice. 
   ; Left button = X slice, Middle button = Z slice, 
   ; Right button = Y slice.
   
CASE info.interactive OF

   'LEFT': BEGIN
      x = FIX(x)
      x = x > 0
      x = x < info.maxx
      PLOTS, [x, x, x, x, x], [0, info.maxy, info.maxy, 0, 0], $
         [0, 0, info.maxz, info.maxz, 0], /T3D, COLOR=info.color
      XYOUTS, 0.05, 0.05, 'X Coordinate = ' + STRTRIM(x,2), /NORMAL, $
         SIZE=info.charscale, COLOR=info.color
      END
            
   'RIGHT': BEGIN
      y = FIX(y)
      y = y > 0
      y = y < info.maxy
      PLOTS, [0, info.maxx, info.maxx, 0, 0], [y,y,y,y,y], $
         [0, 0, info.maxz, info.maxz, 0], /T3D, COLOR=info.color
      XYOUTS, 0.05, 0.05, 'Y Coordinate = ' + STRTRIM(y, 2), /NORMAL, $
         SIZE=info.charscale, COLOR=info.color
      END
   
   ELSE: 
ENDCASE
       
   ; Put the info structure back into its storage location.
   
WIDGET_CONTROL, tlb, SET_UVALUE=info, /NO_COPY
END ; of SLICE_DRAW_MOTION_EVENTS event handler **************************************



PRO SLICE_DRAW_BUTTON_EVENTS, event
thisButtonType = SLICE_WHAT_BUTTON_TYPE(event)

   ; Is this a button down event? If not, return.
   
IF thisButtonType NE 'DOWN' THEN RETURN

   ; Don't want to handle middle mouse buttons.
   
buttonPressed = SLICE_WHAT_BUTTON_PRESSED(event)
IF buttonPressed EQ 'MIDDLE' THEN RETURN

   ; Get info structure.

WIDGET_CONTROL, event.top, GET_UVALUE=tlb
WIDGET_CONTROL, tlb, GET_UVALUE=info, /NO_COPY

   ; Create a pixmap for the window.

WIDGET_CONTROL, info.graphics_tlb, TLB_GET_SIZE=gsize   
WINDOW, /FREE, XSIZE=gsize(0), YSIZE=gsize(1), /PIXMAP
info.pixmapID = !D.WINDOW
info.pixXsize = gsize(0)
info.pixYsize = gsize(1)

   ; Draw the plot here with no axes.
   
HANDLE_VALUE, info.dataptr, data, /NO_COPY
SLICE, data, XCOORD=info.xcoord, YCOORD=info.ycoord, $
  ZCOORD=info.zcoord, XSLICE=info.xslice, $
  YSLICE=info.yslice, ZSLICE=info.zslice, AX=info.ax, $
  AZ=info.az, WID=info.wid, NCOLORS=info.ncolors, $
  BOTTOM=info.bottom, TRANSPARENT=info.transparent, $
  BACKGROUND=info.background, COLOR=info.color, $
  CHARSIZE=info.charscale, NOAXES=1, $
  _EXTRA=info.extra
HANDLE_VALUE, info.dataptr, data, /SET, /NO_COPY  

   ; Set correct transformation matrix.
   
info.pt = !P.T

   ; Draw 3D box.

oldDevice = !D.NAME   
SET_PLOT, 'Z'

PLOTS, [0, 0, info.maxx, info.maxx, 0], [0, info.maxy, info.maxy, 0, 0], $
   [0, 0, 0, 0, 0], /T3D, COLOR=info.color
PLOTS, [0, 0, info.maxx, info.maxx, 0], [0, info.maxy, info.maxy, 0, 0], $
   [info.maxz, info.maxz, info.maxz, info.maxz, info.maxz], /T3D, COLOR=info.color
PLOTS, [0, 0, 0, 0, 0], [0, info.maxy, info.maxy, 0, 0], $
   [0, 0, info.maxz, info.maxz, 0], /T3D, COLOR=info.color
PLOTS, [info.maxx, info.maxx, info.maxx, info.maxx, info.maxx], $
   [0, info.maxy, info.maxy, 0, 0], [0, 0, info.maxz, info.maxz, 0], $
   /T3D, COLOR=info.color
picture = TVRD()
SET_PLOT, oldDevice
WSET, info.pixmapID
TV, picture

   ; Do the same thing in the display window.
   
WSET, info.wid
TV, picture
   
   ; Get the current coodinates in 3D space.
   
coords = CONVERT_COORD(event.x, event.y, /DEVICE, /TO_DATA, /T3D)
x=coords(0)
y=coords(1)

   ; Which button was pushed down? Draw the proper slice. 
   ; Left button = X slice, Right button = Y slice.
   
info.interactive = SLICE_WHAT_BUTTON_PRESSED(event)

CASE info.interactive OF

   'LEFT': BEGIN
      x=info.xcoord
      PLOTS, [x, x, x, x, x], [0, info.maxy, info.maxy, 0, 0], $
         [0, 0, info.maxz, info.maxz, 0], /T3D, COLOR=info.color
      END
      
   'RIGHT': BEGIN
      y = info.ycoord
      PLOTS, [0, info.maxx, info.maxx, 0, 0], [y,y,y,y,y], $
         [0, 0, info.maxz, info.maxz, 0], /T3D, COLOR=info.color
      END
      
   ELSE:
ENDCASE
      
WIDGET_CONTROL, info.drawID, EVENT_PRO='SLICE_DRAW_MOTION_EVENTS', $
   DRAW_MOTION_EVENTS=1
WIDGET_CONTROL, tlb, SET_UVALUE=info, /NO_COPY
END ; of SLICE_DRAW_BUTTON_EVENTS event handler ***************************      
      


PRO SLICE_GUI_SAVE_DISPLAY, event
WIDGET_CONTROL, event.top, GET_UVALUE=info, /NO_COPY

   ; Get a copy of the data.
   
HANDLE_VALUE, info.dataptr, data, /NO_COPY
oldData = data
oldInfo = info
HANDLE_VALUE, info.dataptr, data, /NO_COPY, /SET

   ; Open a file to save the data and the info structure.
   
filename = 'slice' + STRTRIM(info.thisInstance, 2) + '.slc'
filename = PICKFILE(/WRITE, FILE=filename)
IF filename EQ '' THEN BEGIN
   WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
   RETURN
ENDIF

SAVE, oldData, oldinfo, FILE=filename

WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
END ; of SLICE_GUI_SAVE_DISPLAY event handler *********************



PRO SLICE_GUI_RESTORE_DISPLAY, event
WIDGET_CONTROL, event.top, GET_UVALUE=info, /NO_COPY

   ; Open a file to get the data and the info structure.
   
filename = PICKFILE(FILTER='*.slc')
IF filename EQ '' THEN BEGIN
   WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
   RETURN
ENDIF

CATCH, error
IF error NE 0 THEN BEGIN
   ok = WIDGET_MESSAGE(!ERR_STRING)
   WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
   RETURN
ENDIF
   
RESTORE, filename

CATCH, /CANCEL

   ; Free up old pointer and set new pointer to old data.
   
HANDLE_FREE, info.dataptr   
info.dataptr = HANDLE_CREATE(VALUE=oldData, /NO_COPY)

   ; Set the proper display parameters.
   
WIDGET_CONTROL, info.xcoordID, SET_VALUE=oldinfo.xcoord
WIDGET_CONTROL, info.ycoordID, SET_VALUE=oldinfo.ycoord
WIDGET_CONTROL, info.zcoordID, SET_VALUE=oldinfo.zcoord

   ; Set the transparent factor.
   
transpercent = (oldinfo.transparent - (oldinfo.bottom -1))/(oldinfo.ncolors*0.01)
WIDGET_CONTROL, info.transparentID, SET_VALUE=transpercent

   ; Find current selection for the droplist.
   
thisSelection = 0
IF oldinfo.xslice THEN thisSelection = thisSelection + 1
IF oldinfo.yslice THEN thisSelection = thisSelection + 2
IF oldinfo.zslice THEN thisSelection = thisSelection + 4

CASE thisSelection OF
   1: WIDGET_CONTROL, info.droplistID, SET_DROPLIST_SELECT=0
   2: WIDGET_CONTROL, info.droplistID, SET_DROPLIST_SELECT=1
   3: WIDGET_CONTROL, info.droplistID, SET_DROPLIST_SELECT=3
   4: WIDGET_CONTROL, info.droplistID, SET_DROPLIST_SELECT=2
   5: WIDGET_CONTROL, info.droplistID, SET_DROPLIST_SELECT=4
   6: WIDGET_CONTROL, info.droplistID, SET_DROPLIST_SELECT=5
   7: WIDGET_CONTROL, info.droplistID, SET_DROPLIST_SELECT=6
ENDCASE

   ; Set the axes rotations.
   
WIDGET_CONTROL, info.axsliderID, SET_VALUE=oldinfo.ax
WIDGET_CONTROL, info.azsliderID, SET_VALUE=oldinfo.az

   ; Call drawing procedure.
   
IF NOT WIDGET_INFO(info.drawID, /VALID_ID) THEN $
   SLICE_GUI_MAKE_WINDOW, info, XSIZE=400, YSIZE=400
WSET, info.wid
oldinfo.wid = info.wid

HANDLE_VALUE, info.dataptr, data, /NO_COPY
SLICE, data, XCOORD=oldinfo.xcoord, YCOORD=oldinfo.ycoord, $
   ZCOORD=oldinfo.zcoord, XSLICE=oldinfo.xslice, $
   YSLICE=oldinfo.yslice, ZSLICE=oldinfo.zslice, AX=oldinfo.ax, $
   AZ=oldinfo.az, WID=oldinfo.wid, NCOLORS=oldinfo.ncolors, $
   BOTTOM=oldinfo.bottom, TRANSPARENT=oldinfo.transparent, $
   BACKGROUND=oldinfo.background, COLOR=oldinfo.color, $
   CHARSIZE=1.0*oldinfo.charscale, NOAXES=oldinfo.noaxes, $
   _EXTRA=oldinfo.extra
HANDLE_VALUE, info.dataptr, data, /SET, /NO_COPY      

   ; Set up the info structure.

info = { dataptr:info.dataptr, $
         xcoord:oldinfo.xcoord, $
         ycoord:oldinfo.ycoord, $
         zcoord:oldinfo.zcoord, $
         xslice:oldinfo.xslice, $
         yslice:oldinfo.yslice, $
         zslice:oldinfo.zslice, $
         az:oldinfo.az, $
         ax:oldinfo.ax, $
         wid:oldinfo.wid, $
         ncolors:oldinfo.ncolors, $
         bottom:oldinfo.bottom, $
         transparent:oldinfo.transparent, $
         charscale:oldinfo.charscale, $
         background:oldinfo.background, $
         color:oldinfo.color, $
         noaxes:oldinfo.noaxes, $
         extra:oldinfo.extra, $
         wtitle:oldinfo.wtitle, $
         thisInstance:info.thisInstance, $
         xcoordID:info.xcoordID, $
         ycoordID:info.ycoordID, $
         zcoordID:info.zcoordID, $
         transparentID:info.transparentID, $
         maxx:oldinfo.maxx, $
         maxy:oldinfo.maxy, $
         maxz:oldinfo.maxz, $
         interactive:oldinfo.interactive, $
         pt:oldinfo.pt, $
         pixmapID:oldinfo.pixmapID, $  
         pixXsize:oldinfo.pixXsize, $
         pixYsize:oldinfo.pixYsize, $       
         droplistID:info.droplistID, $
         axsliderID:info.axsliderID, $
         azsliderID:info.azsliderID, $
         graphics_tlb:info.graphics_tlb, $
         drawID:info.drawID, $
         tlb:info.tlb, $
         postscript:info.postscript}
 
WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
END ; of SLICE_GUI_RESTORE_DISPLAY event handler *********************



PRO SLICE_GUI_ANIMATE, event
WIDGET_CONTROL, event.top, GET_UVALUE=info, /NO_COPY
WIDGET_CONTROL, event.id, GET_UVALUE=slice

   ; Get the slice coordinates, just in case someone changed
   ; them and didn't hit a CR.
   
WIDGET_CONTROL, info.xcoordID, GET_VALUE=xcoord
info.xcoord = xcoord
WIDGET_CONTROL, info.ycoordID, GET_VALUE=ycoord
info.ycoord = ycoord
WIDGET_CONTROL, info.zcoordID, GET_VALUE=zcoord
info.zcoord = zcoord

CASE slice OF 
   'X': BEGIN
        IF info.xslice EQ 0 THEN BEGIN
           info.xslice = 1
           setBack = 1
        ENDIF ELSE setBack = 0
        SLICE_XINTERANIMATE, SET = [300, 300, info.maxx+1], /SHOWLOAD, $
        /CYCLE, BOTTOM=info.bottom, NCOLORS=info.ncolors
        FOR j=0,info.maxx DO BEGIN
           HANDLE_VALUE, info.dataptr, data, /NO_COPY
           SLICE, data, XCOORD=j(0), YCOORD=info.ycoord, $
           ZCOORD=info.zcoord, XSLICE=info.xslice, $
           YSLICE=info.yslice, ZSLICE=info.zslice, AX=info.ax, $
           AZ=info.az, WID=!D.WINDOW, NCOLORS=info.ncolors, $
           BOTTOM=info.bottom, TRANSPARENT=info.transparent, $
           BACKGROUND=info.background, COLOR=info.color, $
           CHARSIZE=1.0*info.charscale, NOAXES=info.noaxes, $
           _EXTRA=info.extra
           HANDLE_VALUE, info.dataptr, data, /SET, /NO_COPY  
           SLICE_XINTERANIMATE, FRAME=j, WINDOW=!D.WINDOW
        ENDFOR
        SLICE_XINTERANIMATE, 20, GROUP=event.top
        IF setBack THEN info.xslice = 0
        END
        
    'Y': BEGIN
        IF info.yslice EQ 0 THEN BEGIN
           info.yslice = 1
           setBack = 1
        ENDIF ELSE setBack = 0
        SLICE_XINTERANIMATE, SET = [300, 300, info.maxy+1], /SHOWLOAD, $
        /CYCLE, BOTTOM=info.bottom, NCOLORS=info.ncolors
        FOR j=0,info.maxy DO BEGIN
           HANDLE_VALUE, info.dataptr, data, /NO_COPY
           SLICE, data, XCOORD=info.xcoord, YCOORD=j(0), $
           ZCOORD=info.zcoord, XSLICE=info.xslice, $
           YSLICE=info.yslice, ZSLICE=info.zslice, AX=info.ax, $
           AZ=info.az, WID=!D.WINDOW, NCOLORS=info.ncolors, $
           BOTTOM=info.bottom, TRANSPARENT=info.transparent, $
           BACKGROUND=info.background, COLOR=info.color, $
           CHARSIZE=1.0*info.charscale, NOAXES=info.noaxes, $
           _EXTRA=info.extra
           HANDLE_VALUE, info.dataptr, data, /SET, /NO_COPY  
           SLICE_XINTERANIMATE, FRAME=j, WINDOW=!D.WINDOW
        ENDFOR
        SLICE_XINTERANIMATE, 20, GROUP=event.top
        IF setBack THEN info.yslice = 0
        END
        
   'Z': BEGIN
        IF info.zslice EQ 0 THEN BEGIN
           info.zslice = 1
           setBack = 1
        ENDIF ELSE setBack = 0
        SLICE_XINTERANIMATE, SET = [300, 300, info.maxz+1], /SHOWLOAD,  $
        /CYCLE, BOTTOM=info.bottom, NCOLORS=info.ncolors
        FOR j=0,info.maxz DO BEGIN
           HANDLE_VALUE, info.dataptr, data, /NO_COPY
           SLICE, data, XCOORD=info.xcoord, YCOORD=info.ycoord, $
           ZCOORD=j(0), XSLICE=info.xslice, $
           YSLICE=info.yslice, ZSLICE=info.zslice, AX=info.ax, $
           AZ=info.az, WID=!D.WINDOW, NCOLORS=info.ncolors, $
           BOTTOM=info.bottom, TRANSPARENT=info.transparent, $
           BACKGROUND=info.background, COLOR=info.color, $
           CHARSIZE=1.0*info.charscale, NOAXES=info.noaxes, $
           _EXTRA=info.extra
           HANDLE_VALUE, info.dataptr, data, /SET, /NO_COPY  
           SLICE_XINTERANIMATE, FRAME=j, WINDOW=!D.WINDOW
        ENDFOR
        SLICE_XINTERANIMATE, 20, GROUP=event.top
        IF setBack THEN info.zslice = 0
        END

ENDCASE

WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
END ; of SLICE_GUI_ANIMATE event handler ***************************


PRO SLICE_GUI_CLEANUP, id
WIDGET_CONTROL, id, GET_VALUE=info
IF N_ELEMENTS(info) NE 0 THEN HANDLE_FREE, info.dataptr
END ; of SLICE_GUI_CLEANUP routine *******************************



PRO SLICE_GUI_AXES_OFF, event
WIDGET_CONTROL, event.top, GET_UVALUE=info, /NO_COPY
IF info.noaxes EQ 1 THEN BEGIN
   WIDGET_CONTROL, event.id, SET_VALUE='Turn Axes Off'
   info.noaxes = 0
ENDIF ELSE BEGIN
   WIDGET_CONTROL, event.id, SET_VALUE='Turn Axes On'
   info.noaxes = 1
ENDELSE

   ; Get the slice coordinates, just in case someone changed
   ; them and didn't hit a CR.
   
WIDGET_CONTROL, info.xcoordID, GET_VALUE=xcoord
info.xcoord = xcoord
WIDGET_CONTROL, info.ycoordID, GET_VALUE=ycoord
info.ycoord = ycoord
WIDGET_CONTROL, info.zcoordID, GET_VALUE=zcoord
info.zcoord = zcoord

   ; Call drawing procedure.
   
IF NOT WIDGET_INFO(info.drawID, /VALID_ID) THEN $
   SLICE_GUI_MAKE_WINDOW, info, XSIZE=400, YSIZE=400
WSET, info.wid

HANDLE_VALUE, info.dataptr, data, /NO_COPY
SLICE, data, XCOORD=info.xcoord, YCOORD=info.ycoord, $
   ZCOORD=info.zcoord, XSLICE=info.xslice, $
   YSLICE=info.yslice, ZSLICE=info.zslice, AX=info.ax, $
   AZ=info.az, WID=info.wid, NCOLORS=info.ncolors, $
   BOTTOM=info.bottom, TRANSPARENT=info.transparent, $
   BACKGROUND=info.background, COLOR=info.color, $
   CHARSIZE=1.0*info.charscale, NOAXES=info.noaxes, $
   _EXTRA=info.extra
HANDLE_VALUE, info.dataptr, data, /SET, /NO_COPY      
   
WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
END ; of SLICE_GUI_AXES_OFF event handler *************************



PRO SLICE_GUI_CHARSIZE, event
WIDGET_CONTROL, event.top, GET_UVALUE=info, /NO_COPY
 
   ; Get the new character size.
 
scale = SLICE_CHARSIZE(info.charscale)

   ; Did user cancel?
   
IF scale LT 0 THEN BEGIN
   WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
   RETURN
 ENDIF
 
info.charscale = scale

   ; Get the slice coordinates, just in case someone changed
   ; them and didn't hit a CR.
   
WIDGET_CONTROL, info.xcoordID, GET_VALUE=xcoord
info.xcoord = xcoord
WIDGET_CONTROL, info.ycoordID, GET_VALUE=ycoord
info.ycoord = ycoord
WIDGET_CONTROL, info.zcoordID, GET_VALUE=zcoord
info.zcoord = zcoord

   ; Call drawing procedure.
   
IF NOT WIDGET_INFO(info.drawID, /VALID_ID) THEN $
   SLICE_GUI_MAKE_WINDOW, info, XSIZE=400, YSIZE=400
WSET, info.wid

HANDLE_VALUE, info.dataptr, data, /NO_COPY
SLICE, data, XCOORD=info.xcoord, YCOORD=info.ycoord, $
   ZCOORD=info.zcoord, XSLICE=info.xslice, $
   YSLICE=info.yslice, ZSLICE=info.zslice, AX=info.ax, $
   AZ=info.az, WID=info.wid, NCOLORS=info.ncolors, $
   BOTTOM=info.bottom, TRANSPARENT=info.transparent, $
   BACKGROUND=info.background, COLOR=info.color, $
   CHARSIZE=1.0*info.charscale, NOAXES=info.noaxes, $
   _EXTRA=info.extra
HANDLE_VALUE, info.dataptr, data, /SET, /NO_COPY      
   
WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
END ; of SLICE_GUI_CHARSIZE event handler ******************************



PRO SLICE_GUI_AXES_COLOR, event
WIDGET_CONTROL, event.top, GET_UVALUE=info, /NO_COPY
drawbase = WIDGET_INFO(info.drawID, /Parent)
CHGCOLOR, info.color, Title='Change Axes Color', $
   GROUP=event.top, NOTIFYID=[drawbase, event.top]
WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
END ; of SLICE_GUI_AXES_COLOR event handl*******************************


PRO SLICE_GUI_BACKGROUND_COLOR, event
WIDGET_CONTROL, event.top, GET_UVALUE=info, /NO_COPY
drawbase = WIDGET_INFO(info.drawID, /Parent)
CHGCOLOR, info.background, Title='Change Background Color', $
   GROUP=event.top, NOTIFYID=[drawbase, event.top]
WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
END ; of SLICE_GUI_BACKGROUND_COLOR event handl*************************


PRO SLICE_GUI_NEW_DISPLAY, event
WIDGET_CONTROL, event.top, GET_UVALUE=info, /NO_COPY

   ; Create another SQRSLIICE_GUI application.

   ; Get the slice coordinates, just in case someone changed
   ; them and didn't hit a CR.
   
WIDGET_CONTROL, info.xcoordID, GET_VALUE=xcoord
info.xcoord = xcoord
WIDGET_CONTROL, info.ycoordID, GET_VALUE=ycoord
info.ycoord = ycoord
WIDGET_CONTROL, info.zcoordID, GET_VALUE=zcoord
info.zcoord = zcoord

HANDLE_VALUE, info.dataptr, data, /NO_COPY
newdata = data
SLICE_GUI, newdata, XCOORD=info.xcoord, YCOORD=info.ycoord, $
   ZCOORD=info.zcoord, XSLICE=info.xslice, $
   YSLICE=info.yslice, ZSLICE=info.zslice, AX=info.ax, $
   AZ=info.az, WID=info.wid, NCOLORS=info.ncolors, $
   BOTTOM=info.bottom, TRANSPARENT=0, $
   BACKGROUND=info.background, COLOR=info.color, $
   CHARSIZE=1.0*info.charscale, NOAXES=info.noaxes, $
   _EXTRA=info.extra, WTITLE=info.wtitle, $
   THISINSTANCE=info.thisInstance + 1
HANDLE_VALUE, info.dataptr, data, /SET, /NO_COPY      
   
   
WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
END ; of SLICE_GUI_NEW_DISPLAY event handler *********************************



PRO SLICE_GUI_MAKE_WINDOW, info, XSIZE=xsize, YSIZE=ysize, $
   XOFFSET=xoffset, YOFFSET=yoffset
   
ON_ERROR, 1   
np = N_PARAMS()
IF np NE 1 THEN MESSAGE, 'Info structure parameter required.'

IF N_ELEMENTS(xsize) EQ 0 THEN xsize = 400
IF N_ELEMENTS(ysize) EQ 0 THEN ysize = 400
DEVICE, GET_SCREEN_SIZE=screenSize
xoff = (screenSize(0) - xsize) / 2.0
yoff = (screenSize(1) - ysize) / 2.0
IF N_ELEMENTS(xoffset) EQ 0 THEN xoffset = xoff
IF N_ELEMENTS(yoffset) EQ 0 THEN yoffset = yoff
thisTitle = 'Slice Resizeable Display: ' + STRTRIM(info.thisInstance, 2)

graphics_tlb = WIDGET_BASE(TLB_SIZE_EVENT=1, $
   EVENT_PRO='SLICE_GUI_RESIZE', $
   UVALUE=info.tlb, XOFFSET=xoffset, YOFFSET=yoffset, $
   TITLE=thisTitle, GROUP=info.tlb)
drawID = WIDGET_DRAW(graphics_tlb, XSIZE=xsize, YSIZE=ysize, $
   EVENT_PRO='SLICE_DRAW_BUTTON_EVENTS')
WIDGET_CONTROL, graphics_tlb, /REALIZE
WIDGET_CONTROL, drawID, GET_VALUE=wid
info.wid = wid
info.drawID = drawID
info.graphics_tlb = graphics_tlb
WSET, info.wid
END ; of SLICE_GUI_MAKE_WINDOW utility routine *********************************



PRO SLICE_GUI_TRANSPARENT, event
WIDGET_CONTROL, event.top, GET_UVALUE=info, /NO_COPY
 
   ; Get the new transparent factor.
   
WIDGET_CONTROL, event.id, GET_VALUE=transpercent

transvalue = ((info.bottom-1)+(transpercent*info.ncolors*0.01)) > 0

info.transparent = transvalue

   ; Get the slice coordinates, just in case someone changed
   ; them and didn't hit a CR.
   
WIDGET_CONTROL, info.xcoordID, GET_VALUE=xcoord
info.xcoord = xcoord
WIDGET_CONTROL, info.ycoordID, GET_VALUE=ycoord
info.ycoord = ycoord
WIDGET_CONTROL, info.zcoordID, GET_VALUE=zcoord
info.zcoord = zcoord

   ; Call drawing procedure.
   
IF NOT WIDGET_INFO(info.drawID, /VALID_ID) THEN $
   SLICE_GUI_MAKE_WINDOW, info, XSIZE=400, YSIZE=400
WSET, info.wid

HANDLE_VALUE, info.dataptr, data, /NO_COPY
SLICE, data, XCOORD=info.xcoord, YCOORD=info.ycoord, $
   ZCOORD=info.zcoord, XSLICE=info.xslice, $
   YSLICE=info.yslice, ZSLICE=info.zslice, AX=info.ax, $
   AZ=info.az, WID=info.wid, NCOLORS=info.ncolors, $
   BOTTOM=info.bottom, TRANSPARENT=info.transparent, $
   BACKGROUND=info.background, COLOR=info.color, $
   CHARSIZE=1.0*info.charscale, NOAXES=info.noaxes, $
   _EXTRA=info.extra
HANDLE_VALUE, info.dataptr, data, /SET, /NO_COPY      
   
WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
END ; of SLICE_GUI_TRANSPARENT event handler *********************************



PRO SLICE_GUI_ROTATE, event
WIDGET_CONTROL, event.top, GET_UVALUE=info, /NO_COPY
 
   ; Get the new rotation.
   
WIDGET_CONTROL, event.id, GET_VALUE=thisRotation

   ; Branch appropriately.
   
CASE event.id OF
   info.axsliderID: info.ax = thisRotation
   info.azsliderID: info.az = thisRotation
ENDCASE

   ; Get the slice coordinates, just in case someone changed
   ; them and didn't hit a CR.
   
WIDGET_CONTROL, info.xcoordID, GET_VALUE=xcoord
info.xcoord = xcoord
WIDGET_CONTROL, info.ycoordID, GET_VALUE=ycoord
info.ycoord = ycoord
WIDGET_CONTROL, info.zcoordID, GET_VALUE=zcoord
info.zcoord = zcoord

   ; Call drawing procedure.
   
IF NOT WIDGET_INFO(info.drawID, /VALID_ID) THEN $
   SLICE_GUI_MAKE_WINDOW, info, XSIZE=400, YSIZE=400
WSET, info.wid

HANDLE_VALUE, info.dataptr, data, /NO_COPY
SLICE, data, XCOORD=info.xcoord, YCOORD=info.ycoord, $
   ZCOORD=info.zcoord, XSLICE=info.xslice, $
   YSLICE=info.yslice, ZSLICE=info.zslice, AX=info.ax, $
   AZ=info.az, WID=info.wid, NCOLORS=info.ncolors, $
   BOTTOM=info.bottom, TRANSPARENT=info.transparent, $
   BACKGROUND=info.background, COLOR=info.color, $
   CHARSIZE=1.0*info.charscale, NOAXES=info.noaxes, $
   _EXTRA=info.extra
HANDLE_VALUE, info.dataptr, data, /SET, /NO_COPY      
   
WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
END ; of SLICE_GUI_ROTATE event handler *********************************



PRO SLICE_GUI_COORDINATE_SELECT, event
WIDGET_CONTROL, event.top, GET_UVALUE=info, /NO_COPY
 
   ; Get the new coordinate.
   
WIDGET_CONTROL, event.id, GET_VALUE=thisCoordinate

   ; Branch appropriately.
   
CASE event.id OF
   info.xcoordID: BEGIN
   
      IF thisCoordinate LT 0 OR thisCoordinate GT info.maxx THEN BEGIN
         ok = WIDGET_MESSAGE(['The value ' + STRTRIM(thisCoordinate, 2) + $
         ' is out of range.', 'Range: 0 - ' + STRTRIM(info.maxx,2)])
         WIDGET_CONTROL, info.xcoordID, SET_VALUE=info.xcoord
         WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
         RETURN
      ENDIF
      info.xcoord = thisCoordinate
      END
      
   info.ycoordID: BEGIN
      IF thisCoordinate LT 0 OR thisCoordinate GT info.maxy THEN BEGIN
         ok = WIDGET_MESSAGE(['The value ' + STRTRIM(thisCoordinate, 2) + $
         ' is out of range.', 'Range: 0 - ' + STRTRIM(info.maxy,2)])
         WIDGET_CONTROL, info.ycoordID, SET_VALUE=info.ycoord
         WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
         RETURN
      ENDIF
      info.ycoord = thisCoordinate
      END
      
   info.zcoordID: BEGIN
      IF thisCoordinate LT 0 OR thisCoordinate GT info.maxz THEN BEGIN
         ok = WIDGET_MESSAGE(['The value ' + STRTRIM(thisCoordinate, 2) + $
         ' is out of range.', 'Range: 0 - ' + STRTRIM(info.maxz, 2)])
         WIDGET_CONTROL, info.zcoordID, SET_VALUE=info.zcoord
         WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
         RETURN
      ENDIF
      info.zcoord = thisCoordinate
      END
      
ENDCASE

   ; User could type in bogus value. Check for it.

CATCH, error
IF error NE 0 THEN BEGIN
   ok = WIDGET_MESSAGE(['Problem with coordinate you', $
      'just typed in. Returning...'])
   WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
   RETURN
ENDIF

   ; Get the slice coordinates, just in case someone changed
   ; them and didn't hit a CR.
   
WIDGET_CONTROL, info.xcoordID, GET_VALUE=xcoord
info.xcoord = xcoord
WIDGET_CONTROL, info.ycoordID, GET_VALUE=ycoord
info.ycoord = ycoord
WIDGET_CONTROL, info.zcoordID, GET_VALUE=zcoord
info.zcoord = zcoord

   ; Call drawing procedure.
   
IF NOT WIDGET_INFO(info.drawID, /VALID_ID) THEN $
   SLICE_GUI_MAKE_WINDOW, info, XSIZE=400, YSIZE=400
WSET, info.wid

HANDLE_VALUE, info.dataptr, data, /NO_COPY
SLICE, data, XCOORD=info.xcoord, YCOORD=info.ycoord, $
   ZCOORD=info.zcoord, XSLICE=info.xslice, $
   YSLICE=info.yslice, ZSLICE=info.zslice, AX=info.ax, $
   AZ=info.az, WID=info.wid, NCOLORS=info.ncolors, $
   BOTTOM=info.bottom, TRANSPARENT=info.transparent, $
   BACKGROUND=info.background, COLOR=info.color, $
   CHARSIZE=1.0*info.charscale, NOAXES=info.noaxes, $
   _EXTRA=info.extra
HANDLE_VALUE, info.dataptr, data, /SET, /NO_COPY      
   
WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
END ; of SLICE_GUI_COORDINATE_SELECT event handler ***************************



PRO SLICE_GUI_SLICE_SELECT, event
WIDGET_CONTROL, event.top, GET_UVALUE=info, /NO_COPY
WIDGET_CONTROL, event.id, GET_UVALUE=options
CASE options(event.index) OF
   'X Only': BEGIN
      info.xslice = 1
      info.yslice = 0
      info.zslice = 0
      WIDGET_CONTROL, info.drawID, DRAW_BUTTON_EVENTS=0
      END
   'Y Only': BEGIN
      info.xslice = 0
      info.yslice = 1
      info.zslice = 0
      WIDGET_CONTROL, info.drawID, DRAW_BUTTON_EVENTS=0
      END
   'Z Only': BEGIN
      info.xslice = 0
      info.yslice = 0
      info.zslice = 1
      WIDGET_CONTROL, info.drawID, DRAW_BUTTON_EVENTS=0
      END
   'X and Y': BEGIN
      info.xslice = 1
      info.yslice = 1
      info.zslice = 0
      WIDGET_CONTROL, info.drawID, DRAW_BUTTON_EVENTS=0
      END
   'X and Z': BEGIN
      info.xslice = 1
      info.yslice = 0
      info.zslice = 1
      WIDGET_CONTROL, info.drawID, DRAW_BUTTON_EVENTS=0
      END
   'Y and Z': BEGIN
      info.xslice = 0
      info.yslice = 1
      info.zslice = 1
      WIDGET_CONTROL, info.drawID, DRAW_BUTTON_EVENTS=0
      END
   'XYZ': BEGIN
      info.xslice = 1
      info.yslice = 1
      info.zslice = 1
      WIDGET_CONTROL, info.drawID, DRAW_BUTTON_EVENTS=0
      END
   'Interactive XY': BEGIN
      WIDGET_CONTROL, info.drawID, DRAW_BUTTON_EVENTS=1
      END
ENDCASE

   ; Get the slice coordinates, just in case someone changed
   ; them and didn't hit a CR.
   
WIDGET_CONTROL, info.xcoordID, GET_VALUE=xcoord
info.xcoord = xcoord
WIDGET_CONTROL, info.ycoordID, GET_VALUE=ycoord
info.ycoord = ycoord
WIDGET_CONTROL, info.zcoordID, GET_VALUE=zcoord
info.zcoord = zcoord

   ; Call the drawing procedure.
   
IF NOT WIDGET_INFO(info.drawID, /VALID_ID) THEN $
   SLICE_GUI_MAKE_WINDOW, info, XSIZE=400, YSIZE=400
WSET, info.wid

HANDLE_VALUE, info.dataptr, data, /NO_COPY
SLICE, data, XCOORD=info.xcoord, YCOORD=info.ycoord, $
   ZCOORD=info.zcoord, XSLICE=info.xslice, $
   YSLICE=info.yslice, ZSLICE=info.zslice, AX=info.ax, $
   AZ=info.az, WID=info.wid, NCOLORS=info.ncolors, $
   BOTTOM=info.bottom, TRANSPARENT=info.transparent, $
   BACKGROUND=info.background, COLOR=info.color, $
   CHARSIZE=1.0*info.charscale, NOAXES=info.noaxes, $
   _EXTRA=info.extra
HANDLE_VALUE, info.dataptr, data, /SET, /NO_COPY      
WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
END ; of SLICE_GUI_SLICE_SELECT event handler ************************


PRO SLICE_GUI_LOAD_DATA, event
WIDGET_CONTROL, event.top, GET_UVALUE=info, /NO_COPY

CATCH, error
IF error NE 0 THEN BEGIN
   ok = WIDGET_MESSAGE(['You must download the file SLICE_GETIMAGE.PRO', $
      'from the TRAINING directory at RSI. E-mail', $
      'davidf@fortnet.org for more information.'])
   WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
   RETURN
ENDIF
      
data = SLICE_GETIMAGE()

CATCH, /CANCEL

IF N_ELEMENTS(data) LE 1 THEN BEGIN
   WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
   RETURN
ENDIF
s = SIZE(data)
IF s(0) NE 3 THEN BEGIN
   ok = WIDGET_MESSAGE('New data set must be 3D.')
   WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
   RETURN
ENDIF
xs = s(1)
ys = s(2)
zs = s(3)

   ; Use current coodinates if they fit, otherwise
   ; update them.

IF info.xcoord GE s(1) THEN BEGIN
   info.xcoord = s(1) / 2
   WIDGET_CONTROL, info.xcoordID, SET_VALUE=info.xcoord
ENDIF   
IF info.ycoord GE s(2) THEN BEGIN
   info.ycoord = s(2) / 2
   WIDGET_CONTROL, info.ycoordID, SET_VALUE=info.ycoord
ENDIF   
IF info.zcoord GE s(3) THEN BEGIN
   info.zcoord = s(3) / 2
   WIDGET_CONTROL, info.zcoordID, SET_VALUE=info.zcoord
ENDIF   

   ; Get the slice coordinates, just in case someone changed
   ; them and didn't hit a CR.
   
WIDGET_CONTROL, info.xcoordID, GET_VALUE=xcoord
info.xcoord = xcoord
WIDGET_CONTROL, info.ycoordID, GET_VALUE=ycoord
info.ycoord = ycoord
WIDGET_CONTROL, info.zcoordID, GET_VALUE=zcoord
info.zcoord = zcoord

   ; Make sure requested slices are inside the data.
   
info.maxX = xs - 1
info.maxY = ys - 1
info.maxZ = zs - 1
info.xcoord = 0 > info.xcoord
info.xcoord = info.xcoord < info.maxX
info.ycoord = 0 > info.ycoord
info.ycoord = info.ycoord < info.maxY
info.zcoord = 0 > info.zcoord
info.zcoord = info.zcoord < info.maxZ

IF NOT WIDGET_INFO(info.drawID, /VALID_ID) THEN $
   SLICE_GUI_MAKE_WINDOW, info, XSIZE=400, YSIZE=400
WSET, info.wid
SLICE, data, XCOORD=info.xcoord, YCOORD=info.ycoord, $
   ZCOORD=info.zcoord, XSLICE=info.xslice, $
   YSLICE=info.yslice, ZSLICE=info.zslice, AX=info.ax, $
   AZ=info.az, WID=info.wid, NCOLORS=info.ncolors, $
   BOTTOM=info.bottom, TRANSPARENT=info.transparent, $
   BACKGROUND=info.background, COLOR=info.color, $
   CHARSIZE=1.0*info.charscale, NOAXES=info.noaxes, $
   _EXTRA=info.extra
HANDLE_VALUE, info.dataptr, /SET, data, /NO_COPY
WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
END ; of SLICE_GUI_LOAD_DATA event handler ******************************


PRO SLICE_GUI_CHANGE_COLORS, event
WIDGET_CONTROL, event.top, GET_UVALUE=info, /NO_COPY
drawbase = WIDGET_INFO(info.drawID, /Parent)
XCOLORS, NCOLORS=info.ncolors, BOTTOM=info.bottom, $
   GROUP=event.top, NOTIFYID=[drawbase, event.top]
WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
END ; of SLICE_GUI_CHANGE_COLORS event handler ***************************


PRO SLICE_GUI_POSTSCRIPT, event
WIDGET_CONTROL, event.top, GET_UVALUE=info, /NO_COPY

   ; Get the slice coordinates, just in case someone changed
   ; them and didn't hit a CR.
   
WIDGET_CONTROL, info.xcoordID, GET_VALUE=xcoord
info.xcoord = xcoord
WIDGET_CONTROL, info.ycoordID, GET_VALUE=ycoord
info.ycoord = ycoord
WIDGET_CONTROL, info.zcoordID, GET_VALUE=zcoord
info.zcoord = zcoord

   ; Get the user value of the button.
  
WIDGET_CONTROL, event.id, GET_UVALUE=thisUValue
CASE thisUVALUE of

   'CREATE': BEGIN
      HANDLE_VALUE, info.dataptr, data, /NO_COPY
      SLICE, data, XCOORD=info.xcoord, YCOORD=info.ycoord, $
         ZCOORD=info.zcoord, XSLICE=info.xslice, $
         YSLICE=info.yslice, ZSLICE=info.zslice, AX=info.ax, $
         AZ=info.az, WID=info.wid, NCOLORS=info.ncolors, $
         BOTTOM=info.bottom, TRANSPARENT=info.transparent, $
         BACKGROUND=info.color, COLOR=info.background, $
         POSTSCRIPT=info.postscript, $
         CHARSIZE=1.0*info.charscale, NOAXES=info.noaxes, $
         _EXTRA=info.extra
      HANDLE_VALUE, info.dataptr, data, /SET, /NO_COPY
      END
      
   'CONFIGURE': BEGIN
      keywords = PS_FORM(CANCEL=cancelled, CREATE=create, $
         DEFAULTS=info.postscript)
      IF NOT cancelled THEN info.postscript = keywords
      IF create THEN BEGIN
         HANDLE_VALUE, info.dataptr, data, /NO_COPY
         SLICE, data, XCOORD=info.xcoord, YCOORD=info.ycoord, $
            ZCOORD=info.zcoord, XSLICE=info.xslice, $
            YSLICE=info.yslice, ZSLICE=info.zslice, AX=info.ax, $
            AZ=info.az, WID=info.wid, NCOLORS=info.ncolors, $
            BOTTOM=info.bottom, TRANSPARENT=info.transparent, $
            BACKGROUND=info.color, COLOR=info.background, $
            POSTSCRIPT=info.postscript, $
            CHARSIZE=1.0*info.charscale, NOAXES=info.noaxes, $
            _EXTRA=info.extra
         HANDLE_VALUE, info.dataptr, data, /SET, /NO_COPY
      ENDIF
      END
ENDCASE
WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
END ; of SLICE_GUI_POSTSCRIPT event handler *******************************



PRO SLICE_GUI_RESIZE, event
WIDGET_CONTROL, event.top, GET_UVALUE=tlb
WIDGET_CONTROL, tlb, GET_UVALUE=info, /NO_COPY
thisEvent = TAG_NAMES(event, /Structure)
IF (thisEvent EQ 'XCOLORS_LOAD') OR $
   (thisEvent EQ 'CHGCOLOR_LOAD') THEN BEGIN
      skip = 0 
      TVLCT, event.r, event.g, event.b
ENDIF ELSE skip = 1
IF skip THEN BEGIN
   WIDGET_CONTROL, info.drawID, XSIZE=event.x, YSIZE=event.y
   
   ; Update file output configuration structures.
   
   info.gif.xsize = event.x
   info.gif.ysize = event.y
   info.tiff.xsize = event.x
   info.tiff.ysize = event.y
   info.jpeg.xsize = event.x
   info.jpeg.ysize = event.y
ENDIF

   ; Get the slice coordinates, just in case someone changed
   ; them and didn't hit a CR.
   
WIDGET_CONTROL, info.xcoordID, GET_VALUE=xcoord
info.xcoord = xcoord
WIDGET_CONTROL, info.ycoordID, GET_VALUE=ycoord
info.ycoord = ycoord
WIDGET_CONTROL, info.zcoordID, GET_VALUE=zcoord
info.zcoord = zcoord

HANDLE_VALUE, info.dataptr, data, /NO_COPY
WSET, info.wid
IF skip THEN ERASE, Color=info.background
SLICE, data, XCOORD=info.xcoord, YCOORD=info.ycoord, $
   ZCOORD=info.zcoord, XSLICE=info.xslice, $
   YSLICE=info.yslice, ZSLICE=info.zslice, AX=info.ax, $
   AZ=info.az, WID=info.wid, NCOLORS=info.ncolors, $
   BOTTOM=info.bottom, TRANSPARENT=info.transparent, $
   BACKGROUND=info.background, COLOR=info.color, $
   CHARSIZE=1.0*info.charscale, NOAXES=info.noaxes, $
   _EXTRA=info.extra
HANDLE_VALUE, info.dataptr, data, /SET, /NO_COPY
WIDGET_CONTROL, tlb, SET_UVALUE=info, /NO_COPY
END ; of SLICE_GUI_RESIZE event handler *********************************



PRO SLICE_GUI_QUITTER, event
WIDGET_CONTROL, event.top, GET_UVALUE=info, /NO_COPY
HANDLE_FREE, info.dataptr
WIDGET_CONTROL, event.top, /DESTROY
END ; of SLICE_GUI_QUITTER event handler ********************************



PRO SLICE_CONFIGURATION_EVENTS, event

WIDGET_CONTROL, event.top, GET_UVALUE=info, /NO_COPY
WIDGET_CONTROL, event.id, GET_UVALUE=thisEvent
CASE thisEvent OF

   'SELECT_FILE': BEGIN
   
         ; Start in the current directory.
                       
      CD, Current=startDirectory
            
         ; Use PICKFILE to pick a filename for writing.
                   
      pick = Pickfile(Path=startDirectory, /NoConfirm, $
         Get_Path=path, /Write)
      
         ; Make sure the user didn't cancel out of PICKFILE.
         
      IF pick NE '' THEN Widget_Control, info.filenameID, Set_Value=pick
      END ; of the Select Filename button case
      
    'CANCEL': BEGIN
    
         ; Have to exit here gracefully. Set CANCEL field in structure.
         
       formdata = {cancel:1, create:0}
       Handle_Value, info.ptr, formdata, /Set
      
         ; Out of here!
         
       Widget_Control, event.top, /Destroy
       RETURN
       END ; of the Cancel button case
                    
    'ACCEPT': BEGIN  ; Gather the form information.
    
          ; Get the filename.
    
       Widget_Control, info.filenameID, Get_Value=filename
       
       filename = filename(0)
       
          ; Get the size info. 
       
       Widget_Control, info.xsizeID, Get_Value=xsize
       Widget_Control, info.ysizeID, Get_Value=ysize
       
          ; Get the color info from the droplist widget.
       
       listIndex = Widget_Info(info.colordropID, /Droplist_Select)
       colortype = FIX(ABS(1-listindex))
       
          ; Get the order info from the droplist widget.
       
       order = Widget_Info(info.orderdropID, /Droplist_Select)
       order = FIX(order)
       
          ; Get the quality fromt he slider widget, if needed
          
       IF info.sliderID NE -1 THEN $
          Widget_Control, info.sliderID, Get_Value=quality ELSE quality=-1
       
          ; Create the formdata structure from the information you collected.
       
       formdata = {filename:filename, xsize:xsize, ysize:ysize, $
          color:colortype, order:order, quality:quality, create:0}
       
          ; Store the formdata in the pointer location.
          
       Handle_Value, info.ptr, formdata, /Set
    
         ; Out of here!
         
      Widget_Control, event.top, /Destroy
      RETURN
      END ; of the Accept button case
      
    'CREATE': BEGIN  ; Gather the form information.
    
          ; Get the filename.
    
       Widget_Control, info.filenameID, Get_Value=filename
       
       filename = filename(0)
       
          ; Get the size info. 
       
       Widget_Control, info.xsizeID, Get_Value=xsize
       Widget_Control, info.ysizeID, Get_Value=ysize
       
          ; Get the color info from the droplist widget.
       
       listIndex = Widget_Info(info.colordropID, /Droplist_Select)
       colortype = FIX(ABS(1-listindex))
       
          ; Get the order info from the droplist widget.
       
       order = Widget_Info(info.orderdropID, /Droplist_Select)
       order = FIX(order)
       
          ; Get the quality fromt he slider widget, if needed
          
       IF info.sliderID NE -1 THEN $
          Widget_Control, info.sliderID, Get_Value=quality ELSE quality=-1
       
          ; Create the formdata structure from the information you collected.
       
       formdata = {filename:filename, xsize:xsize, ysize:ysize, $
          color:colortype, order:order, quality:quality, create:1}
       
          ; Store the formdata in the pointer location.
          
       Handle_Value, info.ptr, formdata, /Set
    
         ; Out of here!
         
      Widget_Control, event.top, /Destroy
      RETURN
      END ; of the Create button case
      
   ELSE:
ENDCASE
      
WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
END ; of SLICE_CONFIGURATION_EVENTS event handler ************************
  
 

FUNCTION SLICE_CONFIGURATION, filetype, config, TITLE=title, $
   XOFFSET=xoffset, YOFFSET=yoffset, Cancel=cancel, Create=create

CATCH, error
IF error NE 0 THEN BEGIN
ok = WIDGET_MESSAGE(!Err_String)
RETURN, -1
ENDIF

IF N_ELEMENTS(filetype) EQ 0 THEN filetype = 'GIF'
IF N_ELEMENTS(config) EQ 0 THEN config = {XSIZE:400, YSIZE:400, $
   COLOR:1, FILENAME:'xwindow.gif', NCOLORS:(!D.N_Colors < 256)}
filetype = STRUPCASE(filetype)
IF N_ELEMENTS(title) EQ 0 THEN title = 'Configure ' + $
   filetype + ' Output File'

   ; Check for placement offsets. Define defaults.
   
IF (N_ELEMENTS(xoffset) EQ 0) THEN BEGIN
   DEVICE, GET_SCREEN_SIZE=screenSize
   xoffset = (screenSize(0) - 200) / 2.
ENDIF
IF (N_ELEMENTS(yoffset) EQ 0) THEN BEGIN
   DEVICE, GET_SCREEN_SIZE=screenSize
   yoffset = (screenSize(1) - 100) / 2.
ENDIF

   ; Create widgets.
   
tlb = WIDGET_BASE(Column=1, Title=title, XOffset=xoffset, $
   YOffset=yoffset, Base_Align_Center=1)

bigbox = WIDGET_BASE(tlb, Column=1, Frame=1, Base_Align_Center=1)

   ; Create the filename widgets.
filebox = Widget_Base(bigbox, Column=1, Base_Align_Center=1)
filename = config.filename        
filenamebase = Widget_Base(filebox, Row=1)
   filenamelabel = Widget_Label(filenamebase, Value='Filename:')
   filenameID = Widget_Text(filenamebase, Value=filename, /Editable, $
      Event_Pro='SLICE_NULL_EVENTS', SCR_XSIZE=320)
      
   ; Create a button to allow user to pick a filename.

pickbutton = Widget_Button(filebox, Value='Select Filename', $
   UVALUE='SELECT_FILE')

   ; Create size widgets
sizebox = Widget_Base(bigbox, Column=1, Base_Align_Left=1)   
sizebase = Widget_Base(sizebox, Row=1)
xsizeID = CW_FIELD(sizebase, Value=config.xsize, Title='XSize: ', $
   /Integer)
ysizeID = CW_FIELD(sizebase, Value=config.ysize, Title='YSize: ', $
   /Integer)

   ; File type and order.
   
orderbase = Widget_Base(sizebox, Row=1)
type = ['Color', 'Grayscale']
order = ['0', '1']
colordropID = Widget_Droplist(orderbase, Value=type, $
   Title='File Type: ', EVENT_PRO='SLICE_NULL_EVENTS')
orderdropID = Widget_Droplist(orderbase, Value=order, $
   Title='Display Order: ', EVENT_PRO='SLICE_NULL_EVENTS')

Widget_Control, colordropID, Set_Droplist_Select=FIX(ABS(config.color-1))
Widget_Control, orderdropID, Set_Droplist_Select=config.order

   ; Quality Slider if needed.
   
IF filetype EQ 'JPEG' THEN $
   sliderID = Widget_Slider(bigbox, Value=config.quality, Max=100, Min=0, $
      Title='Compression Quality', EVENT_PRO='SLICE_NULL_EVENTS', $
      SCR_XSize=350) ELSE sliderID = -1
      
   ; Cancel and Accept buttons.
   
buttonbase = Widget_Base(tlb, Row=1)
cancelID = Widget_Button(buttonbase, Value='Cancel', UValue='CANCEL')
createID = Widget_Button(buttonbase, Value='Create File', UValue='CREATE')
ok = Widget_Button(buttonbase, Value='Accept', UValue='ACCEPT')

Widget_Control, tlb, /Realize

ptr = HANDLE_CREATE()

info = { filenameID:filenameID, xsizeID:xsizeID, $
         ysizeID:ysizeID, colordropID:colordropID, $
         orderdropID:orderdropID, ptr:ptr, sliderID:sliderID}
         
Widget_Control, tlb, Set_UValue=info, /No_Copy
XManager, 'slice_configuration', tlb, /Modal, $
   Event_Handler='SLICE_CONFIGURATION_EVENTS'

Handle_Value, ptr, formdata
Handle_Free, ptr

IF N_ELEMENTS(formdata) EQ 0 THEN BEGIN
   cancel = 1
   create = 0
   RETURN, -1
ENDIF
   
fields = TAG_NAMES(formdata)
create = formdata.create
cancel = WHERE(fields EQ 'CANCEL')
IF cancel(0) EQ -1 THEN BEGIN
   cancel = 0
   newConfiguration = Create_Struct('XSIZE', formdata.xsize, $
      'YSIZE', formdata.ysize, 'COLOR', formdata.color, $
      'FILENAME', formdata.filename, 'ORDER', formdata.order, $
      'QUALITY', formdata.quality, NAME='XWINDOW_' + filetype)
   RETURN, newConfiguration 
ENDIF ELSE BEGIN
   cancel = 1
   create = 0
   RETURN, -1
ENDELSE
END ; of SLICE_CONFIGURATION event handler *******************************
  
 

PRO SLICE_CONFIGURE_FILES, event
WIDGET_CONTROL, event.top, GET_UVALUE=info, /NO_COPY

   ; What kind of file to configure?
   
WIDGET_CONTROL, event.id, GET_VALUE=whichFile
CASE whichFile OF

   'Configure GIF File...': BEGIN
      config = info.gif
      newConfiguration = SLICE_CONFIGURATION('GIF', config, $
         Cancel=cancel, Create=create)
      IF NOT cancel THEN info.gif = newConfiguration
      IF create THEN WIDGET_CONTROL, info.gifID, SEND_EVENT={ID:info.gifID, $
         TOP:event.top, HANDLER:0L}
      END
      
   'Configure TIFF File...': BEGIN
      config = info.tiff
      newConfiguration = SLICE_CONFIGURATION('TIFF', config, $
         Cancel=cancel, Create=create)
      IF NOT cancel THEN info.tiff = newConfiguration
      IF create THEN WIDGET_CONTROL, info.tiffID, SEND_EVENT={ID:info.tiffID, $
         TOP:event.top, HANDLER:0L}
      END
      
   'Configure JPEG File...': BEGIN
      config = info.jpeg
      newConfiguration = SLICE_CONFIGURATION('JPEG', config, $
         Cancel=cancel, Create=create)
      IF NOT cancel THEN info.jpeg = newConfiguration
      IF create THEN WIDGET_CONTROL, info.jpegID, SEND_EVENT={ID:info.jpegID, $
         TOP:event.top, HANDLER:0L}
      END
ENDCASE

WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
END ; of SLICE_CONFIGURE_FILES event handler ***********************************



PRO SLICE_CREATE_FILES, event
WIDGET_CONTROL, event.top, GET_UVALUE=info, /NO_COPY

   ; There can be all kinds of problems writing a file.
   ; Trap errors here and try to get out of here.
   
CATCH, error
IF error NE 0 THEN BEGIN
junk=widget_message(!err_string)
   ok = WIDGET_MESSAGE(['Problem writing file. The most', $
      'common problem is a mis-spelled filename.', 'Returning...'])
   IF WIDGET_INFO(id, /Valid_ID) THEN WIDGET_CONTROL, id, /Destroy
   IF N_ELEMENTS(thisDevice) GT 0 THEN SET_PLOT, thisDevice
   IF N_ELEMENTS(info) NE 0 THEN WIDGET_CONTROL, event.top, $
      SET_UVALUE=info, /NO_COPY
   RETURN
ENDIF

id = SLICE_DELAY_NOTICE('Please be patient while writing the file...')

WIDGET_CONTROL, event.id, GET_VALUE=whichFile
CASE whichFile OF

   'Create GIF File': BEGIN
      config = info.gif
      
         ; Copy the image
         
      IF WIDGET_INFO(info.drawID, /Valid_ID) THEN BEGIN
         thisImage = TVRD()
         IF config.color EQ 0 THEN BEGIN
            TVLCT, rr, gg, bb, /GET
            LOADCT, 0, NCOLORS=info.ncolors, BOTTOM=info.bottom
            TVLCT, r, g, b, /GET
         ENDIF ELSE TVLCT, r, g, b, /GET
      END
      
         ; Write GIF file.
         
      s = SIZE(thisImage)
      IF (s(1) NE config.xsize) OR (s(2) NE config.ysize) THEN $
         thisImage = CONGRID(thisImage, config.xsize, config.ysize, /Interp)
      WRITE_GIF, config.filename, thisImage, r, g, b
      END ; of GIF file creation.
      
   'Create TIFF File': BEGIN
      config = info.tiff
      
         ; Copy the image
         
      thisImage = TVRD()
      TVLCT, r, g, b, /GET
      
         ; Write TIFF file. Use screen resolution.
         
      s = SIZE(thisImage)
      IF (s(1) NE config.xsize) OR (s(2) NE config.ysize) THEN $
         thisImage = CONGRID(thisImage, config.xsize, config.ysize, /Interp)
      IF config.color EQ 1 THEN $
         TIFF_WRITE, config.filename, thisImage, config.order, $
            RED=r, GREEN=g, BLUE=b, XRESOL=ROUND(!D.X_PX_CM * 2.54), $
            YRESOL=ROUND(!D.X_PX_CM * 2.54) ELSE $
         TIFF_WRITE, config.filename, thisImage, config.order, $
            XRESOL=ROUND(!D.X_PX_CM * 2.54), YRESOL=ROUND(!D.X_PX_CM * 2.54)
      END
      
   'Create JPEG File': BEGIN
      config = info.jpeg
      
         ; Get the image.
      
      thisImage = TVRD()
      TVLCT, r, g, b, /GET
      
         ; Write JPEG file.
         
      s = SIZE(thisImage)
      IF (s(1) NE config.xsize) OR (s(2) NE config.ysize) THEN $
         thisImage = CONGRID(thisImage, config.xsize, config.ysize, /Interp)
      IF config.color EQ 1 THEN BEGIN
         image24 = BYTARR(3, config.xsize, config.ysize)
         image24(0,*,*) = r(thisImage)
         image24(1,*,*) = g(thisImage)
         image24(2,*,*) = b(thisImage)
         WRITE_JPEG, config.filename, image24, TRUE=1, $
            QUALITY=config.quality, ORDER=config.order
      ENDIF ELSE $
          WRITE_JPEG, config.filename, thisimage, $
            QUALITY=config.quality, ORDER=config.order
      END
ENDCASE

WIDGET_CONTROL, id, /DESTROY
WIDGET_CONTROL, event.top, SET_UVALUE=info, /NO_COPY
END ; of SLICE_CREATE_FILES event handler ***********************************



PRO SLICE_GUI, data, XSLICE=xslice, YSLICE=yslice, ZSLICE=zslice, $
   WID=wid, NOAXES=noaxes, XCOORD=xcoord, YCOORD=ycoord, ZCOORD=zcoord, $
   NCOLORS=ncolors, BOTTOM=bottom, BACKGROUND=background, GROUP=group, $
   COLOR=color, AZ=az, AX=ax, TRANSPARENT=transparent, _EXTRA=extra, $
   CHARSIZE=charscale, WTITLE=wtitle, THISINSTANCE=thisInstance, $
   POSTSCRIPT=postscript

ON_ERROR, 1

   ; If no data passed in, make some up for demo purposes.
   
np = N_PARAMS()   
IF np EQ 0 THEN BEGIN
    MESSAGE, 'No data passed in. Using jet data...', /INFORMATIONAL
    filename = FILEPATH(SUBDIR=['examples', 'data'], 'jet.dat')
    CATCH, error
    IF error NE 0 THEN BEGIN
       ok = WIDGET_MESSAGE("Can't find JET.DAT. Returning...")
       RETURN
    ENDIF
    OPENR, lun, filename, /GET_LUN
    CATCH, /CANCEL
    data = BYTARR(81, 40, 101)
    READU, lun, data
    FREE_LUN, lun
ENDIF

   ; Make sure data is 3D. Pick appropriate row and column slices.
   
s = SIZE(data)
IF s(0) NE 3 THEN MESSAGE, 'Data set must be 3D.'
xs = s(1)
ys = s(2)
zs = s(3)
IF N_ELEMENTS(xcoord) EQ 0 THEN xcoord= xs / 2
IF N_ELEMENTS(ycoord) EQ 0 THEN ycoord= ys / 2
IF N_ELEMENTS(zcoord) EQ 0 THEN zcoord= zs / 2

   ; Make sure requested slices are inside the data.
   
maxX = xs - 1
maxY = ys - 1
maxZ = zs - 1
xcoord = 0 > xcoord
xcoord = xcoord < maxX
ycoord = 0 > ycoord
ycoord = ycoord < maxY
zcoord = 0 > zcoord
zcoord = zcoord < maxZ

   ; What slices do you want. Must have something! X Slice is default.
   
xslice = KEYWORD_SET(xslice)
yslice = KEYWORD_SET(yslice)
zslice = KEYWORD_SET(zslice)
IF NOT xslice AND NOT yslice AND NOT zslice THEN xslice = 1

   ; Set colors. 
   
IF N_ELEMENTS(ncolors) EQ 0 THEN ncolors=!D.N_COLORS
IF N_ELEMENTS(bottom) EQ 0 THEN bottom = 0
IF N_ELEMENTS(background) EQ 0 THEN background = bottom
IF N_ELEMENTS(color) EQ 0 THEN color = (ncolors-1) + bottom

   ; Default axes rotations. Keep in program range.
   
IF N_ELEMENTS(az) EQ 0 THEN az = 30
az = az > (-30)
az = az < 210
IF N_ELEMENTS(ax) EQ 0 THEN ax = 30
ax = ax > 0
ax = ax < 90

   ; Miscellaneous keywords.
   
IF N_ELEMENTS(noaxes) EQ 0 THEN noaxes = 0
IF N_ELEMENTS(transparent) EQ 0 THEN transparent = 0
transvalue = ((bottom-1)+(transparent*ncolors*0.01)) > 0
IF N_ELEMENTS(charscale) EQ 0 THEN charscale = 1.0
IF N_ELEMENTS(extra) EQ 0 THEN extra = CREATE_STRUCT('title', '')
IF N_ELEMENTS(wid) EQ 0 THEN wid = -1
IF N_ELEMENTS(thisInstance) EQ 0 THEN thisInstance = 1
IF N_ELEMENTS(wtitle) EQ 0 THEN wtitle = 'Slice Controls: '
IF N_ELEMENTS(postscript) EQ 0 THEN postscript = PS_FORM(/Initialize)

   ; Only one program by this name, please!
   
registerName = 'xsection_gui' + STRTRIM(thisInstance, 2)
IF XREGISTERED(registerName) THEN BEGIN
   ok = WIDGET_MESSAGE(['Sorry, a new display cannot be opened', $
   'from this SLICE program. Try the SLICE', $
   'program that is now forward on the display.'])
   RETURN
ENDIF
   
   ; Create the control widgets.
   
tlb = WIDGET_BASE(COLUMN=1, TLB_FRAME_ATTR=1, $
   TITLE=wtitle + STRTRIM(thisInstance, 2))

coordbase = WIDGET_BASE(tlb, COLUMN=1, FRAME=1, $
   EVENT_PRO='SLICE_GUI_COORDINATE_SELECT')
label = WIDGET_LABEL(coordbase, Value='Slice Coordinates')
xcoordID = CW_FIELD(coordbase, /INTEGER, Value=xcoord, TITLE='X Coordinate: ', $
   RETURN_EVENTS=1)
ycoordID = CW_FIELD(coordbase, /INTEGER, Value=ycoord, TITLE='Y Coordinate: ', $
   RETURN_EVENTS=1)
zcoordID = CW_FIELD(coordbase, /INTEGER, Value=zcoord, TITLE='Z Coordinate: ', $
   RETURN_EVENTS=1)

slicebase = WIDGET_BASE(tlb, COLUMN=1, FRAME=1)
options = ['X Only', 'Y Only', 'Z Only', 'X and Y', 'X and Z', 'Y and Z', $
   'XYZ', 'Interactive XY']
droplistID = WIDGET_DROPLIST(slicebase, Value=options, $
   UValue=options, TITLE='Slice Selection: ', $
   EVENT_PRO='SLICE_GUI_SLICE_SELECT')
   
   ; Find current selection for the droplist.
   
thisSelection = 0
IF xslice THEN thisSelection = thisSelection + 1
IF yslice THEN thisSelection = thisSelection + 2
IF zslice THEN thisSelection = thisSelection + 4

CASE thisSelection OF
   1: WIDGET_CONTROL, droplistID, SET_DROPLIST_SELECT=0
   2: WIDGET_CONTROL, droplistID, SET_DROPLIST_SELECT=1
   3: WIDGET_CONTROL, droplistID, SET_DROPLIST_SELECT=3
   4: WIDGET_CONTROL, droplistID, SET_DROPLIST_SELECT=2
   5: WIDGET_CONTROL, droplistID, SET_DROPLIST_SELECT=4
   6: WIDGET_CONTROL, droplistID, SET_DROPLIST_SELECT=5
   7: WIDGET_CONTROL, droplistID, SET_DROPLIST_SELECT=6
ENDCASE

   ; Rotation and transparency.

rotbase = WIDGET_BASE(tlb, COLUMN=1, FRAME=1)
azsliderID = WIDGET_SLIDER(rotbase, TITLE='Rotate About Z', MIN=-30, $
   EVENT_PRO='SLICE_GUI_ROTATE', MAX=210, VALUE=az, SCR_XSIZE=200)
axsliderID = WIDGET_SLIDER(rotbase, TITLE='Rotate About X', MIN=0, $
   EVENT_PRO='SLICE_GUI_ROTATE', MAX=90, VALUE=ax, SCR_XSIZE=200)
transparentID = WIDGET_SLIDER(rotbase, TITLE='Transparency Threshold', $
   MIN=0, EVENT_PRO='SLICE_GUI_TRANSPARENT', MAX=100, $
   VALUE=transparent, SCR_XSIZE=200)
   
   ; Animate pull-down menu.
   
animateSlice = WIDGET_BUTTON(tlb, VALUE='Animate Slice', MENU=2,  $
   EVENT_PRO='SLICE_GUI_ANIMATE')
junk = WIDGET_BUTTON(animateSlice, VALUE='X Slice', UVALUE='X')
junk = WIDGET_BUTTON(animateSlice, VALUE='Y Slice', UVALUE='Y')
junk = WIDGET_BUTTON(animateSlice, VALUE='Z Slice', UVALUE='Z')

   ; Graphics display options.
   
character = WIDGET_BUTTON(tlb, VALUE='Graphics Display Properties', MENU=2)
changecolorID = WIDGET_BUTTON(character, VALUE='Change Data Colors', $
   EVENT_PRO='SLICE_GUI_CHANGE_COLORS')
backcolor = WIDGET_BUTTON(character, VALUE='Change Background Color', $
   EVENT_PRO='SLICE_GUI_BACKGROUND_COLOR')
axescolor = WIDGET_BUTTON(character, VALUE='Change Axes Color', $
   EVENT_PRO='SLICE_GUI_AXES_COLOR')
IF noaxes THEN noaxesText = ' Turn Axes On ' ELSE noaxesText = 'Turn Axes Off'
axesoff = WIDGET_BUTTON(character, VALUE=noaxesText, $
   EVENT_PRO='SLICE_GUI_AXES_OFF')
annotationSize = WIDGET_BUTTON(character, VALUE='Change Character Size', $
   EVENT_PRO='SLICE_GUI_CHARSIZE')
 saveDisplay = WIDGET_BUTTON(character, VALUE='SAVE Graphic Display', $
   EVENT_PRO='SLICE_GUI_SAVE_DISPLAY')
 restoreDisplay = WIDGET_BUTTON(character, VALUE='RESTORE Graphic Display', $
   EVENT_PRO='SLICE_GUI_RESTORE_DISPLAY')

   ; Hardcopy output.
   
outputButton = WIDGET_BUTTON(tlb, Value='File Output', $
   Menu=2, Event_Pro='Slice_Create_Files') 
   psID = WIDGET_BUTTON(outputButton, Value='Create PostScript File', $
      EVENT_PRO='SLICE_GUI_POSTSCRIPT', UValue='CREATE')
   gifID = WIDGET_BUTTON(outputButton, Value='Create GIF File')
   tiffID = WIDGET_BUTTON(outputButton, Value='Create TIFF File')
   jpegID = WIDGET_BUTTON(outputButton, Value='Create JPEG File')
configure = WIDGET_BUTTON(outputButton, Value='Configure Output File', $
      /Menu, /Separator, Event_Pro='Slice_Configure_Files')
   ps_config = WIDGET_BUTTON(configure, Value='Configure PostScript File...', $
      EVENT_PRO='SLICE_GUI_POSTSCRIPT', UValue='CONFIGURE')
   gif_config = WIDGET_BUTTON(configure, Value='Configure GIF File...')
   tiff_config = WIDGET_BUTTON(configure, Value='Configure TIFF File...')
   jpeg_config = WIDGET_BUTTON(configure, Value='Configure JPEG File...')

   ; Load new data.
   
loaddataID = WIDGET_BUTTON(tlb, VALUE='Load New Data', $
   EVENT_PRO='SLICE_GUI_LOAD_DATA')
   
   ; New graphic display.
   
newdisplayID = WIDGET_BUTTON(tlb, Value='New Graphic Display', $
   EVENT_PRO='SLICE_GUI_NEW_DISPLAY')
   
   ; Quit button.
   
quitID = WIDGET_BUTTON(tlb, VALUE='QUIT', EVENT_PRO='SLICE_GUI_QUITTER')

   ; Show it.
   
WIDGET_CONTROL, tlb, /REALIZE

   ; Set up the info structure.

info = { dataptr:-1L, $
         xcoord:xcoord, $
         ycoord:ycoord, $
         zcoord:zcoord, $
         xslice:xslice, $
         yslice:yslice, $
         zslice:zslice, $
         az:az, $
         ax:ax, $
         wid:wid, $
         ncolors:ncolors, $
         bottom:bottom, $
         psID:psID, $
         tiffID:tiffID, $
         gifID:gifID, $
         jpegID:jpegID, $
         transparent:transvalue, $
         charscale:charscale, $
         background:background, $
         color:color, $
         noaxes:noaxes, $
         extra:extra, $
         wtitle:wtitle, $
         thisInstance:thisInstance, $
         xcoordID:xcoordID, $
         ycoordID:ycoordID, $
         zcoordID:zcoordID, $
         transparentID:transparentID, $
         interactive:'', $
         pt:!P.T, $
         pixmapID:-1, $
         pixXsize:-1, $
         pixYsize:-1, $       
         maxx:maxx, $
         maxy:maxy, $
         maxz:maxz, $
         droplistID:droplistID, $
         axsliderID:axsliderID, $
         azsliderID:azsliderID, $
         graphics_tlb:-1L, $
         drawID:-1L, $
         tlb:tlb, $
         postscript:postscript}
 
   ; File Output configuration structures.
   
CD, Current=thisDir
gif =  {XWINDOW_GIF,XSIZE:400, YSIZE:400, COLOR:1, $
       FILENAME:FilePath(Root_Dir=thisDir,'slice.gif'), $
       ORDER:0, QUALITY:-1}
jpeg = {XWINDOW_JPEG,XSIZE:400, YSIZE:400, COLOR:1, $
       FILENAME:FilePath(Root_Dir=thisDir,'slice.jpg'), $
       ORDER:0, QUALITY:75}
tiff = {XWINDOW_TIFF,XSIZE:400, YSIZE:400, COLOR:1, $
       FILENAME:FilePath(Root_Dir=thisDir,'slice.tif'), $
       ORDER:1, QUALITY:-1}
info = CREATE_STRUCT(info, 'GIF', gif, 'JPEG', jpeg, 'TIFF', tiff)

   ; Create the display window.
   
WIDGET_CONTROL, tlb, TLB_GET_OFFSET=tlb_offsets, TLB_GET_SIZE=tlb_sizes
thisXoff = tlb_offsets(0) + tlb_sizes(0) + 50
thisYoff = tlb_offsets(1) 
SLICE_GUI_MAKE_WINDOW, info, XSIZE=400, YSIZE=400, XOFFSET=thisXoff, YOFFSET=thisYoff

   ; Put initial display into the display window.
  
SLICE, data, XCOORD=xcoord, YCOORD=ycoord, ZCOORD=zcoord, XSLICE=xslice, $
   YSLICE=yslice, ZSLICE=zslice, AX=ax, AZ=az, WID=info.wid, NCOLORS=ncolors, $
   BOTTOM=bottom, TRANSPARENT=transparent, BACKGROUND=background, COLOR=color, $
   CHARSIZE=1.0*charscale, NOAXES=noaxes, _EXTRA=extra

   ; Get correct transformation in the info.pt field.
   
info.pt = !P.T
   
   ; Set up pointer to data so data can be easily changed.

info.dataptr = HANDLE_CREATE(VALUE=data, /NO_COPY)
   
   ; Store the info structure and set focus on the control panel.
   
WIDGET_CONTROL, tlb, SET_UVALUE=info, /NO_COPY, $
   EVENT_PRO='SLICE_GUI_RESIZE', /SHOW
XMANAGER, registerName, tlb, EVENT_HANDLER='SLICE_GUI_RESIZE', $
   GROUP_LEADER=group, CLEANUP = 'SLICE_GUI_CLEANUP'

END ; of SLICE_GUI PROGRAM *************************************************



PRO SLICE, data, XSLICE=xslice, YSLICE=yslice, ZSLICE=zslice, WID=wid, $
   NOAXES=noaxes, XCOORD=xcoord, YCOORD=ycoord, ZCOORD=zcoord, $
   NCOLORS=ncolors, BOTTOM=bottom, POSTSCRIPT=postscript, BACKGROUND=background, $
   COLOR=color, AZ=az, AX=ax, TRANSPARENT=transparent, _EXTRA=extra, $
   CHARSIZE=charscale, GUI=gui, WTITLE=wtitle, THISINSTANCE=thisInstance

   ; Error checking
   
ON_ERROR, 1
np = N_PARAMS()

   ; If no data passed in, make some up for demo purposes.
   
IF np EQ 0 THEN BEGIN
    MESSAGE, 'No data passed in. Using jet data...', /INFORMATIONAL
    filename = FILEPATH(SUBDIR=['examples', 'data'], 'jet.dat')
    CATCH, error
    IF error NE 0 THEN BEGIN
       ok = WIDGET_MESSAGE("Can't find JET.DAT. Returning...")
       RETURN
    ENDIF
    OPENR, lun, filename, /GET_LUN
    CATCH, /CANCEL
    data = BYTARR(81, 40, 101)
    READU, lun, data
    data = CONGRID(TEMPORARY(data), 20, 10, 25, /INTERP)
    FREE_LUN, lun
 ENDIF

   ; Make sure data is 3D. Pick appropriate row and column slices.
   
s = SIZE(data)
IF s(0) NE 3 THEN MESSAGE, 'Data set must be 3D.'
xs = s(1)
ys = s(2)
zs = s(3)
IF N_ELEMENTS(xcoord) EQ 0 THEN xcoord= xs / 2
IF N_ELEMENTS(ycoord) EQ 0 THEN ycoord= ys / 2
IF N_ELEMENTS(zcoord) EQ 0 THEN zcoord= zs / 2

   ; What slices do you want. Must have something! X Slice is default.
   
xslice = KEYWORD_SET(xslice)
yslice = KEYWORD_SET(yslice)
zslice = KEYWORD_SET(zslice)
IF NOT xslice AND NOT yslice AND NOT zslice THEN xslice = 1

   ; Set colors.
   
IF N_ELEMENTS(ncolors) EQ 0 THEN ncolors=(!D.N_COLORS < 256)
IF N_ELEMENTS(bottom) EQ 0 THEN bottom = 0
IF N_ELEMENTS(transparent) EQ 0 THEN transparent = 0
transparent = transparent > 0
transparent = transparent < 100
IF N_ELEMENTS(background) EQ 0 THEN BEGIN
   IF KEYWORD_SET(postscript) THEN background = 255 ELSE background = bottom
ENDIF
IF N_ELEMENTS(color) EQ 0 THEN BEGIN
   IF KEYWORD_SET(postscript) THEN color = 0 ELSE color = (ncolors-1) + bottom
ENDIF

   ; Miscellaneous keywords.
   
IF N_ELEMENTS(charscale) EQ 0 THEN charscale = 1.0
IF N_ELEMENTS(extra) EQ 0 THEN extra = CREATE_STRUCT('title', '')
IF N_ELEMENTS(wid) EQ 0 THEN wid = !D.WINDOW
IF N_ELEMENTS(wtitle) EQ 0 THEN wtitle='Slice Graphics Window'
IF N_ELEMENTS(thisInstance) EQ 0 THEN thisInstance = 1
noaxes = KEYWORD_SET(noaxes)

   ; Default axes rotations.
   
IF N_ELEMENTS(az) EQ 0 THEN az = 30
IF N_ELEMENTS(ax) EQ 0 THEN ax = 30

   ; Does user want a widget program?
   
IF KEYWORD_SET(gui) THEN BEGIN
   SLICE_GUI, data, XSLICE=xslice, YSLICE=yslice, ZSLICE=zslice, $
   WID=wid, NOAXES=noaxes, XCOORD=xcoord, YCOORD=ycoord, ZCOORD=zcoord, $
   NCOLORS=ncolors, BOTTOM=bottom, BACKGROUND=background, GROUP=group, $
   COLOR=color, AZ=az, AX=ax, TRANSPARENT=transparent, _EXTRA=extra, $
   CHARSIZE=charscale, WTITLE='Slice Controls: ', THISINSTANCE=thisInstance, $
   POSTSCRIPT=postscript
   RETURN
ENDIF

   ; Create a window if one is needed.

IF wid EQ -1 THEN BEGIN
   IF NOT KEYWORD_SET(postscript) THEN $
      WINDOW, /FREE, XSIZE=400, YSIZE=400, TITLE=wtitle
   wid = !D.WINDOW
ENDIF

   ; Make sure requested slices are inside the data.
   
maxX = xs - 1
maxY = ys - 1
maxZ = zs - 1
maxData = MAX(data, MIN=minData)
xcoord = 0 > xcoord
xcoord = xcoord < maxX
ycoord = 0 > ycoord
ycoord = ycoord < maxY
zcoord = 0 > zcoord
zcoord = zcoord < maxZ

   ; Obtain the slice planes.
   
xplane = [ [xcoord, 0, 0], [xcoord, 0, maxz], [xcoord, maxy, maxz], [xcoord, maxy, 0] ]
yplane = [ [0, ycoord, 0], [0, ycoord, maxz], [maxx, ycoord, maxz], [maxx, ycoord, 0] ]
zplane = [ [ 0, 0, zcoord], [maxx, 0, zcoord], [maxx, maxy, zcoord], [0, maxy, zcoord] ]

   ; Obtain the slice images.

ximage = data(xcoord, *, *)
yimage = data(*, ycoord, *)
zimage = data(*, *, zcoord)

   ; Make a 2D image out of the images.

ximage = REFORM(ximage)
yimage = REFORM(yimage)
zimage = REFORM(zimage)

   ; Scale images appropriately for number of colors.
     
ximage = BYTSCL(ximage, TOP=ncolors-1, MAX=maxData, MIN=minData) + BYTE(bottom)
yimage = BYTSCL(yimage, TOP=ncolors-1, MAX=maxData, MIN=minData) + BYTE(bottom)
zimage = BYTSCL(zimage, TOP=ncolors-1, MAX=maxData, MIN=minData) + BYTE(bottom)

   ; If the window index number doesn't exist, create it.

CATCH, error
IF error NE 0 THEN BEGIN
   wid = wid < 31
   WINDOW, wid, XSIZE=400, YSIZE=400, TITLE=wtitle
ENDIF
IF NOT KEYWORD_SET(postscript) THEN WSET, wid
CATCH, /CANCEL
 
   ; The slices will be rendered in the Z-Buffer. 
   ; Set Z-Buffer size.

IF KEYWORD_SET(postscript) THEN xsize=400 ELSE xsize = !D.X_SIZE 
IF KEYWORD_SET(postscript) THEN ysize=400 ELSE ysize = !D.Y_SIZE 

oldDevice = !D.NAME  
SET_PLOT, 'Z'
DEVICE, SET_RESOLUTION=[xsize, ysize], SET_COLORS=ncolors

   ; Set up 3D coordinate system.
   
SURFACE, dist(30), /NODATA, XRANGE=[0,maxx], YRANGE=[0,maxy], ZRANGE=[0,maxz], $
   XSTYLE=5, YSTYLE=5, ZSTYLE=5, POSITION=[0.15, 0.15, 0.95, 0.95, 0.1, 0.95], $
   /SAVE, AZ=az, AX=ax

   ; Set up background color.
   
ERASE, COLOR=background
      
   ; Render the slices.

IF xslice THEN POLYFILL, xplane, /T3D, PATTERN=ximage, $
   IMAGE_COORD=[ [0,0], [0, zs-1], [ys-1, zs-1], [ys-1, 0] ], /IMAGE_INTERP, $
   TRANSPARENT=transparent
   
IF yslice THEN POLYFILL, yplane, /T3D, PATTERN=yimage, $
   IMAGE_COORD=[  [0,0], [0, zs-1], [xs-1, zs-1], [xs-1, 0] ], /IMAGE_INTERP, $
   TRANSPARENT=transparent
   
IF zslice THEN POLYFILL, zplane, /T3D, PATTERN=zimage, $
   IMAGE_COORD=[ [0,0], [xs-1, 0], [xs-1, ys-1], [0, ys-1] ], /IMAGE_INTERP, $
   TRANSPARENT=transparent
   
   ; Add axes if needed and not in PostScript mode.
   
IF NOT KEYWORD_SET(postscript) THEN $          
IF NOT noaxes THEN SURFACE, dist(30), /NODATA, XRANGE=[0,maxx], $
   YRANGE=[0,maxy], ZRANGE=[0,maxz], XSTYLE=1, YSTYLE=1, ZSTYLE=1, /NOERASE, $
   POSITION=[0.15, 0.15, 0.95, 0.95, 0.1, 0.95], XTITLE='X Coordinate', $
   YTITLE='Y Coordinate', ZTITLE='Z Coordinate', CHARSIZE=1.0*charscale, $
   COLOR=color, AZ=az, AX=ax, _EXTRA=extra
   
   ; Take a snap-shot.
   
picture = TVRD(0, 0, xsize-1, ysize-1)

   ; Display the data in the display window. 
   ; Make sure window is valid and whether the device is PostScript.
   
SET_PLOT, oldDevice

   ; Can ask for a window that is not available. Anticipate!!
   
CATCH, error
IF error NE 0 THEN BEGIN
   ok = WIDGET_MESSAGE(['Requested window index number '+ STRTRIM(wid,2) + ' is', $
      'not available. Using current window index.'])
   wid = !D.WINDOW
   IF wid EQ -1 THEN BEGIN
      ok = WIDGET_MESSAGE("Can't find valid display window. Returning...")
      RETURN
   ENDIF
ENDIF

IF NOT KEYWORD_SET(postscript) THEN WSET, wid
CATCH, /CANCEL

   ; Special handling if in PostScript.
   
IF KEYWORD_SET(postscript) THEN BEGIN

      ; Get instructions for configuring the device from the user.
   
   deviceKeywords = PS_FORM(INITIALIZE=1, LOCALDEFAULTS=postscript)

      ; Let user know something is going on.
      
   notice = SLICE_DELAY_NOTICE('Please be patient while writing the file...')

      ; Configure PostScript device.
         
   oldDevice = !D.NAME
   IF oldDevice NE 'PS' THEN BEGIN
      SET_PLOT, 'PS'
      setBack = 1
   ENDIF ELSE setBack = 0    
   DEVICE, _EXTRA=deviceKeywords
   filename = deviceKeywords.filename
      
   ; Special PostScript processing for backgrounds and axes color.
   
   index = WHERE(picture EQ background, count)
   IF count GT 0 THEN picture(index) = 255
   TVLCT, r, g, b, /GET
   rThis = r(0)
   gThis = g(0)
   bThis = b(0)
   TVLCT, 0, 0, 0, 0
   color = 0
   
      ; Display the image.
      
   inches = deviceKeywords.inches
   IF inches THEN TV, picture, XSIZE=deviceKeywords.xsize, $
      YSIZE=deviceKeywords.ysize, Inches=1 ELSE $
      TV, picture, XSIZE=deviceKeywords.xsize, $
      YSIZE=deviceKeywords.ysize, Centimeters=1
       
      ; Add axes if needed.
         
    IF NOT noaxes THEN SURFACE, DIST(30,30), /NODATA, XRANGE=[0,maxx], $
       YRANGE=[0,maxy], ZRANGE=[0,maxz], XSTYLE=1, YSTYLE=1, ZSTYLE=1, /NOERASE, $
       POSITION=[0.15, 0.15, 0.95, 0.95, 0.1, 0.95], XTITLE='X Coordinate', $
       YTITLE='Y Coordinate', ZTITLE='Z Coordinate', _EXTRA=extra, $
       COLOR=color, CHARSIZE=1.0*charscale, AZ=az, AX=ax
        
    TVLCT, rThis, gThis, bThis, 0   
    DEVICE, /CLOSE
    WIDGET_CONTROL, notice, /DESTROY
    IF setBack THEN SET_PLOT, oldDevice
ENDIF ELSE TV, picture

END ; of SLICE PROGRAM **********************************************************



   ; EXAMPLE program to see how it works.

PRO EXAMPLE

   ; How many colors?
   
thisWindow = !D.WINDOW
WINDOW, /FREE, /PIXMAP, XSIZE=10, YSIZE=10
WDELETE, !D.WINDOW
IF thisWindow GE 0 THEN WSET, thisWindow
ncolors = ((!D.N_COLORS-4) < 256)

   ; Load data and drawing colors
   
LOADCT, 5, NCOLORS=ncolors-2, BOTTOM=0
TVLCT, [70, 255], [70, 255], [70, 0], ncolors-2

   ; Run the SLICE program.
   
SLICE, /GUI, BACKGROUND=ncolors-2, COLOR=ncolors-1, NCOLORS=ncolors-2, $
   BOTTOM=0, /XSLICE, /YSLICE
END