; $Id: imgdisplay__define.pro,v 1.3 2001/12/11 23:41:25 bhill Exp $
;+
;              Object Oriented Widget for Image Display and Hardcopy
;
; NAME:
;       IMGDISPLAY
;
; PURPOSE:
;       This file contains code for an object-oriented image display 
;       widget.
;
; EXPLANATION:
;       The programming is object-oriented, but the graphics are
;       in direct graphics mode, not object graphic.  This widget
;       lets the user adjust the image scaling and contrast and print
;       out hard copies.  
;
; CATEGORY:
;       Widgets.
;
; CALLING SEQUENCE FOR INITIALIZATION:
;       new = obj_new('imgdisplay', parent, keywords=.....)
;
; ARGUMENT:
;       parent:          widget ID of parent base widget
;
; KEYWORD INPUTS:
;       xsize, ysize:    dimensions of total image area, pixels
;       x_scroll_size, y_scroll_size:  dimensions of scrolling
;                        viewport, if desired
;       uvalue:          arbitrary user value
;       kill_button:     include a button to destroy the widget cleanly
;       kill_dialog:     ask user "really?" before killing widget
;       fixedfont:       font for data entry fields
;       refresh:         make image refreshable; the widget
;                        will carry around its own pixmap backup(s)
;       subwindow:       X0,Y0, NX, NY specifying a section of the
;                        total window that will be used for image
;                        display (and be separately refreshable if
;       title:           title of generated hardcopy
;       nocontrols:      do not generate image display controls
;       scale_template:  another IMGDISPLAY from which to get
;                        image scaling parameters
;       bottom, ncolors: specify a section of the color table
;                        to be used, leaving entres 0...bottom-1
;                        for graphics colors
;       ps_dialog:       on generating hardcopy, prompt user for file
;                        name
;       nobuttons:       do not generate any control buttons at all
;       zoomwin:         generate a zoom window
;       panwin:          generate a pan window;  if two elements,
;                        then they are taken as x size and y size
;       readout:         generate a pixel readout
;       vertical:        put readout all the way at bottom
;
; OUTPUTS:
;       result = ID of immediate child of parent
;
; RESTRICTIONS:
;       This widget is self-contained and doesn't return
;       any events to the outside world.  It is a subordinate widget
;       and must be generated as a child of some other base.
;
; MODIFICATION HISTORY:
;       Converted to object-oriented code from cw_sig_display.pro,
;           3 Mar 99, RSH, RITSS
;       Several new capabilities.  23 April 1999 RSH
;       Spiffed up documentation, make ::V routine cleaner, fixed
;           bugs, added kill_dialog.  29 April 1999 RSH
;       Several small changes.  11 May 1999 RSH
;       Changed name of ::v to ::draw, as more conventional naming.
;           21 May 1999
;       Some cursor debugging.  24 May 1999 RSH
;       Cursor outputs not given if no image.  A pixmap memory leak
;           fixed.  14 July 1999  RSH
;       Fixed problem if zoom box is bigger in at least on dimension
;           than the main display.  23 July 1999 RSH
;       Size function now returns more info.  28 July 1999 RSH
;       Fixed bug in setting boxcolor.  2 Aug 1999 RSH
;       Made popup group leader of xloadct.  5 Aug 1999 RSH
;       Changed readouts to label widgets.  14 Sep 1999 RSH
;       Add NOOLDZOOM to ::DRAW so that a random zoom shadow
;           can be suppressed.  5 Oct 1999 RSH
;       Fixed bug trying to read from zoom window with no contents.
;           13 Oct 1999 RSH
;       INIT routine forces device to x.  21 Mar 2000 RSH
;       Redisplay button.  Scale pan window same as main window.
;           21 Mar 2000 RSH
;       Use tracking events to make sure mouse presses/releases don't
;           get mixed up.  Force pan window to have same scaling
;           as main window.  8 May 2000 RSH
;       Change label of xloadct button.  Wset main window before leaving
;           event handler.  17 May 2000 RSH
;       Subscripts surrounded by square brackets instead of
;           parentheses.  Thick=1 added to plots calls.  27 June 2000 RSH
;       Changed FIELDFONT to FIXEDFONT and use it in data listings.
;           Save viewport in info structure.  Emulated events assumed
;           anonymous.  Byte image instead of pixmap used to generate
;           zoom and pan displays to work better with 24 bit.  REDRAW
;           distinct from REFRESH added to support TrueColor visual.
;           TICKLE to force cursor move into main graphics window.
;           ZOOMDRAW modularized.  Sqrt and hist_equal
;           scaling added.  Togglable crosshair in zoom window.
;           XCOLORS (Fanning routine) instead of XLOADCT, to support
;           24 bits.  29 Aug 2000 RSH
;       Fixed bug in SIZE method if no zoom window.  RSH, 31 Aug 2000
;       Clamped cursor X and Y coords at image size.  RSH, 21 Sep 2000
;       Bug in boxcolor when not needed (no zoom or pan).  RSH, 3 Oct 2000
;       Button titles more compact.  SIZE returns box color.  RSH, 5 Oct 2000
;       Zoomlist fixed to work with repeater.  RSH, 6 Oct 2000
;       Zoom window cursor updated via zoom window events.  RSH, 10 Oct 2000
;       Support decomposed=1.  RSH, 10 Oct 2000
;       Grayimg ptr manipulation fixed for nocon, noscale.  RSH, 21 Oct 2000
;       Simplied uvalue search.  Climb hierarchy until an IMGDISPLAY
;          is found.  RSH, 21 Oct 2000
;       Button to toggle zoom window cursor.  RSH, 23 Oct 2000
;       Zoom cursor better in case of tiny images.  RSH, 30 Oct 2000
;       XDISPSTR substituted for XDISPLAYFILE.  RSH, 17 Nov 2000
;       Fixed subwindow bug I'd put in.  RSH, 17 Nov 2000
;       SETDISPLAY added.  Changed XCOLORS handling for TRUECOLOR
;          visual.  RSH, 17 May 2001
;       Unequal X and Y sizes of pan window allowed.  /NOSCALE option
;           for ::draw method.  RSH, 25 Sep 2001
;       Cleaner scrollable windows.  RSH, 26 Sep 2001
;       Gyration to make PostScript work with X visuals.  RSH, 26 Sep 2001
;       !DUMP was removed somewhere along the way.  RSH, 11 Dec 2001
;       Fixed a problem with generating image PS files.  RSH, 21 July 2003
;-

PRO IMGDISPLAY_E, Event

;  Event handler.  Can't be an object routine itself;  main job 
;  is to find out what object the widget is part of, then call the
;  real event handler for that instance.

tmp = event.id
iter = 0
imgd = 0
REPEAT BEGIN
    widget_control, tmp, get_uvalue=uval
    sz = size(uval)
    type = sz[sz[0]+1]
    IF type EQ 11 THEN imgd = obj_isa(uval, 'IMGDISPLAY')
    IF (type NE 11) OR (imgd LE 0) THEN tmp = widget_info(tmp,/parent)
    iter = iter + 1
ENDREP UNTIL (type EQ 11) AND (imgd GT 0)

uval->e, event

RETURN
END

PRO IMGDISPLAY::SIZE, Xsize, Ysize, X_scroll_size, Y_scroll_size, $
                      Pansize, Zoomsize, $
                      ZOOMFACTOR=zoomfactor, $
                      MAINDISP=maindisp, $
                      ZOOMDISP=zoomdisp, $
                      PANDISP=pandisp, $
                      CURZOOM=curzoom, $
                      CURSOR=cursor, $
                      BOXCOLOR=boxcolor

;  Returns size and other parameters to outer world.

xsize = self.xsize
ysize = self.ysize
x_scroll_size = self.x_scroll_size
y_scroll_size = self.y_scroll_size
pansize = self.pansize
maindisp = self.maindisp
zoomdisp = self.zoomdisp
pandisp = self.pandisp
zoomsize = self.zoomsize
boxcolor = self.boxcolor
IF zoomsize GT 0 THEN BEGIN
    zoomfactor = (*self.zoomfactors)[widget_info(self.zoomlist, $
                                         /droplist_sel)]
    curzoom = [self.xzstart, self.xzend, self.yzstart, self.yzend]
ENDIF
cursor = [self.xcursor, self.ycursor]

RETURN
END

PRO IMGDISPLAY::IMAGE, Imgptr

;  Returns pointer to image.

imgptr = self.image
RETURN
END

PRO IMGDISPLAY::E, Event

;  The real event handler for the object, called by the widget
;  event handler.
IF self.dump THEN print,'Entering imgdisplay::e'

imgex = ptr_valid(self.image)
boxcolor=self.boxcolor
widget_control, self.maindisp, get_value=maingraph
zoom_exists = widget_info(self.zoomdisp, /valid)
readout_exists = widget_info(self.xreadout, /valid)
pan_exists = widget_info(self.pandisp, /valid)
IF zoom_exists THEN widget_control, self.zoomdisp, get_value=zoomgraph
IF pan_exists THEN widget_control, self.pandisp, get_value=pangraph
scrollable = self.x_scroll_size LT self.xsize $
         OR  self.y_scroll_size LT self.ysize

delete_widget=0b
process_xy_event=0b
display_popup=0b
do_zoom_draw=0b
do_zoom_maint=0b
do_pan=0b
do_readout=0b
viewport_update=0b
;
;  The event handling is in two phases:  (1) Set flags requiring
;  certain actions and gather information needed.  (2)  Do the
;  work.  This makes it easier to delete the widget cleanly, among
;  other things.
;
CASE event.id OF
    ;
    ;  Change of zoom factor results in updating zoom display.
    self.zoomlist:  BEGIN
        ;  
        ;  For compatibility with repeater object, anything
        ;  that persistently changes widget state must be able
        ;  to come from outside.
        widget_control, self.zoomlist, set_droplist_sel=event.index
        do_zoom_maint=1b
        do_zoom_draw=1b
    END
    ;
    ;  Pushing left mouse button in pan window activates panning.
    ;  Releasing it deactivates panning.  If panning is active,
    ;  a cursor motion event results in updating current X and
    ;  Y coordinates.
    ;
    self.pandisp:  BEGIN
        evname = tag_names(event, /struct)
        CASE evname OF
            'WIDGET_TRACKING':  self.panactive = 0
            ELSE:  BEGIN
                IF self.panactive THEN BEGIN
                    IF event.x GE 0 AND event.x LE (self.pansize[0]-1) $
                        AND event.y GE 0 AND event.y LE (self.pansize[1]-1) THEN BEGIN
                        self.xcursor $
                            = (round(float(event.x)/self.pansize[0]*self.xsize)>0)<(self.xsize-1)
                        self.ycursor $
                            = (round(float(event.y)/self.pansize[1]*self.ysize)>0)<(self.ysize-1)
                        do_pan=1b
                        do_zoom_maint=1b
                        do_zoom_draw=1b
                        do_readout=1b
                    ENDIF
                    IF event.release EQ 1 THEN BEGIN
                        self.panactive = 0
                    ENDIF
                ENDIF ELSE BEGIN
                    IF event.press EQ 1 THEN BEGIN
                        self.panactive = 1
                    ENDIF ELSE BEGIN
                        self.panactive = 0
                    ENDELSE
                ENDELSE
            END
        ENDCASE
    END
    ;
    ;  Switch zoom cross-hair on and off.
    ;
    self.zoomxswitch:  BEGIN
        self.zoomxhair = 1b - self.zoomxhair
        self->zoomdraw
    END
    ;
    ;  Pushing left mouse button in zoom window activates the
    ;  update to current X and Y positions, which ultimately
    ;  results in update of pixel readout.  Releasing mouse button
    ;  stops X & Y update.
    ;
    self.zoomdisp:  BEGIN
        evname = tag_names(event, /struct)
        CASE evname OF
            'WIDGET_TRACKING':  BEGIN
                self.zoomactive = 0
            END
            ELSE:  BEGIN
            IF event.x GE 0 AND event.x LE (self.zoomsize-1) $
                AND event.y GE 0 AND event.y LE (self.zoomsize-1) THEN BEGIN
                zf = (*self.zoomfactors)[widget_info(self.zoomlist, $
                                             /droplist_sel)]
                self.xpos = (round((event.x+0.5)/zf-0.5+self.xzstart) $
                                   > self.xzstart) < self.xzend
                self.ypos = (round((event.y+0.5)/zf-0.5+self.yzstart) $
                                   > self.yzstart) < self.yzend
            ENDIF
            IF self.zoomactive THEN BEGIN
                self.xcursor = self.xpos
                self.ycursor = self.ypos
                do_readout=1b
                do_zoom_draw=1b
                IF event.type EQ 0 OR event.type EQ 1 THEN BEGIN
                    self.zoomactive = 0
                    IF self.xyactive THEN BEGIN
                        IF self.dump THEN print, 'XY branch 1'
                        self.xyactive = 0
                        process_xy_event=1b
                        xyhandler = *(self.xyhandler)
                    ENDIF
                ENDIF
            ENDIF ELSE BEGIN
                IF event.type EQ 0 OR event.type EQ 1 THEN BEGIN
                    self.zoomactive = 1
                    IF self.xyactive THEN BEGIN
                        IF self.dump THEN print, 'XY branch 2'
                        self.xyactive = 0
                        process_xy_event=1b
                        xyhandler = *(self.xyhandler)
                    ENDIF
                ENDIF ELSE BEGIN
                    self.zoomactive = 0
                ENDELSE
            ENDELSE
            END
        ENDCASE
    END
    ;
    ;  Display controls button activates popup to do image scaling,
    ;  color table manipulation, postscript output.
    ;
    self.dbuttonid: display_popup=1b
    ;
    ;  Kill button sets flag to delete widget further down in code.
    ;
    self.killid:  delete_widget=1b
    ;
    ;  Pushing left mouse button in main window activates the
    ;  update to current X and Y positions, which ultimately results
    ;  in update of pixel readout.  Releasing mouse button stops
    ;  X & Y update.  Sliding the scrollbars results in setting the
    ;  viewport_update flag, which ultimately tickles the pan 
    ;  window to move the scrolling box.
    ;
    self.maindisp:  BEGIN
        evname = tag_names(event, /struct)
        CASE evname OF
            'WIDGET_TRACKING':  self.mainactive = 0
            'XCOLORS_LOAD':  self->redraw
            ELSE:  BEGIN
            self.xpos = (event.x>0)<(self.xsize-1)
            self.ypos = (event.y>0)<(self.ysize-1)
            IF event.type EQ 3 THEN BEGIN
                viewport_update = 1b
            ENDIF ELSE BEGIN
                IF self.mainactive THEN BEGIN
                    self.xcursor = self.xpos
                    self.ycursor = self.ypos
                    do_zoom_maint=1b
                    do_zoom_draw=1b
                    do_readout=1b
                    IF event.type EQ 0 OR event.type EQ 1 THEN BEGIN
                        self.mainactive = 0
                        IF self.xyactive THEN BEGIN
                            IF self.dump THEN print, 'XY branch 3'
                            self.xyactive = 0
                            process_xy_event=1b
                            xyhandler = *(self.xyhandler)
                        ENDIF
                    ENDIF
                ENDIF ELSE BEGIN
                    IF event.type EQ 0 OR event.type EQ 1 THEN BEGIN
                        self.mainactive = 1
                        IF self.xyactive THEN BEGIN
                            IF self.dump THEN print, 'XY branch 4'
                            self.xyactive = 0
                            process_xy_event=1b
                            xyhandler = *(self.xyhandler)
                        ENDIF
                    ENDIF ELSE BEGIN
                        self.mainactive = 0
                    ENDELSE
                ENDELSE
            ENDELSE
            END
        ENDCASE
    END
    ELSE:
ENDCASE 

;
;  This is the "doing" part of the program, as opposed to the
;  "knowing" part above.
;
IF delete_widget THEN BEGIN
    ;
    ;  Delete the widget.  Note that the top widget is 
    ;  destroyed, i.e., the whole widget that this display
    ;  is part of.  Make sure all the widgets have cleanup
    ;  routines!
    ;
    ;  Ask user first, if it was set up that way.
    IF self.kill_dialog THEN BEGIN
        answer = dialog_message(['Really exit?'], /question, $
                                title='CONFIRM')
    ENDIF ELSE BEGIN
        answer = 'Yes'
    ENDELSE
    answer = strupcase(strtrim(answer,2)) ; defensive programming
    IF answer EQ 'YES' THEN widget_control, event.top, /destroy
ENDIF ELSE BEGIN     
    ;
    ;  Widget not deleted; go on to actually do stuff.
    ;
    IF imgex THEN BEGIN
        maindisp = self.maindisp
        rep = self.repeat_events
        ;
        ;  This is a hook left over from the non-object version of
        ;  this software.  It is probably a better idea in the world
        ;  of object orientation to use inheritance to add new
        ;  capabilities.  The xyhandler just processes the event
        ;  (whatever window it's in) before the rest of the actions
        ;  called for are taken as well.
        ;
        ;  The xyhandler must be written as a method of the class,
        ;  so that it has access to the data members.
        ;
        IF process_xy_event THEN BEGIN
            call_method, xyhandler, self, event
            IF rep THEN self.xyactive = 1
        ENDIF
        ;
        ;  This block handles motions of the scrollbars on the main
        ;  window.  The viewport motions are transmitted back to the
        ;  pan window so it can update its box cursor.  This is
        ;  done by generating the appropriate fake events and using
        ;  widget_control with send_event.
        ;
        IF viewport_update THEN BEGIN
            geo = widget_info(self.maindisp, /geometry)
            widget_control, self.maindisp, get_draw_view=newview
            IF ptr_valid(self.mainview) THEN ptr_free, self.mainview
            self.mainview = ptr_new(newview)
            ;  Generate new pan cursor
            x0n = round((newview[0]*self.pansize[0])/geo.draw_xsize)>0
            y0n = round((newview[1]*self.pansize[1])/geo.draw_ysize)>0
            x1n = round(((newview[0]+geo.xsize)*self.pansize[0]) $
                  /geo.draw_xsize)<(self.pansize[0]-1)
            y1n = round(((newview[1]+geo.ysize)*self.pansize[1]) $
                  /geo.draw_ysize)<(self.pansize[1]-1)
            xnew = round(0.5*(x0n+x1n))
            ynew = round(0.5*(y0n+y1n))
            event_a = {id:self.pandisp, top:self.imgtop, handler:0L, $
                       type:0, x:xnew, y:ynew, press:1b, $
                       release:0b, clicks:1}
            event_b = {id:self.pandisp, top:self.imgtop, handler:0L, $
                       type:1, x:xnew, y:ynew, press:0b, $
                       release:1b, clicks:1}
            IF widget_info(self.pandisp, /valid) THEN BEGIN
                widget_control, self.pandisp, send_event=event_a
                widget_control, self.pandisp, send_event=event_b
            ENDIF
        ENDIF
        ;  
        ;  This block handles pan window events.  What the user
        ;  sees is a little box outline moving around to represent
        ;  the portion of the whole image shown in the current
        ;  viewport of the main window.
        ;  
        IF scrollable AND do_pan THEN BEGIN
            svwin = !D.window
            geo = widget_info(self.maindisp, /geometry)
            old_pan_exists  = (self.xpstart GE 0) $
                          AND (self.xpend GE self.xpstart) $
                          AND (self.ypstart GE 0) $
                          AND (self.ypend GE self.ypstart)
            IF old_pan_exists THEN BEGIN
                ;  Restore pixels under old pan cursor
                wset, pangraph
                device, copy=[0, 0, $
                              self.xpend-self.xpstart+1, $
                              self.ypend-self.ypstart+1, $
                              self.xpstart, self.ypstart, $
                              self.pan_shad]
            ENDIF
            newview = [(self.xcursor-geo.xsize/2)>0, $
                       (self.ycursor-geo.ysize/2)>0] $
                      < [geo.draw_xsize-geo.xsize, $
                         geo.draw_ysize-geo.ysize]
            widget_control, self.maindisp, set_draw_view=newview
            ;  Generate new pan cursor
            x0n = round((newview[0]*self.pansize[0])/geo.draw_xsize)>0
            y0n = round((newview[1]*self.pansize[1])/geo.draw_ysize)>0
            x1n = round(((newview[0]+geo.xsize)*self.pansize[0]) $
                  /geo.draw_xsize)<(self.pansize[0]-1)
            y1n = round(((newview[1]+geo.ysize)*self.pansize[1]) $
                  /geo.draw_ysize)<(self.pansize[1]-1)
            ;  Save to shadow
            wset, self.pan_shad
            device, copy=[x0n, y0n, x1n-x0n+1, y1n-y0n+1, 0, 0, pangraph]
            ;  Make plot
            wset, pangraph
            plots, [x0n, x1n, x1n, x0n, x0n], $
                   [y0n, y0n, y1n, y1n, y0n], color=boxcolor, $
                   /device, thick=1
            IF svwin GE 0 THEN wset, svwin
            self.xpstart = x0n & self.ypstart = y0n
            self.xpend = x1n & self.ypend = y1n
        ENDIF
        ;
        ;  This block updates the zoom window.  It is updated in
        ;  response either to panning or to moving the cursor around
        ;  in the main window.  The main window shows a moving box
        ;  cursor that outlines the zoomed portion.
        ;
        IF do_zoom_maint AND zoom_exists THEN BEGIN
            ename = tag_names(event, /struct)
            IF (strmid(ename,0,7) EQ 'WIDGET_') OR ename EQ '' THEN BEGIN
                old_zoom_exists = (self.xzstart GE 0) $
                              AND (self.xzend GE self.xzstart) $
                              AND (self.yzstart GE 0) $
                              AND (self.yzend GE self.yzstart)
                IF old_zoom_exists THEN BEGIN
                    svwin = !D.window
                    wset, maingraph
                    device, copy=[0, 0, $
                                  self.xzend-self.xzstart+1, $
                                  self.yzend-self.yzstart+1, $
                                  self.xzstart, self.yzstart, $
                                  self.main_shad]
                    IF svwin GE 0 THEN wset, svwin
                ENDIF
                zf = (*self.zoomfactors)[widget_info(self.zoomlist, $
                                             /droplist_sel)]
                unzoom = floor(float(self.zoomsize)/zf)
                unzoom_half = unzoom/2
                xzstart = (self.xcursor - unzoom_half) > 0
                xzend = (self.xcursor + unzoom_half - 1) < (self.xsize-1)
                yzstart = (self.ycursor - unzoom_half) > 0
                yzend = (self.ycursor + unzoom_half - 1) < (self.ysize-1)
                IF self.xcursor GE self.xsize/2 THEN xzstart = xzend - unzoom + 1 $
                                                ELSE xzend = xzstart + unzoom - 1
                IF self.ycursor GE self.ysize/2 THEN yzstart = yzend - unzoom + 1 $
                                                ELSE yzend = yzstart + unzoom - 1
                xzstart = xzstart > 0
                xzend = xzend < (self.xsize-1)
                yzstart = yzstart > 0
                yzend = yzend < (self.ysize-1)
                self.xzstart=xzstart & self.xzend=xzend
                self.yzstart=yzstart & self.yzend=yzend
                unz_x = xzend-xzstart + 1
                unz_y = yzend-yzstart + 1
                svwin = !D.window
                ;  Box cursor:  Save to shadow
                wset, self.main_shad
                device, copy=[xzstart, yzstart, $
                        xzend-xzstart+1, yzend-yzstart+1, 0, 0, maingraph]
                ;  Make plot
                wset, maingraph
                IF 1b - process_xy_event THEN $
                    plots, [xzstart, xzend, xzend, xzstart, xzstart], $
                           [yzstart, yzstart, yzend, yzend, yzstart], $
                           color=boxcolor, /device, thick=1
                IF svwin GE 0 THEN wset, svwin
            ENDIF
        ENDIF
        IF do_zoom_draw AND zoom_exists THEN self->zoomdraw
        ;
        ;  This block updates the pixel readout.  This is very simple.
        ;  It just uses the current X and Y coordinates that have been
        ;  kept track of above, where keeping track includes correcting 
        ;  for all shrinkage/expansion factors.
        ;
        IF do_readout AND readout_exists AND ptr_valid(self.image) AND $
            self.xcursor GE 0 AND self.ycursor GE 0 THEN BEGIN
            widget_control, self.xreadout, set_value=strn(self.xcursor)
            widget_control, self.yreadout, set_value=strn(self.ycursor)
            sz = size(*self.image) & xsz = sz[1] & ysz = sz[2]
            selfx = (self.xcursor>0)<(xsz-1)
            selfy = (self.ycursor>0)<(ysz-1)
            curpix = (*self.image)[selfx,selfy]
            widget_control, self.preadout, set_value=strn(curpix)
        ENDIF
        IF display_popup THEN self->dpopup
        wset, maingraph
    ENDIF
ENDELSE 

EARLY_EXIT:
IF obj_valid(self) THEN IF self.dump THEN print,'Exiting IMGDISPLAY::e'

RETURN
END

PRO IMGDISPLAY::Tlb, Tlb
;
;  Return to the outside world the ID of top-level base widget 
;  containing the display.
;
next = self.baseid
WHILE next GT 0 DO BEGIN
   tlb = next
   next = widget_info(tlb, /parent)
ENDWHILE
RETURN
END

PRO IMGDISPLAY::ID, Draw_widget_id

;  Gets display subwidget id.
;
;  Input:
;        id = compound widget id
;  Output:
;        draw_widget_id = id of draw subwidget.  This isn't the
;                         same as the graphics window id, which
;                         is the value of the draw subwidget;
;                         the image itself is the value of the
;                         overall compound widget, not of the
;                         draw widget.

draw_widget_id = self.maindisp
RETURN
END

PRO IMGDISPLAY::BASEID, Imgdisplay_id

;  Gets imgdisplay base widget id.

imgdisplay_id = self.baseid
RETURN
END

PRO IMGDISPLAY::GRAB, Array, VERSION=version

;  Grab a pixmap, or the display if version=-1 (default).

IF n_elements(version) NE 1 THEN version=-1

IF version EQ -1 THEN BEGIN
    widget_control, self.maindisp, get_value=newwin
ENDIF ELSE BEGIN
    newwin = self.pixmap[version]
ENDELSE

savewin = !D.window
wset, newwin
device, bypass_translation=1
array = tvrd()
device, bypass_translation=0
IF savewin GE 0 THEN wset, savewin

RETURN
END

PRO IMGDISPLAY::RESIZE, XYTOTAL=xytotal, XYVIEWPORT=xyviewport, $
    SUBWINDOW=subwindow
;+
; NAME:
;       IMGDISPLAY::RESIZE
;
; PURPOSE:
;       This procedure resizes the main draw window.
;
; CATEGORY:
;       Widgets.
;
; CALLING SEQUENCE:
;       obj->RESIZE
;
; INPUT KEYWORD PARAMETERS:
;       XYTOTAL:      2-element vector giving the X and Y size of the
;                     total image area.
;       XYVIEWPORT:   2-element vector giving the X and Y of the
;                     portion actually seen and scrolled.
;       SUBWINDOW:    4-element vector giving portion of total display
;                     actually used to receive data.  Format is
;                     [X0, Y0, NX, NY].
;
; MODIFICATION HISTORY:
;       Written by:     RSH, RITSS, 6 Jan 98
;-

self->id, draw_wid

IF n_elements(xytotal) EQ 2 THEN BEGIN
    IF xytotal[0] GT 0 AND xytotal[1] GT 0 THEN BEGIN 
        widget_control, draw_wid, $
            draw_xsize=xytotal[0], draw_ysize=xytotal[1]
    ENDIF
    IF n_elements(subwindow) NE 4 THEN $
        self.subwindow=[0,0,xytotal]
    self.xsize = xytotal[0]
    self.ysize = xytotal[1]
    IF self.refresh THEN BEGIN
        savewin = !D.window
        FOR i=0, self.nversion-1 DO BEGIN
            wdelete, self.pixmap[i]
            window, /free, /pixmap, $
                xsize=self.subwindow[2], ysize=self.subwindow[3]
            self.pixmap[i] = !D.window
        ENDFOR
        IF savewin GE 0 THEN wset, savewin
    ENDIF
ENDIF

IF n_elements(xyviewport) EQ 2 THEN BEGIN
    IF xyviewport[0] GT 0 AND xyviewport[1] GT 0 THEN BEGIN 
        widget_control, draw_wid, $
            xsize=xyviewport[0], ysize=xyviewport[1]
    ENDIF
    self.x_scroll_size = xyviewport[0]
    self.y_scroll_size = xyviewport[1]
ENDIF

RETURN
END

PRO IMGDISPLAY::XY_SENSITIVE, Xyhandler, REPEAT_EVENTS=rep

;  Sets up a handler for XY cursor events.  RSH, RSTX, 11 Feb. 1998
;  This is left over from the non-object version of this software;
;  in the object-oriented paradigm, it is probably better to use
;  inheritance and define a new class.

;  Input arguments:
;     xyhandler = name of an IDL procedure to handle draw widget
;                 mouse clicks.  The calling sequence must be, e.g.,
;                 clickstuff, event
;                 if xyhandler = 'clickstuff'.  The handler is
;                 explicitly called in IMGDISPLAY::e above.
;
;  Input keyword:
;     /repeat_events:  This allows multiple events to be processed.
;                      The default is that only one event is processed,
;                      then you have to call this routine again to
;                      reinitialize for the next event if desired.
;                 

if self.dump then print,'Entering IMGDISPLAY::xy_sensitive'

;
IF ptr_valid(self.xyhandler) THEN ptr_free, self.xyhandler
self.xyactive = 1
self.xyhandler = ptr_new(xyhandler)
self.repeat_events = keyword_set(rep)

if self.dump then print,'Exiting IMGDISPLAY::xy_sensitive'

return
END

PRO IMGDISPLAY::PSFILE, Psfile, Title

;  Sets the name and title of a Postscript file that may or may
;  not be produced by clicking on the PS file button.
;  Moved data from uvalues to state struct.  RSH, 19 Feb 99

IF self.dump THEN print,'Entering IMGDISPLAY::psfile'
IF ptr_valid(self.psfilename) THEN ptr_free,self.psfilename
IF ptr_valid(self.psfiletitle) THEN ptr_free,self.psfiletitle
self.psfilename = ptr_new(psfile)
self.psfiletitle = ptr_new(title)
IF self.dump THEN print,'Exiting IMGDISPLAY::psfile'

RETURN
END

PRO IMGDISPLAY::REFRESH, SETBUF=setbuf, VERSION=version

;  Refreshes the display (copies pixmap to display).
;
;  /SETBUF does the reverse copy; display to pixmap instead
;  of pixmap to display.

IF self.dump THEN print,'Entering IMGDISPLAY::refresh'

IF n_elements(version) LT 1 THEN version=0

widget_control, self.maindisp, get_value=maingraph

IF keyword_set(setbuf) THEN BEGIN
    wset, self.pixmap[version]
    device, copy=[self.subwindow,0,0,maingraph]
    wset, maingraph
ENDIF ELSE BEGIN
    wset, maingraph
    device, copy=[0,0,self.subwindow[2],self.subwindow[3], $
            self.subwindow[0], self.subwindow[1], $
            self.pixmap[version]]
ENDELSE

IF self.dump THEN print,'Exiting IMGDISPLAY::refresh'

RETURN
END

PRO IMGDISPLAY_CLEANUP, Id

;
;  Widget cleanup routine.  Main job is to find out the associated
;  object and call the cleanup routine for that.

widget_control, id, get_uvalue=this
obj_destroy, this

RETURN
END

PRO IMGDISPLAY::CLEANUP

;  Object cleanup routine, called by widget cleanup routine.
;  26 May 1999 - Don't deallocate image supplied from somewhere else.
;  28 Aug 2000 - Free grayimg.

IF self.dump THEN print,'Entering IMGDISPLAY::cleanup'

IF self.refresh THEN BEGIN
    FOR i=0,self.nversion-1 DO wdelete, self.pixmap[i]
ENDIF

IF self.pan_shad GE 0 THEN wdelete, self.pan_shad
IF self.main_shad GE 0 THEN wdelete, self.main_shad

IF ptr_valid(self.psfilename) THEN ptr_free, self.psfilename
IF ptr_valid(self.psfiletitle) THEN ptr_free, self.psfiletitle
IF ptr_valid(self.xyhandler) THEN ptr_free, self.xyhandler
IF self.last_image_type NE 10 $
    AND ptr_valid(self.image) THEN ptr_free, self.image
IF ptr_valid(self.wedge_array) THEN $
    ptr_free, self.wedge_array
IF ptr_valid(self.zoomfactors) THEN $
    ptr_free, self.zoomfactors
IF ptr_valid(self.grayimg) THEN ptr_free, self.grayimg
IF ptr_valid(self.mainview) THEN ptr_free, self.mainview
IF ptr_valid(self.fixedfont) THEN ptr_free, self.fixedfont

IF self.dump THEN print,'Exiting IMGDISPLAY::cleanup'

RETURN
END

PRO IMGDISPLAY::GETSCALE, Scale, LENGTH=length
;+
; NAME:
;       IMGDISPLAY::GETSCALE
;
; PURPOSE:
;       This procedure returns an array the same length as the color
;       table, telling what data value corresponds to each element.
;
; CATEGORY:
;       Widgets.
;
; CALLING SEQUENCE:
;       obj->GETSCALE, Scale, LENGTH=length
;
; INPUT POSITIONAL PARAMETERS:
;       Id:       Widget ID of CW_SIG_DISPLAY compound widget.
;
; INPUT KEYWORD PARAMETERS:
;       LENGTH:   If you want the returned vector to have a different
;                 length from !D.table_size.
;
; OUTPUT POSITIONAL PARAMETERS:
;       Scale:    Floating point vector giving the data value for
;                 every color.
;
; MODIFICATION HISTORY:
;       Written by:     RSH, RITSS, 6 Jan 98
;       Fixed for bottom and ncolors.  RSH, SSAI, 26 Sep 2001
;-

min_sig = self.lowsigval
max_sig = self.hisigval
log = self.linlogval
sga = self.sigabsval

IF sga THEN BEGIN
    mindata = min_sig
    maxdata = max_sig
ENDIF ELSE BEGIN
    mindata = self.min_other
    maxdata = self.max_other
ENDELSE

IF n_elements(length) LT 1 THEN length = self.ncolors + self.bottom

data = findgen(1000)/999.*(maxdata-mindata)+mindata
IF log THEN bytedata = logscl(data, top=self.ncolors-1) + self.bottom $
       ELSE bytedata = bytscl(data, top=self.ncolors-1) + self.bottom

arg_inp = findgen(length)/(length-1)*(!D.table_size-1)
tabinv, float(bytedata), arg_inp, subs
scale = interpolate(data,subs)

RETURN
END

PRO IMGDISPLAY_V, Id, Image 

;  Stores value for imgdisplay widget; that is to say, displays an image.
;
;  Input arguments:
;      id = widget ID of overall compound widget (not the base
;           widget the next layer up)
;      image = image to be displayed
;

widget_control, id, get_uvalue=this
this->draw, image

RETURN
END

PRO IMGDISPLAY::TICKLE, _EXTRA=extra

;  Move cursor into main graphics window.  To force color table
;  loading in DirectGraphics.  RSH, 29 Aug 2000

widget_control, self.maindisp, get_value=maingraph
wset, maingraph
tvcrs,!D.x_size/2, !D.y_size/2

RETURN
END

PRO IMGDISPLAY::REDRAW, NOOLDZOOM=nooldzoom, _EXTRA=extra

;  Redraw existing image.  Needed for 24-bit mode.  Distinct from
;  refresh, which uses pixmaps.  RSH, 28 Aug 2000

IF self.dump THEN print,'Entering IMGDISPLAY::REDRAW'

IF 1b - ptr_valid(self.image) THEN GOTO, EARLY_EXIT

boxcolor = self.boxcolor

widget_control, self.maindisp, get_value=maingraph
;
;  Do we need to update a panning display?
;
use_pan = 0b
IF widget_info(self.pandisp, /valid) THEN BEGIN
    use_pan = 1b
    widget_control, self.pandisp, get_value=pangraph
    psize = self.pansize
ENDIF
old_pan_exists  = (self.xpstart GE 0) $
              AND (self.xpend GE self.xpstart) $
              AND (self.ypstart GE 0) $
              AND (self.ypend GE self.ypstart)

;
;  Do we need to update a zoom display?
;
use_zoom = 0b
IF widget_info(self.zoomdisp, /valid) THEN BEGIN
    use_zoom = 1b
    widget_control, self.zoomdisp, get_value=zoomgraph
    zf = (*self.zoomfactors)[widget_info(self.zoomlist, $
                                         /droplist_sel)]
    unzoom = floor(float(self.zoomsize)/zf)
ENDIF

IF keyword_set(nooldzoom) THEN BEGIN
    self.xzstart = -1
    self.xzend = -1
    self.yzstart = -1
    self.yzend = -1
ENDIF

old_zoom_exists  = (self.xzstart GE 0) $
              AND (self.xzend GE self.xzstart) $
              AND (self.yzstart GE 0) $
              AND (self.yzend GE self.yzstart)

;
;  How about the density wedge?
;
IF widget_info(self.xreadout, /valid) THEN BEGIN
    widget_control, self.wedge, get_value=wedgegraph
    svwin = !D.window
    wset, wedgegraph
    tv, *self.wedge_array
    IF svwin GE 0 THEN wset, svwin
ENDIF

widget_control, hourglass=1

;
;   In this block, the displaying is actually done.
;
wset, maingraph
tv, *self.grayimg
IF use_pan THEN BEGIN
    svwin = !D.window
    IF ptr_valid(self.grayimg) THEN BEGIN
        wset, pangraph
        tv, byte(round(frebin(*self.grayimg,psize[0],psize[1])))
        ;
        ;  Update panning cursor shadow.
        IF old_pan_exists THEN BEGIN
            svwin = !D.window
            ;  Get new pixels under old pan cursor
            wset, self.pan_shad
            device, copy=[self.xpstart, self.ypstart, $
                          self.xpend-self.xpstart+1, $
                          self.ypend-self.ypstart+1, $
                          0, 0, $
                          pangraph]
            IF svwin GE 0 THEN wset, svwin
        ENDIF
        plots, [self.xpstart, self.xpend, self.xpend, $
                self.xpstart, self.xpstart], $
               [self.ypstart, self.ypstart, self.ypend, $
                self.ypend, self.ypstart], color=boxcolor, $
               /device, thick=1
    ENDIF
    IF svwin GE 0 THEN wset, svwin
ENDIF

;
;   Display zoomed version of new image.
;
IF use_zoom THEN BEGIN
    IF old_zoom_exists THEN BEGIN
        ;  Get new pixels under old zoom cursor
        svwin = !D.window ; defensive programming
        wset, self.main_shad
        device, copy=[self.xzstart, self.yzstart, $
                      self.xzend-self.xzstart+1, $
                      self.yzend-self.yzstart+1, $
                      0, 0, $
                      maingraph]
        self->zoomdraw
        ;  Make plot
        wset, maingraph
        plots, [self.xzstart, self.xzend, self.xzend, self.xzstart, $
               self.xzstart], $
               [self.yzstart, self.yzstart, self.yzend, self.yzend, $
               self.yzstart], $
               color=boxcolor, /device, thick=1
        IF svwin GE 0 THEN wset, svwin
    ENDIF
ENDIF

;
;  If this IMGDISPLAY is set up to be refreshable (i.e., keep
;  track of its own backup pixmaps), then update the pixmaps.
;
IF self.refresh THEN BEGIN
    FOR i=0,self.nversion-1 DO BEGIN
        wset, self.pixmap[i]
        device, copy=[self.subwindow,0,0,maingraph]
    ENDFOR
    wset, maingraph
ENDIF
widget_control, hourglass=0

EARLY_EXIT:

IF self.dump THEN print,'Exiting IMGDISPLAY::REDRAW'

RETURN
END

PRO IMGDISPLAY::ZOOMDRAW
;
;  Display zoomed image
;  RSH, 29 Aug 2000
;  Revisions for small images.  RSH, 30 Oct 2000
;
IF widget_info(self.zoomdisp, /valid) THEN BEGIN

    unz_x = self.xzend-self.xzstart + 1
    unz_y = self.yzend-self.yzstart + 1

    boxcolor = self.boxcolor

    widget_control, self.zoomdisp, get_value=zoomgraph
    zf = (*self.zoomfactors)[widget_info(self.zoomlist, $
                                         /droplist_sel)]
    svwin = !D.window
    IF ptr_valid(self.grayimg) THEN BEGIN
        zoombytes = rebin((*(self.grayimg))[self.xzstart:self.xzend, $
            self.yzstart:self.yzend], unz_x*zf, unz_y*zf, /sample)
        wset, zoomgraph
        tv, zoombytes
    ENDIF
    IF self.zoomxhair THEN BEGIN
        xhair_unz = [self.xcursor - self.xzstart, $
                     self.ycursor - self.yzstart ]
        xhair = xhair_unz*zf + zf/2
        pct = round(0.4*self.zoomsize)
        x_xh_0 = (xhair[0] - pct)>0
        y_xh_0 = (xhair[1] - pct)>0
        x_xh_1 = (xhair[0] + pct)<(unz_x*zf-1)
        y_xh_1 = (xhair[1] + pct)<(unz_y*zf-1)
        plots, [xhair[0],xhair[0]], [y_xh_0,y_xh_1], $
               color=boxcolor,/dev,thick=1
        plots, [x_xh_0,x_xh_1], [xhair[1],xhair[1]], $
               color=boxcolor,/dev,thick=1
    ENDIF
    IF svwin GE 0 THEN wset, svwin
ENDIF
RETURN
END    

PRO IMGDISPLAY::SETDISPLAY, MINVAL=minval, MAXVAL=maxval, $
    LOGARITHMIC=logarithmic, LINEAR=linear, SQUAREROOT=squareroot, $
    HISTEQUAL=histequal, SIGMASCALING=sigmascaling, $
    FLUXSCALING=fluxscaling

;  Set image display scaling parameters.  RSH, 15 May 2001

self.linlogval = 0
self.sigabsval = 1
IF keyword_set(logarithmic) THEN self.linlogval = 1
IF keyword_set(squareroot) THEN self.linlogval = 2
IF keyword_set(histequal) THEN self.linlogval = 3
IF n_elements(minval) GT 0 THEN self.lowsigval = minval[0]
IF n_elements(maxval) GT 0 THEN self.hisigval = maxval[0]
IF n_elements(sigmascaling) GT 0 THEN self.sigabsval = 0

RETURN
END

PRO IMGDISPLAY::DRAW, Image, NOOLDZOOM=nooldzoom, NOSCALE=noscale

;  Display routine for imgdisplay object; called by the widget 
;  value-storing routine.
;
;  /NOOLDZOOM - Don't restore the zoom shadow array.
;  /NOSCALE   - Force TVing the image directly.

IF self.dump THEN print,'Entering IMGDISPLAY::DRAW'

boxcolor = self.boxcolor

widget_control, self.maindisp, get_value=maingraph

imagepar = n_params(0) GT 0
newimage = imagepar AND n_elements(image) GT 0

;
;  Do we need to update a panning display?
;
use_pan = 0b
IF widget_info(self.pandisp, /valid) THEN BEGIN
    use_pan = 1b
    widget_control, self.pandisp, get_value=pangraph
    psize = self.pansize
ENDIF
old_pan_exists  = (self.xpstart GE 0) $
              AND (self.xpend GE self.xpstart) $
              AND (self.ypstart GE 0) $
              AND (self.ypend GE self.ypstart)

;
;  Do we need to update a zoom display?
;
use_zoom = 0b
IF widget_info(self.zoomdisp, /valid) THEN BEGIN
    use_zoom = 1b
    widget_control, self.zoomdisp, get_value=zoomgraph
    zf = (*self.zoomfactors)[widget_info(self.zoomlist, $
                                         /droplist_sel)]
    unzoom = floor(float(self.zoomsize)/zf)
ENDIF

IF keyword_set(nooldzoom) THEN BEGIN
    self.xzstart = -1
    self.xzend = -1
    self.yzstart = -1
    self.yzend = -1
ENDIF

old_zoom_exists  = (self.xzstart GE 0) $
              AND (self.xzend GE self.xzstart) $
              AND (self.yzstart GE 0) $
              AND (self.yzend GE self.yzstart)

;
;  How about the density wedge?
;
IF widget_info(self.xreadout, /valid) THEN BEGIN
    widget_control, self.wedge, get_value=wedgegraph
    svwin = !D.window
    wset, wedgegraph
    tv, *self.wedge_array
    IF svwin GE 0 THEN wset, svwin
ENDIF

;
;  Are we copying our scale parameters from another IMGDISPLAY
;  object used as a template?
;
sct = self.scale_template
use_sct = obj_valid(sct)
nocon = keyword_set(self.nocontrols)

widget_control, hourglass=1

;
;  Update the current image.  If the new image is a pointer, we
;  just copy the pointer.  If it is an array, we move it to heap
;  storage.
;
;  Deallocate image, only if the previous display was from an array
;  rather than a pointer.  If the previous display was from a pointer, 
;  the storage should be managed elsewhere.
IF newimage THEN BEGIN
    ;
    ;  Is the image a pointer or an array?
    ;
    sz = size(image)
    type = sz[sz[0]+1]

    ;
    ;  Is image undefined?  If so, hey, no problem, exit.
    IF type EQ 0 THEN GOTO, EARLY_EXIT
    IF type EQ 10 THEN IF NOT ptr_valid(image) THEN GOTO, EARLY_EXIT

    IF self.last_image_type NE 10 THEN $
        IF ptr_valid(self.image) THEN ptr_free, self.image
    IF type EQ 10 THEN BEGIN   ; If the image argument is a pointer
        self.image = image
    ENDIF ELSE BEGIN           
        ;   In this block, the image arg isn't a pointer, so we
        ;   assume it's a good image array.
        self.image = ptr_new(image)
    ENDELSE
    self.last_image_type = type
ENDIF

;
;  In this block, we figure out if we need scaling parameters
;  then get them if we do need them.
;  
do_scale = 1b     ;  Usually we will scale the image
IF use_sct THEN BEGIN
    ;
    ;  Copy the scaling parameters from another
    ;  IMGDISPLAY object.
    min_sig = sct.lowsigval
    max_sig = sct.hisigval
    linlog = sct.linlogval
    sga = sct.sigabsval
    mno = sct.min_other
    mxo = sct.max_other
    IF sga THEN BEGIN
        mindisp = min_sig
        maxdisp = max_sig
    ENDIF ELSE BEGIN
        mindisp = mno
        maxdisp = mxo
    ENDELSE
ENDIF ELSE BEGIN
    IF nocon THEN BEGIN
        ;  If we have no display controls in existence, and
        ;  no template to be a source of scaling, we just tv 
        ;  the image directly (below).
        do_scale = 0b
    ENDIF ELSE BEGIN
        IF keyword_set(noscale) THEN BEGIN
            ;  Forced using keyword.
            do_scale = 0b
        ENDIF ELSE BEGIN
            ;  Here we are getting the scaling parameters from
            ;  this object itself.
            mindisp = self.lowsigval
            maxdisp = self.hisigval
            linlog = self.linlogval
            sga = self.sigabsval
        ENDELSE
    ENDELSE
ENDELSE

;
;   In this block, the displaying is actually done.
;
IF do_scale THEN BEGIN
    ;
    ;  Scaling
    self->display, maingraph, *self.image, mindisp, maxdisp, info=info, $
                 subwindow=self.subwindow, sctype=linlog, $
                 pixelunits=sga, ncolors=self.ncolors, $
                 min_other=mno, max_other=mxo, $
                 bottom=self.bottom,grayimg=gitmp
    IF ptr_valid(self.grayimg) THEN ptr_free, self.grayimg
    wset, maingraph
    xs = !D.x_size & ys = !D.y_size
    gi = bytarr(xs,ys)
    sw = self.subwindow
    sz = size(gitmp) & xdim = sz[1] & ydim = sz[2]
    xtlim = (sw[2]<(xs-1))<(xdim-1)
    ytlim = (sw[3]<(ys-1))<(ydim-1)
    gi[sw[0],sw[1]] = gitmp[0:xtlim,0:ytlim]
    gitmp = 0
    self.grayimg = ptr_new(gi, /no_copy)
    ;
    ;  We can scale either in image sigma or absolute pixel units.
    ;  Here we keep track of the limits in the "other" units for the
    ;  edification of the user.
    self.min_other = mno
    self.max_other = mxo
    self.lowsigval = mindisp
    self.hisigval = maxdisp
    self.linlogval = linlog
    self.sigabsval = sga
ENDIF ELSE BEGIN
    ;
    ;   No scaling
    IF ptr_valid(self.grayimg) THEN ptr_free, self.grayimg
    gi = byte(*self.image)
    self.grayimg = ptr_new(gi, /no_copy)
    wset, maingraph
    tv, *self.grayimg
ENDELSE
;
;   Display panning version.
;
IF use_pan THEN BEGIN
    svwin = !D.window
    IF ptr_valid(self.grayimg) THEN BEGIN
        wset, pangraph
        tv, byte(round(frebin(*self.grayimg,psize[0],psize[1])))
        ;
        ;  Update panning cursor shadow.
        IF old_pan_exists THEN BEGIN
            svwin = !D.window
            ;  Get new pixels under old pan cursor
            wset, self.pan_shad
            device, copy=[self.xpstart, self.ypstart, $
                          self.xpend-self.xpstart+1, $
                          self.ypend-self.ypstart+1, $
                          0, 0, $
                          pangraph]
            IF svwin GE 0 THEN wset, svwin
        ENDIF
        plots, [self.xpstart, self.xpend, self.xpend, $
                self.xpstart, self.xpstart], $
               [self.ypstart, self.ypstart, self.ypend, $
                self.ypend, self.ypstart], color=boxcolor, $
               /device, thick=1
    ENDIF
    IF svwin GE 0 THEN wset, svwin
ENDIF
;
;   Display zoomed version of new image.
;
IF use_zoom THEN BEGIN
    IF old_zoom_exists THEN BEGIN
        ;  Get new pixels under old zoom cursor
        svwin = !D.window ; defensive programming
        wset, self.main_shad
        device, copy=[self.xzstart, self.yzstart, $
                      self.xzend-self.xzstart+1, $
                      self.yzend-self.yzstart+1, $
                      0, 0, $
                      maingraph]
        self->zoomdraw
        ;  Make plot
        wset, maingraph
        plots, [self.xzstart, self.xzend, self.xzend, self.xzstart, $
               self.xzstart], $
               [self.yzstart, self.yzstart, self.yzend, self.yzend, $
               self.yzstart], $
               color=boxcolor, /device, thick=1
        IF svwin GE 0 THEN wset, svwin
    ENDIF
ENDIF

;
;  If this IMGDISPLAY is set up to be refreshable (i.e., keep
;  track of its own backup pixmaps), then update the pixmaps.
;
IF self.refresh THEN BEGIN
    FOR i=0,self.nversion-1 DO BEGIN
        wset, self.pixmap[i]
        device, copy=[self.subwindow,0,0,maingraph]
    ENDFOR
    wset, maingraph
ENDIF
widget_control, hourglass=0

EARLY_EXIT:
IF self.dump THEN print,'Exiting IMGDISPLAY::DRAW'

RETURN
END

PRO IMGDISPLAY::DISPLAY, Graph, Image, Min_sig, Max_sig, SCTYPE=sctype, $
                 INFO=info, TITLE=title, FILENAME=filename, $
                 GRAYIMG=grayimg, SUBWINDOW=subwindow, $
                 PIXELUNITS=pixelunits, $
                 MIN_OTHER=mno,MAX_OTHER=mxo,NCOLORS=ncolors, $
                 BOTTOM=bottom
;+
; NAME:
;       IMGDISPLAY::DISPLAY
;
; PURPOSE:
;       This procedure does the image scaling for the imgdisplay 
;       object/widget.
;
; CATEGORY:
;       Widgets, image processing
;
; CALLING SEQUENCE:
;       obj->DISPLAY, graph, Image, Min_sig, Max_sig
;
; INPUT POSITIONAL PARAMETERS:
;       Graph:        Graphics window ID for display (NOT the 
;                     draw widget ID)
;       Image:        Image to be displayed
;       IF /pixelunits is set:
;       Min_sig:      Minimum of stretch in image units.
;       Max_sig:      Maximum of stretch in image units.
;       IF pixelunit not set:
;       Min_sig:      Minimum of stretch in std devs above or below
;                     mean.  
;       Max_sig:      Maximum of stretch in std devs above or below
;                     mean.
;
; INPUT KEYWORD PARAMETERS:
;       SCTYPE:       One of 'LINEAR', 'LOG', 'SQRT', 'HIST'.
;                     Default='LINEAR'
;       PS:           Use big_ps to display image to Postscript.  
;                     Default = display to X window 
;       TITLE:        Title of Postscript display.  Default=
;                     'Display Dump'
;       FILENAME:     Filename of Postscript output.  Default=
;                     'sig_display.ps'
;       SUBWINDOW:    [xmin,ymin,xmax,ymax] defining region of display
;                     that is actually to be used.  Default=all.
;       PIXELUNITS:   Set this flag to work in data units rather than
;                     units of std dev.  Default=not set, units
;                     are std dev relative to mean
;       NCOLORS:      Number of byte values to scale to.
;       BOTTOM:       Lowest byte value to scale to.
;
; OUTPUT KEYWORD PARAMETERS:
;       GRAYIMG:      Scaled byte image actually displayed.
;       INFO:      Actual min and max of stretch in data units.
;       MIN_OTHER: Min of stretch in "other" units (sigmas if
;                  /pixelunits set, data units of pixelunits not set)
;       MAX_OTHER: Max of stretch in "other" units.
;
;
; MODIFICATION HISTORY:
;  Bare bones.  RSH, HSTX, 27 Oct. 1997
;  Postscript.  RSH, HSTX, 29 Oct. 1997
;  Zeroes display window before putting up image.  RSH, 3 Apr. 1998
;  3-sigma clip before taking final sigma.  RSH, RSTX, 4 May 1998
;  Redisplay with good notations.  RSH, 4 May 1998
;  Use 1024 random points rather than subimage for sigma.  RSH, 29 Dec 98
;  NCOLORS, BOTTOM.  RSH, 25 Feb 99
;-

if self.dump then print,'Entering imgdisplay::display'
IF n_elements(filename) LT 1 THEN filename='imgdisplay.ps'
IF n_elements(sctype) LT 1 THEN sctype=0
sz = size(image)
xdim = sz[1] & ydim = sz[2]
IF n_elements(subwindow) NE 4 THEN subwindow=[0,0,xdim,ydim]
IF n_elements(ncolors) LT 1 THEN ncolors=!D.table_size
IF n_elements(bottom) LT 1 THEN bottom=0 
nc = ncolors-bottom

nlm = n_elements(image)
irand = (round( (nlm-1)*randomu(seed, nlm<(512L*512L)) ) > 0) $
    < (nlm-1)
imtmp = image[irand]
moment_vec = moment(imtmp, maxmom=2, /nan)
av = moment_vec[0]
stdv = sqrt(moment_vec[1])
w3 = where((imtmp GT (av-3*stdv)) AND (imtmp LT (av+3*stdv)), c3)
IF c3 GT 0 THEN BEGIN
    moment_vec = moment((temporary(imtmp))[w3], maxmom=2, /nan)
    stdv = sqrt(moment_vec[1])
    av = moment_vec[0]
ENDIF ELSE BEGIN
    stdv = 0.5*max(temporary(imtmp))
ENDELSE
IF stdv EQ 0 THEN stdv = 1.0
IF keyword_set(pixelunits) THEN BEGIN
    xmin = min_sig
    xmax = max_sig
    mno = (xmin-av)/stdv
    mxo = (xmax-av)/stdv
ENDIF ELSE BEGIN
    xmin = av+min_sig*stdv
    xmax = av+max_sig*stdv
    mno = xmin
    mxo = xmax
ENDELSE
rev = 1
minpos = 0
IF n_elements(grayimg) LT 1 THEN BEGIN
    CASE sctype OF
        0:  BEGIN
        IF self.dump THEN print,'LINEAR SCALING'
        grayimg = bytscl(image,min=xmin,max=xmax,top=nc-1)+bottom
        END
        1:  BEGIN  
        IF self.dump THEN print,'LOG SCALING'
        grayimg = logscl(image,min=xmin,max=xmax,top=nc-1)+bottom
        END
        2:  BEGIN  
        IF self.dump THEN print,'SQRT SCALING'
        grayimg = sqrtscl(image,min=xmin,max=xmax,top=nc-1)+bottom
        END
        3:  BEGIN  
        IF self.dump THEN print,'HIST SCALING'
        grayimg = histscl(image,min=xmin,max=xmax,top=nc-1)+bottom
        END
    ENDCASE
ENDIF
IF self.dump THEN BEGIN
    print,'MIN(GRAYIMG)=',min(grayimg)
    print,'MAX(GRAYIMG)=',max(grayimg)
    print,'MEAN(GRAYIMG)=',avg(grayimg)
    print,'STDDEV(GRAYIMG)=',stddev(grayimg)
ENDIF
IF keyword_set(ps) THEN BEGIN
    IF keyword_set(pixelunits) THEN tag = ' data units' $
                               ELSE tag = ' sigma'
    scaletitle=strn(min_sig)+' to '+strn(max_sig)+tag
    scaletitle=scaletitle+' ('+sctype+')'
    IF NOT keyword_set(title) THEN title='Display Dump'
    big_ps, grayimg, immin=bottom,immax=ncolors-1, $
      title=title, rev=rev, scaletitle=scaletitle, $
      /nobar,file=filename
ENDIF ELSE BEGIN 
    wset, graph
    blank = bytarr(subwindow[2],subwindow[3])
    tv, blank, subwindow[0], subwindow[1]
    tv, grayimg, subwindow[0], subwindow[1]
ENDELSE 
info = [xmin,xmax]
if self.dump then print,sctype,av,stdv,xmin,xmax,minpos
if self.dump then print,'Exiting imgdisplay::display'
return
END

PRO IMGDISPLAY_DPOPUP_E, Event

;       This procedure is the event handler for image display
;       controls.

widget_control, event.top, get_uvalue=this
this->dpopup_e, event

RETURN
END

PRO IMGDISPLAY::DPOPUP_HELP
helptext=[ $
'Display Controls Popup Widget', $
'  ', $ 
'Summary of capabilities:  ', $
'   (1)  Change image display scaling', $
'   (2)  Change color table', $
'   (3)  Redisplay image', $
'   (4)  Generate PostScript output file', $
' ', $
'Details:', $
' ', $
'   (1)  Change image display scaling', $
' ', $
'        - Change between log, linear, square root, and histogram ', $
'          equalization intensity scaling using buttons at lower left;', $
'          this causes image to be redisplayed.', $
' ', $
'        - Change upper and lower scaling limits by typing them', $
'          into the boxes at the top center and hitting the', $
'          Return (Enter) key on the keyboard.', $
' ', $
'        - Scaling limits can be specified either as a number of sigma', $
'          about the mean value of the image, or directly in image units.', $
'          Which interpretation to give the limits is specified by the ', $
'          buttons underneath the limit boxes.  Note that the statistics', $
'          are computed from a random subset of image pixels and may vary', $
'          somewhat from invocation to invocation.', $
' ', $
'   (2)  Change color table.  Click on the Color/Gray button to invoke', $
'        the IDL program XCOLORS (from Coyote at www.dfanning.com).', $
'        For 8-bit color, update is continuous, whereas for 24-bit color, ', $
'        update is after the choice is made.', $
' ', $
'   (3)  Redisplay image.  Click on the Redisplay button; this works', $
'        whether or not you are changing display parameters.  The same', $
'        effect can be had by clicking in one of the scaling limit boxes', $
'        and hitting the Return (Enter) key.', $
' ', $
'   (4)  Generate PostScript output file.  Click on the PS File button;', $
'        you will be prompted for an output file name.' ]

xdispstr, helptext, title='Help with Display Controls', $
    group=self.dpopupid, font=*self.fixedfont
END

PRO IMGDISPLAY::DPOPUP_E, Event

;
;  The real event handler for the object, called by the
;  event handler for the widget.

IF self.dump THEN print,'Entering IMGDISPLAY::dpopup_e'

;  Routine is in two parts:  (1) figure out the situation,
;  gather data;  (2) do the required actions.

;
;  Do we have scaling controls?
yes_cont = 1b - keyword_set(self.nocontrols)
;
;  Do we want a dialog to choose the PostScript output
;  file name?
ps_dialog = self.ps_dialog

;
;  What sort of facilities have been chosen for the
;  overall IMGDISPLAY object?
zoom_exists = widget_info(self.zoomdisp, /valid)
readout_exists = widget_info(self.xreadout, /valid)
pan_exists = widget_info(self.pandisp, /valid)
widget_control, self.maindisp, get_value=maingraph
image = self.image  ; A pointer, in practice

;
;  If scaling controls exist, get their data.
IF yes_cont THEN BEGIN
    widget_control, self.lowsigid, get_value=min_sig
    widget_control, self.hisigid, get_value=max_sig
    widget_control, self.linlogid, get_value=log
    widget_control, self.sigabsid, get_value=sga
    self.lowsigval = min_sig
    self.hisigval = max_sig
    self.linlogval = log
    self.sigabsval = sga
ENDIF

;
;  Flags for actions to be taken in second part.
redisplay=0b       ; redisplay image
copy_to_ps=0b      ; make a PostScript version
destroy=0b         ; destroy the popup

CASE event.id OF
    self.redisplayid:  redisplay=1b   ; Nothing changed
    self.hisigid:  redisplay=1b    ; High limit changed
    self.lowsigid:  redisplay=1b   ; Low limit changed
    self.sigabsid:  BEGIN
        ;
        ;  Switch between scaling by sigmas and by
        ;  actual pixel data units.
        tmpmn = min_sig
        tmpmx = max_sig
        min_sig = self.min_other
        max_sig = self.max_other
        widget_control, self.lowsigid, set_value=min_sig
        widget_control, self.hisigid, set_value=max_sig
        self.min_other = temporary(tmpmn)
        self.max_other = temporary(tmpmx)
        other_tag = 'data units'
        IF sga THEN other_tag = 'sigma'
        widget_control, self.msgid, set_value = $
            +string(strtrim(string(self.min_other,format='(e10.3)'),2)) $
            + ' - ' +string(strtrim(string(self.max_other, $
            format='(e10.3)'),2)) + ' ' + other_tag       
    END
    ;
    ;  Switch between linear and log
    self.linlogid:  redisplay=1b   
    ;
    ;  Pop up the XCOLORS widget for color tables
    self.xloadctid:   BEGIN
        device, get_visual_name=vn
        vn = strupcase(vn)
        IF vn EQ 'GRAYSCALE' OR vn EQ 'PSEUDOCOLOR' THEN BEGIN
            xcolors,ncolors=self.ncolors,bottom=self.bottom, $
                group=self.dpopupid, /drag
        ENDIF ELSE BEGIN
            IF vn EQ 'DIRECTCOLOR' THEN BEGIN
                xcolors,ncolors=self.ncolors,bottom=self.bottom, $
                    group=self.dpopupid
            ENDIF ELSE BEGIN
                ;receiver = {XCOLORS_NOTIFYOBJ, self, method:'redraw'}
                ;xcolors,ncolors=self.ncolors,bottom=self.bottom, $
                    ;group=self.dpopupid, notifyobj=receiver
                xcolors,ncolors=self.ncolors,bottom=self.bottom, $
                    group=self.dpopupid, $
                    notifyid=[self.maindisp, self.imgtop]
            ENDELSE
        ENDELSE
    END
    ;
    ;  PostScript file desired
    self.psfileid:  BEGIN
        copy_to_ps=1b
    END 
    ;
    ;  Help text
    self.helpid:  BEGIN
        self->dpopup_help
    END
    ;
    ;  Prepare to go away
    self.quitid:  destroy=1b
ENDCASE

sz = size(image)
type = sz[sz[0]+1]
;
;  Type NE 0 means the image is defined (defensive 
;  programming).
IF redisplay AND (type NE 0) THEN BEGIN
    widget_control, hourglass=1
    IF self.dump THEN print, 'maingraph=',maingraph
    ;
    ;  Do the redisplay:
    self->draw
    ;
    ;  Inform the user on scaling results.
    other_tag = 'data units'
    IF self.sigabsval THEN other_tag = 'sigma'
    widget_control, self.msgid, set_value = $
        +string(strtrim(string(self.min_other, $
                        format='(e10.3)'),2)) $
        + ' - ' +string(strtrim(string(self.max_other, $
                                format='(e10.3)'),2)) $
        + ' ' + other_tag
    IF self.refresh THEN BEGIN
        FOR i=0,self.nversion-1 DO BEGIN
            wset, self.pixmap[i]
            device, copy=[self.subwindow,0,0,maingraph]
        ENDFOR
        wset, maingraph
    ENDIF
    widget_control, hourglass=0
ENDIF

;
;  PostScript output.
IF copy_to_ps AND (type NE 0) THEN BEGIN
    widget_control, hourglass=1
    ;
    ;  Set main graphics window.
    wset, maingraph
    ;
    ;  Do we have grayscale colors?
    tvlct, rtst, gtst, btst, /get
    wcolr = where(rtst ne gtst, colr)
    wcolg = where(gtst ne btst, colg)
    IF colr EQ 0 AND colg EQ 0 THEN colkey=0 ELSE colkey=1
    ;
    ;  Do we want a dialog to get the output
    ;  filename?
    IF ps_dialog THEN BEGIN
        IF ptr_valid(self.psfilename) THEN BEGIN
            prev = *self.psfilename
            ptr_free, self.psfilename
        ENDIF ELSE BEGIN
            prev = ''
        ENDELSE
        psfn = dialog_pickfile(file=prev, $
                   group=self.baseid, $
                   title='Specify Output PostScript File', $
                   filter='*.ps', /write)
        self.psfilename = ptr_new(psfn)        
    ENDIF ELSE BEGIN
        psfn = *self.psfilename
    ENDELSE
    ;
    ;  If the filename isn't a null string (CANCEL button
    ;  on dialog_pickfile), the go ahead... almost ...
    IF psfn NE '' THEN BEGIN
        ;
        ;  First, make sure one can write to the selected
        ;  directory (what a pain... a whole 'nother
        ;  "if" level).
        r = execute('openw,lun,psfn,/get_lun')
        IF r THEN BEGIN
            ;
            ;  NOW go ahead.
            free_lun, lun   ; close our fake file
            ;
            ;  There are two hard parts here:
            ;    -- including the pan and zoom windows on the
            ;       output, if they exist
            ;    -- making sure we don't accidentally create a
            ;       graphics window bigger than the screen (a no-no
            ;       in some systems, even for pixmaps)
            wsv = !D.window
            ;
            ;  Get main window geometry
            geo = widget_info(self.maindisp, /geometry)
            ;
            ;  Get main graphics window ID (not same thing as widget ID)
            widget_control, self.maindisp, get_value=win_for_ps
            ;
            ;  Create a pixmap the size of the scrolling area
            window, /free, /pixmap, xsize=geo.xsize, ysize=geo.ysize
            ;
            ;  If we really have a scrolling area, figure out where
            ;  in the image it is; if not, use [0,0]
            IF geo.xsize LT geo.draw_xsize $
              OR geo.ysize LT geo.draw_ysize THEN BEGIN
                widget_control, self.maindisp, get_draw_view=corner
            ENDIF ELSE BEGIN
                corner=[0,0]
            ENDELSE
            ;
            ;  Save pixmap number
            tmp_pixmap = !D.window
            ;
            ;  Copy the scrolling area from the main window into the
            ;  pixmap
            ur_corner = round([geo.xsize,geo.ysize]) + corner - 1
            x_excess = (ur_corner[0] - round(geo.draw_xsize))>0
            y_excess = (ur_corner[1] - round(geo.draw_ysize))>0
            xs_copy = round(geo.xsize) - x_excess
            ys_copy = round(geo.ysize) - y_excess
            device, copy=[corner,xs_copy,ys_copy,0,0,win_for_ps]
            ;
            ;  Handle the blankety-blank visuals.
            device, get_visual_name=visual
            visual = strupcase(visual)
            ;
            ;  TVRD from the pixmap;  hang onto this byte image for
            ;  later
            IF visual EQ 'PSEUDOCOLOR' THEN BEGIN
                mainbytes = tvrd()
            ENDIF ELSE BEGIN
                mainbytes = tvrd(true=3)
            ENDELSE
            ;
            ;  Delete the pixmap
            wdelete, tmp_pixmap
            ;
            ;  Do we need to worry about a panning window?
            ;  If so, get byte image, get size.
            psz = [0L, 0L]
            IF pan_exists THEN BEGIN
                widget_control, self.pandisp, $
                    get_value=pangraph
                window, /free, /pixmap, xsize=self.pansize[0],  $
                    ysize=self.pansize[1]
                tmp_pixmap = !D.window
                device, copy=[0,0,self.pansize,0,0,pangraph]
                IF visual EQ 'PSEUDOCOLOR' THEN BEGIN
                    panbytes = tvrd()
                ENDIF ELSE BEGIN
                    panbytes = tvrd(true=3)
                ENDELSE
                psz = self.pansize
                wdelete, tmp_pixmap
            ENDIF
            ;  
            ;  Do we need to worry about a zoom window?
            ;  If so, get byte image, get size.
            zsz = [0L, 0L]
            IF zoom_exists THEN BEGIN
                widget_control, self.zoomdisp, $
                    get_value=zoomgraph
                window, /free, /pixmap, xsize=self.zoomsize,  $
                    ysize=self.zoomsize
                tmp_pixmap = !D.window
                device, copy=[0,0,self.zoomsize,self.zoomsize,0,0,zoomgraph]
                IF visual EQ 'PSEUDOCOLOR' THEN BEGIN
                    zoombytes = tvrd()
                ENDIF ELSE BEGIN
                    zoombytes = tvrd(true=3)
                ENDELSE
                zsz = self.zoomsize
                wdelete, tmp_pixmap
            ENDIF
            ;
            ;  Figure out size of array needed to tile the main,
            ;  pan, and zoom images together
            xout = ((psz[0] + zsz[0]) > geo.xsize) + 20
            yout = (psz[1] > zsz[1]) + geo.ysize + 50
            ;
            ;  Tile all the byte images we have (from 1 to 3)
            tvlct, rtab, gtab, btab, /get
            IF visual EQ 'PSEUDOCOLOR' THEN BEGIN
                allbytes = bytarr(xout,yout)
                IF pan_exists THEN allbytes[10,30] = panbytes
                IF zoom_exists THEN allbytes[psz[0]+20,30] = zoombytes
                allbytes[10,(psz[1]>zsz[1])+40] = mainbytes
                allbytes[10,10] = *(self.wedge_array)
            ENDIF ELSE BEGIN
                allbytes = bytarr(xout,yout,3)
                IF pan_exists THEN allbytes[10,30,0] = panbytes
                IF zoom_exists THEN allbytes[psz[0]+20,30,0] = zoombytes
                allbytes[10,(psz[1]>zsz[1])+40,0] = mainbytes
                allbytes[10,10,0] = rtab[*(self.wedge_array)]
                allbytes[10,10,1] = gtab[*(self.wedge_array)]
                allbytes[10,10,2] = btab[*(self.wedge_array)]
            ENDELSE
            tmp_device = !D.name

            ;
            ;  Get rid of tvlaser, which I cannot control.
            set_plot, 'ps'
            rat = (6.5/float(xout)) < (9.0/float(yout))
            xout_inches = rat*xout & yout_inches = rat*yout
            device, bits_per_pixel=8, filename=psfn, /color
            device, xoffset=1, yoffset=1, xsize=6.5, ysize=9, $
                /inches, /portrait
            IF visual EQ 'PSEUDOCOLOR' THEN BEGIN
                tvlct, rtab, gtab, btab
                tv, allbytes
            ENDIF ELSE BEGIN
                tvlct, bindgen(256), bindgen(256), bindgen(256)
                tv, allbytes, true=3
            ENDELSE
            device,/close
            ;
            ;tvlaser, file=psfn, /noprint, color=colkey, $
                ;title=*self.psfiletitle, bottom=self.bottom, $
                ;ncolors=self.ncolors
            set_plot, tmp_device
            tvlct, rtab, gtab, btab
            ;
            ;  Reset graphics window we had to start with, if valid
            ;  (defensive programming)
            IF wsv GE 0 THEN wset, wsv
            ;
            ;  Notify user in text area of popup
            widget_control, self.msgid, $
                set_value='Wrote ' + strtrim(*self.psfilename)
        ENDIF ELSE BEGIN
            ;
            ;  Bottom half of IF-block where we tried to open a file
            ;  in the selected directory.
            mess = ['Cannot plot to PostScript file', $
                    psfn, $
                    'Wrong directory?']
            junk = dialog_message(mess, /error)
        ENDELSE
    ENDIF

    wset, maingraph
    widget_control, hourglass=0
ENDIF

IF destroy THEN BEGIN
    widget_control, event.top, /destroy
ENDIF

IF self.dump THEN print,'Exiting IMGDISPLAY::dpopup_e'

RETURN
END

PRO IMGDISPLAY::DPOPUP

;       This procedure realizes the display controls popup.
;

IF self.dump THEN print,'Entering IMGDISPLAY::dpopup'

;
;  Only one copy per customer, please.  (Each IMGDISPLAY
;  object can have one copy going.)
IF widget_info(self.dpopupid,/valid_id) THEN BEGIN
    widget_control, self.dpopupid, /show
    IF self.dump THEN print,'Exiting IMGDISPLAY::dpopup'
    RETURN
ENDIF

;
;  Flag to tell whether we need controls as such:  image
;  scaling and color tables, apart from the PostScript button.
yes_cont = 1b-keyword_set(self.nocontrols)

;
;  Does zoom display exist?
yes_zoom = widget_info(self.zoomdisp, /valid)

;
;  Set up top level base and subpanels.  Note that we don't
;  make it modal.
widget_control, self.maindisp, get_value=mw
title = 'CONTROLS (Window '+strn(mw) + ')'
panel = widget_base(col=1,group_leader=self.baseid, title=title)
self.dpopupid=panel
panel_c1 = widget_base(panel,col=1)
panel_c1a = widget_base(panel_c1,col=1)
panel_c1b = widget_base(panel_c1a,col=1,/frame)

;
;  Scaling/display controls are desired:  upper and lower
;  limits, selection of units
IF yes_cont THEN BEGIN
    self.labelid = widget_label(panel_c1b, value='Display Limits')
    self.hisigid = cw_field(panel_c1b, title='Upper:  ', $
                       /float,/return_events, $
                       value=self.hisigval, $
                       fieldfont=*self.fixedfont)
    self.lowsigid = cw_field(panel_c1b, $
                             title='Lower:  ', $
                             /float,/return_events, $
                             value=self.lowsigval,fieldfont=*self.fixedfont)
    self.sigabsid = cw_bgroup(panel_c1b, $
                        ['sigma','data units'], /row, $
                        /exclusive, $
                        set_value=self.sigabsval, /no_release)
ENDIF

;
;  More subpanels.
panel_c23_all = widget_base(panel,col=1)
panel_c23_top = widget_base(panel_c23_all,row=1)
panel_c2 = widget_base(panel_c23_top,col=1,/frame)

;
;  Scaling/display control are desired:  linear/log selection
IF yes_cont THEN BEGIN
    self.linlogid = cw_bgroup(panel_c2, ['linear','log', 'sqrt', 'hist eq'], $
                       /col, label_top='Scaling Type', /exclusive, $
                       set_value=self.linlogval, /no_release)
ENDIF

;  Another subpanel
panel_c3 = widget_base(panel_c23_top,col=1,/frame)
label2a = widget_label(panel_c3,value='Controls')
self.redisplayid = widget_button(panel_c3, value='Redisplay')

;
;  Scaling/display controls are desired:  color table manipulation,
IF yes_cont THEN BEGIN
    self.xloadctid = widget_button(panel_c3, value='Color/Gray')
ENDIF

;
;  If you have the popup at all, you get the PostScript file
;  capability and the popup quit button.
self.psfileid = widget_button(panel_c3, value='PS File')
self.helpid = widget_button(panel_c3, value='Help')
self.msgid = widget_text(panel_c23_all, ysize=1,xsize=40,value=' ')
self.quitid = widget_button(panel_c3,value='DISMISS')

;
;  Text display.
IF yes_cont THEN BEGIN
    other_tag = 'data units'
    IF self.sigabsval THEN other_tag = 'sigma'
    msgstr = string(strtrim(string(self.min_other,format='(e10.3)'),2)) $
                + ' - ' +string(strtrim(string(self.max_other, $
                format='(e10.3)'),2)) + ' ' + other_tag       
    widget_control, self.msgid, set_value=msgstr
ENDIF

xmanager, 'IMGDISPLAY::dpopup', $
    self.dpopupid, event_handler='IMGDISPLAY_dpopup_e', /no_block
widget_control, self.dpopupid, /realize
widget_control, self.dpopupid, set_uvalue=self
IF self.dump THEN print,'Exiting IMGDISPLAY::dpopup'

RETURN
END

FUNCTION IMGDISPLAY::INIT, Parent, TITLE=title, $
                         XSIZE=xsize, YSIZE=ysize, $
                         X_SCROLL_SIZE=x_scroll_size, $
                         Y_SCROLL_SIZE=y_scroll_size, $
                         KILL_BUTTON=kill_button, $
                         FIXEDFONT=fixedfont,REFRESH=refresh, $
                         SUBWINDOW=subwindow,NOCONTROLS=nocontrols, $
                         SCALE_TEMPLATE=scale_template, $
                         NCOLORS=ncolors,BOTTOM=bottom, $
                         PS_DIALOG=ps_dialog,NOBUTTONS=nobuttons, $
                         ZOOMWIN=zoomwin,PANWIN=panwin, $
                         READOUT=readout,VERTICAL=vertical, $
                         BOXCOLOR=boxcolor,KILL_DIALOG=kill_dialog,  $
                         DUMP=dump

;  Make sure device is X-windows.  When this wasn't in, it got the
;  synchronization of moving boxes all messed up, if the device was
;  changed after the fact.

set_plot, 'x'

;  The important routine to generate the display window.
;  See comments at top of file.

yes_buttons = 1b-keyword_set(nobuttons)
yes_panwin = keyword_set(panwin)
yes_zoomwin = keyword_set(zoomwin)
yes_readout = keyword_set(readout)
yes_extras = yes_panwin OR yes_zoomwin OR yes_readout
yes_dump = keyword_set(dump)

IF yes_dump THEN print,'Entering imgdisplay::INIT'

;
;  Find top-level widget
;
above = parent
REPEAT BEGIN
    lastabove = above
    above = widget_info(above, /parent)
ENDREP UNTIL above EQ 0
top = lastabove

imgd_base = widget_base(parent, col=1, $
                      kill_notify='imgdisplay_cleanup', $
                      frame=2)
                      
;
;  The event-generating widgets have to be at the same level so that
;  the event handler can find the state structure.
;
imgd_buttons = widget_base(imgd_base, row=1)
IF keyword_set(title) THEN $
    title = widget_label(imgd_buttons, value=title, /align_left)
dbutton = -1L & kill = -1L

;  Do we want a button to create a popup for image scaling,
;  Postscript output, etc.?

kill_dialog = keyword_set(kill_dialog)
IF yes_buttons THEN BEGIN
    IF keyword_set(kill_button) THEN BEGIN 
        kill = widget_button(imgd_buttons,value='QUIT WIDGET')
    ENDIF 
    dbutton = widget_button(imgd_buttons, value='CONTROLS')
ENDIF

;  Do we want a zoom window?  If so, set up a droplist with
;  choices of zoom factor.

zoomlist = -1L
zoomfactors = [2,5,10,15,20]
IF yes_zoomwin THEN BEGIN
    zoomvalue = 'Zoom '+strtrim(string(zoomfactors,format='(I3)'),2)
    zoomlist = widget_droplist(imgd_buttons,value=zoomvalue)
    widget_control, zoomlist, set_droplist_select=1
ENDIF

;  Do we want to confine our display to part of the main window
;  (to leave room, e.g., for a plot)?

IF n_elements(subwindow) NE 4 THEN $
    subwindow=[0,0,xsize,ysize]

;  Create main window.

IF n_elements(x_scroll_size) LT 1 THEN x_scroll_size = -1L
IF n_elements(y_scroll_size) LT 1 THEN y_scroll_size = -1L
IF x_scroll_size GT 0 AND y_scroll_size GT 0 THEN BEGIN
    scrollable = x_scroll_size LT xsize OR  y_scroll_size LT ysize
ENDIF ELSE BEGIN
    scrollable = 0
ENDELSE
imgd_display = widget_base(imgd_base, row=1)
IF scrollable THEN BEGIN
    maindisp = widget_draw(imgd_display, x_scroll_size=x_scroll_size, $
                       y_scroll_size=y_scroll_size, $
                       xsize=xsize, ysize=ysize, /button_events, $
                       /motion_events, /tracking_events, /scroll)
ENDIF ELSE BEGIN
    maindisp = widget_draw(imgd_display,  $
                       xsize=xsize, ysize=ysize, /button_events, $
                       /motion_events, /tracking_events)
ENDELSE
IF scrollable THEN widget_control, maindisp, /draw_viewport_events

;  Do we want the widget to keep track of pixmaps for refreshing
;  the display?

nversion = 0
pixmap = lonarr(20)
IF keyword_set(refresh) THEN BEGIN
    nversion = round(refresh[0])
    IF nversion GT 20 THEN BEGIN
        message, 'Number of backup pixmaps truncated from '+ $
            strn(nversion) + ' to 20.', /info
        nversion = 20
    ENDIF
    ref=1b
    savewin = !D.window
    FOR i=0, nversion-1 DO BEGIN
        window, /free, /pixmap, xsize=subwindow[2], ysize=subwindow[3]
        pixmap[i] = !D.window
    ENDFOR
    IF savewin GE 0 THEN wset, savewin
ENDIF ELSE BEGIN
    ref=0b
    pixmap = -1L
ENDELSE

rep = 0b

nocon = keyword_set(nocontrols)
IF n_elements(scale_template) LT 1 THEN scale_template = obj_new()

IF n_elements(ncolors) LT 1 THEN ncolors=!D.table_size
IF n_elements(bottom) LT 1 THEN bottom=0
ps_dialog = keyword_set(ps_dialog)

pandisp = -1L
zoomdisp = -1L
zoomxswitch = -1L
pan_shad = -1L
main_shad = -1L

;  Set up "extras":  pan window, zoom window, pixel readout

IF yes_extras THEN BEGIN

    extras_frame = widget_base(imgd_base, row=1)

    IF yes_panwin THEN BEGIN
        IF (n_elements(panwin) GT 1) OR (panwin[0] GT 1) THEN  $
            pansize=panwin ELSE pansize=100
        IF n_elements(pansize) EQ 1 THEN pansize = [pansize, pansize]   
        pandisp = widget_draw(extras_frame, xsize=pansize[0], $
                      ysize=pansize[1], /button_events, /motion_events, $
                      /tracking_events)
        ;
        ;   Set up pixmap to hold cursor shadow - size is upper limit
        ;   - no need to be exact, just hold it under screen size
        ;   for CDE
        svwin = !D.window
        window, /free, /pixmap, xsize=pansize[0], ysize=pansize[1]
        pan_shad = !D.window & IF svwin GE 0 THEN wset, svwin
    ENDIF ELSE BEGIN
        pansize = -1
        pan_shad = -1
    ENDELSE
    IF yes_zoomwin THEN BEGIN
        IF zoomwin GT 1 THEN zoomsize=zoomwin ELSE zoomsize=100
        zoomdisp = widget_draw(extras_frame, xsize=zoomsize, $
                       ysize=zoomsize, /button_events, /motion_events, $
                       /tracking_events)
        ;
        ;  Cross-hair control in zoom window
        zoomxswitch = widget_button(extras_frame, /no_release, $
                      value='+')
        ;
        ;   Set up pixmap to hold cursor shadow
        svwin = !D.window
        xshad = zoomsize & yshad = zoomsize
        window, /free, /pixmap, xsize=xshad, ysize=yshad
        main_shad = !D.window & IF svwin GE 0 THEN wset, svwin
    ENDIF ELSE BEGIN
        zoomsize = -1
        main_shad = -1
    ENDELSE
    boxcolor = -1L
    IF yes_panwin OR yes_zoomwin THEN BEGIN
        IF n_elements(boxcolor) LE 0 THEN boxcolor=255L $
                                     ELSE boxcolor=long(boxcolor[0])
        device, get_decomposed=decomp
        IF decomp THEN boxcolor = boxcolor + 256*boxcolor + 256^2*boxcolor
    ENDIF
    IF yes_readout THEN BEGIN
        IF keyword_set(vertical) THEN thisbase = imgd_base $
                                 ELSE thisbase = extras_frame
        readout_frame = widget_base(thisbase, col=1, frame=2)
        wrow = widget_base(readout_frame, row=1)
        quantity_row = widget_base(readout_frame, row=1)
        label_column = widget_base(quantity_row, col=1)
        value_column = widget_base(quantity_row, col=1)
        xlab = widget_label(label_column, value='X: ')
        ylab = widget_label(label_column, value='Y: ')
        plab = widget_label(label_column, value='Pixel: ')
        xreadout = widget_label(value_column, value=strn(-999.9), xsize=70)
        yreadout = widget_label(value_column, value=strn(-999.9), xsize=70)
        preadout = widget_label(value_column, value=strn(-999.9), xsize=150)
        wedge = widget_draw(wrow, xsize=200, ysize=10)
    ENDIF ELSE BEGIN
        xreadout = -1
        yreadout = -1
        preadout = -1
        wedge = -1
    ENDELSE
ENDIF ELSE BEGIN
    pansize = -1
    zoomsize = -1
    xreadout = -1
    yreadout = -1
    preadout = -1
    main_shad = -1
    pan_shad = -1
    wedge = -1
    boxcolor = -1
ENDELSE

IF n_elements(fixedfont) LT 1 THEN $
    fixedfont='-misc-fixed-bold-r-normal--15-140-75-75-c-90-iso8859-1'

;  Fill in object data fields.

self.imgtop=top
self.baseid=imgd_base
self.button_frame=imgd_buttons
self.image=ptr_new()
self.last_image_type=-1L
self.xsize=xsize
self.ysize=ysize
self.x_scroll_size=x_scroll_size
self.y_scroll_size=y_scroll_size
self.dbuttonid=dbutton
self.dpopupid=-1L
self.redisplayid=-1L
self.quitid=-1L
self.labelid=-1L
self.lowsigid=-1L
self.hisigid=-1L
self.maindisp=maindisp
self.xloadctid=-1L
self.linlogid=-1L
self.sigabsid=-1L
self.lowsigval=-1.0
self.hisigval=3.0
self.linlogval=0L
self.sigabsval=0L
self.xcursor=-1L
self.ycursor=-1L
self.xpos=-1L
self.ypos=-1L
self.xyactive=0L
self.xyhandler=ptr_new()
self.killid=kill
self.psfileid=-1L
self.helpid=-1L
self.msgid=-1L
self.min_other=-32768.0
self.max_other=-32768.0
self.refresh=ref
self.nversion=nversion
self.pixmap=pixmap
self.subwindow=subwindow
self.repeat_events=rep
self.nocontrols=nocon
self.scale_template=scale_template
self.bottom=bottom
self.ncolors=ncolors
self.ps_dialog=ps_dialog
self.kill_dialog=kill_dialog
self.psfilename = ptr_new('imgdisplay.ps')
self.psfiletitle = ptr_new('IMGDISPLAY Image')
self.pandisp = pandisp
self.pansize = pansize
self.panactive = 0L
self.mainactive = 0L
self.zoomactive = 0L
self.zoomxhair = 1L
self.zoomdisp = zoomdisp
self.zoomxswitch = zoomxswitch
self.zoomsize = zoomsize
self.zoomlist = zoomlist
self.zoomfactors = ptr_new(zoomfactors)
self.xreadout = xreadout
self.yreadout = yreadout
self.preadout = preadout
self.main_shad = main_shad
self.pan_shad = pan_shad
self.boxcolor = boxcolor
self.wedge = wedge
self.wedge_array = ptr_new(  $
    byte(round(indgen(200)*float(ncolors-1)/199.0)+bottom) $
    # make_array(10, value=1b) )
self.fixedfont = ptr_new(fixedfont)
self.dump = yes_dump

IF yes_dump THEN BEGIN
    print,'IMGDISPLAY Base = ',imgd_base
ENDIF

widget_control, imgd_base, set_uvalue=self
widget_control, imgd_base, event_pro='imgdisplay_e', $
  pro_set_value='imgdisplay_v'

IF yes_dump THEN print,'Exiting imgdisplay::INIT'
RETURN,1
END

PRO IMGDISPLAY__DEFINE

;  Defines the imgdisplay object and the state structure contained
;  in it.  Routine name formatted for IDL autodefinition.

resolve_routine, 'xcolors'  ; Because a contained object
                            ; definition may be needed

struct  = {IMGDISPLAY, imgtop:-1L, baseid:-1L, $
         button_frame:-1L, image:ptr_new(), last_image_type:-1L, $
         xsize:0L,ysize:0L,x_scroll_size:0L, y_scroll_size:0L, $
         maindisp:-1L, dbuttonid:-1L, $
         dpopupid:-1L, redisplayid:-1L, quitid:-1L, $
         labelid:-1L, lowsigid:-1L, hisigid:-1L, $
         xloadctid:-1L, linlogid:-1L, sigabsid:-1L, $
         lowsigval:-1.0, hisigval:3.0, $
         linlogval:0L, sigabsval:0L, $
         xcursor:-1L, ycursor:-1L, xpos:-1L, ypos:-1L, xyactive:0L, $
         xyhandler:ptr_new(), killid:-1L, psfileid:-1L, helpid:-1L, $
         psfilename:ptr_new(), psfiletitle:ptr_new(), $
         msgid:-1L, min_other:-32768.0, max_other:-32768.0, $
         grayimg:ptr_new(), refresh:0L, nversion:0L, pixmap:lonarr(20), $
         subwindow:lonarr(4), $
         repeat_events:0L, nocontrols:0L, $
         scale_template:obj_new(), $
         bottom:0L, ncolors:0L, ps_dialog:0L, kill_dialog:0L, $
         pandisp:-1L, pansize:[-1L,-1L], panactive:0L, mainactive:0L, $
         zoomactive:0L, zoomxhair:1L, $
         zoomxswitch:-1L,zoomdisp:-1L, zoomsize:-1L, zoomlist:-1L, $
         zoomfactors:ptr_new(), $
         xzstart:-1L, yzstart:-1L, xzend:-1L, yzend:-1L, $
         xpstart:-1L, ypstart:-1L, xpend:-1L, ypend:-1L, $
         xreadout:-1L, yreadout:-1L, preadout:-1L, $
         main_shad:-1L, pan_shad:-1L, boxcolor:255L, $
         mainview:ptr_new(), $
         wedge:-1L, wedge_array:ptr_new(), fixedfont:ptr_new(), $
         dump:0 }
         
RETURN
END