;+
; NAME:
;     SOLAID
; PURPOSE:
;     Solar On-Line Analysis and Interactive Display, of impeccable lineage,
;     based on the routine of the same name written by Dan Briggs and serving
;     us for over a decade under the old system.  Monitors real-time data and
;     displays it in various forms for monitoring data quality and health
;     of the system.
; CATEGORY:
;     OVRO APC REAL-TIME DISPLAY
; CALLING SEQUENCE:
;     Solaid[,filename][,GROUP=group]
; INPUTS:
;     filename   the name of the file containing data to be monitored.
;                  Should always be DAILY.ARC, but this is included for
;                  generality.
;     group      the widget ID of a base widget from the calling routine
;                  so that when the calling routine exits this widget
;                  will also be destroyed.  Default is zero--no group leader.
; OPTIONAL (KEYWORD) INPUT PARAMETERS:
; ROUTINES CALLED:
;     openarc, utplot, cw_plmonitor, cvdoy, get_color, getdata, newscan, get_cycle
; OUTPUTS:
; COMMENTS:
; SIDE EFFECTS:
; RESTRICTIONS:
; MODIFICATION HISTORY:
;     Written 24-Aug-1998 by Dale Gary (based on SOLCHEK and WPLDPLOT)
;     04-Sep-1998  DG
;       First reasonably working version.
;     12-Sep-1998  DG
;       Implemented changes to use new CW_PLMONITOR() routine.
;     20-Sep-1998  DG
;       Added labels to Dynamic Spectrum plot.  Changed symbols for phases
;       to open squares.  Fixed date labelling in time plots to reflect
;       date of header.
;     20-Dec-1998  DG
;       Made changes to reflect changes to NEWSCAN.
;     20-Jan-1999  DG
;       Fixed problem with cycle time calculation, when cycle was shorter
;       than one record duration.  Also slight bug in plotting the dynamic
;       spectrum, which sometimes caused solaid to crash.
;     07-Feb-1999  DG
;       Removed functions DOTIMEAVG and READ_TPFAC to files of their own
;     14-Feb-1999  DG
;       Added 5 s and 10 s time-average choices
;     14-May-1999  DG
;       Major rewrite to use newly updated GET_CYCLE() routine, which
;       simplifies the code considerably.
;     23-May-1999  DG
;       Major enhancements.  Added background subtraction, spectral mode.
;       Still some refinements to come, but it basically works.
;     15-Jun-1999  DG
;       Several major enhancements.  Finished file handling so that it
;       works with existing files, not just the DAILY.ARC.  Select the
;       File/Open from the file menu.  Select REALTIME menu item to go
;       back to real time operation reading DAILY.ARC.
;       Added a switch to start and stop updating of the plots (actually
;       stops reading the file by turning the timer off).
;       Added a snapshot menu item to allow the dynamic spectrum to be
;       saved as a JPG file for putting on the web.
;     16-Jun-1999  DG
;       Fixed some bugs related to switching scans, and also made it
;       non-blocking.
;     20-Jun-1999  DG
;       Changed snapshot to save all of the plot windows as well as the
;       dynamic spectrum.  Saving as jpeg is not good for plots, but I
;       haven't solved the problem of capturing decomposed colors using
;       TVRD().
;     21-Jun-1999  DG
;       Solved the problem of creating GIF rather than JPG. The trick was
;       to use the COLOR_QUAN() routine.  Also fixed up a couple of
;       snapshot things (fonts) and added the sourcename to the title.
;     05-Jul-1999  DG
;       Changed output directory for .GIF files (Snapshot files) to !DEFAULTS.WEBDIR.
;     22-Oct-1999  DG
;       Changed to close DAILY.ARC upon getting an EOF record.  Note that
;       this will not work if in UPDATE_OFF state at the time...
;     02-Nov-1999  DG
;       Added calculation of u,v coordinates.
;     13-Nov-1999  DG
;       Added code to close file at the end of a scan if in non-realtime mode.
;       Also added a logscale button.
;     14-Nov-1999  DG
;       Removed most (hopefully all) dependence on 5-element data, so the
;       routine should work with any combination of antennas.
;     13-Dec-1999  DG
;       Added application of total power update calibration.
;     11-Jan-2000  DG
;       Eliminated hardwired directory locations.
;     05-Feb-2000  DG
;       Removed now unnecessary sin and cos lookup tables in GAINCOR call.
;     28-Jun-2000  DG
;       Fixed time averaging (now called sample averaging) to be perfectly robust.
;       Also changed plot symbol to 10, to show amplitudes as bar graph.
;     09-Nov-2000  DG
;       Turn off background subtraction on scan change.
;     04-Jan-2001  DG
;       Fixed some long-standing, annoying little bugs--auto-change of scan
;       ought to put correct channel in each window, and text list widgets
;       now update properly when window is changed.
;     08-May-2001  DG
;       Fix SOLAID crash when changescan() newscan call returned -1
;     13-Jul-2001  DG
;       Added NOTRACK button, for showing data while dishes not tracking.  This
;       should be made more clever.  Now it only affects data not yet read, and
;       after data are read with NOTRACK, they cannot be hidden again.
;     20-Jun-2002  DG
;       Converted to use PIXMAPs, which speeds up the screen updating by a
;       large factor.
;     23-Dec-2002  DG
;       Fixed bug in ANTLIST in main routine.
;     15-Jul-2003  DG
;       Changed read-ahead record number from 10 to 50, so that when SOLAID
;       is reading from a file to catch up, it reads 50 cycles at a time.
;     23-Jul-2003  DG
;       Changed GET_COLOR to OVSA_GET_COLOR to avoid conflict
;       with routine of the same name in SMM tree.
;     06-Nov-2003  DG
;       Slight change to DIM3LABEL handling to allow for cross-polarization.
;       Also fixed default polarization setting to use R and L instead of I.
;     04-Oct-2004  DG
;       Slight change to skip reduction of polarization channels after 2004.231.
;       Now that dual-polarization feeds are done, the data format is standardized
;       to two polarization channels, even when obseq only measures one poln.
;       Any missing polarization is treated as NaN.
;     23-Jul-2004  DG
;       Added use of pshift, which is set to [0,0] but can be changed via
;       break point interruption at the entry to SOLAID and manual entry.
;     25-Jul-2005  DG
;       Rescale spectral plots to avoid scaling to glitches (i.e. interference).
;       Scheme looks at histogram and uses peak value that occurs more than
;       twice in a bin.
;-

;--------------------------------------------------------------------------

function get_bgnd,pdata,tselect

   ; Extract the desired part of the data, defined as 10 records centered on
   ; the index TSELECT.  If TSELECT is at the end of the existing record range,
   ; the nearest possible 10 records is used.
   ntimes = n_elements((*pdata)[0,0,0,*])
   ; Set nominal range, but capped at 0 and/or NTIMES-1
   srange = [(tselect-5)>0,(tselect+4)<(ntimes-1)]
   ; If bottom end is 0, set top end at 10
   if (srange(0) eq 0) then srange(1) = 10
   ; If top end is ntimes-1, set bottom end at ntimes-10
   if (srange(1) eq ntimes-1) then srange(0) = ntimes-10
   ; Handle case where there are less than 10 records total
   srange(0) = srange(0)>0
   srange(1) = srange(1)<(ntimes-1)

   ; Select the required range of data
   data = (*pdata)(*,*,*,srange[0]:srange[1])

   ; Construct flag array for keeping track of bad data (=1 where data good,
   ; or 0 where data bad)
   dflg = data*0 + 1
   bad = where(finite(data) ne 1,nbad)
   if (nbad ne 0) then begin
      dflg(bad) = 0
      data(bad) = 0
   endif

   ; Get average over the last index
   bgnd = total(data,4)/(total(dflg,4)>1)

return,bgnd
end

pro changescan,state

   ; Got a header, so read the new header information, update
   ; the STATE variable, clear the plots and start getting
   ; new data.

   ; For real time data, nstitute a wait to ensure that all of the
   ; ancillary records have time to get written after the header.
   if (state.realtime) then wait,10

   ; Free memory contained in OBSEQ pointers
   obseq = state.obseq
   a = *state.filptr
   ptr_free,obseq.ppol
   ptr_free,obseq.pharm
   ptr_free,obseq.pnd
   ptr_free,obseq.phord
   ptr_free,obseq.phflag
   ; (may want to write existing data to a save file before
   ; freeing this memory, but this can wait...)
   ptr_free,state.pRecent
   ptr_free,state.pOld
   ptr_free,state.ptcycle
   ptr_free,state.puv
   ptr_free,state.pGeometry
   ptr_free,state.pGParm
   ptr_free,state.ppol
   ptr_free,state.pbigdata
   ptr_free,state.pnrpt
   ptr_free,state.pindx
   state.BgndApply = 0  ; Turn off background subtraction
   WIDGET_CONTROL,state.SubApply,SET_BUTTON=0


   ; Run the procedure to read header information from the scan and
   ; declare pointers to data arrays
   nrec = state.nrec
   hrec = state.rec
   pshift = state.pshift
   rec = newscan(a,nrec,hrec,header,obseq,cfg,traj,refcal,gparm,geometry,pRecent,pOld,cycle,pshift=pshift)
   if (rec eq -1) then begin
      ; NEWSCAN returned an error
      ans = WIDGET_MESSAGE('Error in file structure or missing segment.',/error)
      free_lun,state.lun
      nextid = widget_info(/parent,state.specid)
      while (nextid ne 0) do begin
         topid = nextid
         nextid = widget_info(/parent,topid)
      endwhile
      WIDGET_CONTROL, TopId, /DESTROY
      return
   endif
   srcname = traj.srcname

   tls = tl_decode(getdata(hrec-1,a))
   WIDGET_CONTROL,state.DateID,SET_VALUE='Date: '+tls.yrday+tls.timstr
   if (srcname ne '') then WIDGET_CONTROL,state.NameID,SET_VALUE='Name: '+srcname $
                      else WIDGET_CONTROL,state.NameID,SET_VALUE='Name: <Unknown>'

   state.srcname = srcname
   ; Put returned variables into STATE structure
   state.header = header
   state.obseq  = obseq
   state.cfg    = cfg
   state.refcal = refcal

   state.nent = 0
   if (size(geometry,/tname) ne 'STRUCT') then begin
      state.pGeometry = ptr_new(0)
      state.pGParm  = ptr_new(0)
      state.pRecent = ptr_new(0)
      state.pOld    = ptr_new(0)
      state.ptcycle = ptr_new(0)
      state.puv     = ptr_new(0)
      state.nRecent = 0
      state.nOld = 0
      state.pnrpt    = ptr_new(0)
      state.pbigdata = ptr_new(0)
      state.ppol     = ptr_new(0)
      state.pindx    = ptr_new(0)
      state.plmonID  = 0L
   endif else begin
      state.pGParm  = ptr_new(gparm)
      state.pGeometry = ptr_new(geometry)
      state.pRecent = pRecent
      state.pOld    = pOld
      state.nRecent = n_elements((*pRecent)(0,0,0,*))
      state.nOld    = n_elements((*pOld)(0,0,0,*))
      state.ptcycle = ptr_new(lonarr(state.nRecent))
                                            ;   to each sample in one cycle
      ; Replace current array pointers in the STATE structure
      ; with new ones from the CYCLE structure, first
      ; freeing the memory associated with the original pointers
      state.ppol     = cycle.ppol
      state.pbigdata = cycle.pbigdata
      state.pnrpt    = cycle.pnrpt
      state.pindx    = cycle.pindx

      state.nent = header.nws*obseq.nx ; Total number of entries in one cycle

      ; List of indexes where noise diode is off (OFFIDX)
      ; as obtained from observing sequence (OBSEQ structure)
      offidx = where(*obseq.pnd EQ 2)
      nfp = n_elements(offidx)   ; Number of frequencies/polarizations

      ; Activate the phase lock monitor, after destroying the existing
      ; one.
      nf = n_elements(*obseq.phord)
      plock = intarr(nf)
      flist = (*obseq.phord)*200
      WIDGET_CONTROL,state.plmonID,SET_UVALUE=flist
      WIDGET_CONTROL,state.plmonID,SET_VALUE=plock

      ; Read total power factors valid for this list of frequencies
;       tpfac = read_tpfac(flist)
;       ptr_free,state.ptpfac
;       state.ptpfac = ptr_new(tpfac)

      nant = header.nant
      state.puv     = ptr_new(fltarr(nant,nant,state.nRecent))
      ; Set up droplist widgets according to dimensions in data
      dim1 = nant + nant*(nant-1)/2
      dim2 = n_elements(*obseq.phord)
      dim3 = n_elements((*pRecent)(0,0,*,0))

      ; Ensure that the default indexes for the 4 plot windows is within range
      ; of the actual data
      fmin = min(abs(*obseq.phord - 25),imin)  ; Closest H to 25
      state.wIndex(*,0) = [0,nant,nant+2,2*nant+1]<(dim1-1)
      state.wIndex(*,1) = imin                 ; Index of closest H to 25
      state.wIndex(*,2) = [2,0,2,2]<(dim3-1)

      dim1label = replicate('           ',36)
      antlist = [1,2,4,5,6,7,8]
      aatab = header.aatab
      iants = intarr(nant)
      for i = 0, nant-1 do begin
         iants[i] = where(antlist eq aatab[i])
      endfor

      lchoff = header.lchoff
      nndw = 1 + header.ndbw + header.nfgw
      for j = 0, nant-1 do begin
         for i = 0, j do begin
            k = lchoff(iants[i],iants[j]) - nndw
            if (i eq j) then begin
               ; Case of total power channel
               dim1label(k) = 'TP Ant '+string(aatab(i),format='(i1)')+'   '
            endif else begin
               ; Case of correlated data
               if (k gt (nant-1)) then k = nant + (k-nant)/2
               str = string(aatab(i),aatab(j),format='(i1,"-",i1)')
               dim1label(k) = 'Amp/Pha '+str
            endelse
         endfor
      endfor

      ; Dimension 2 labels (for buttons and plot labelling) and parameters
      ; Parameters are frequency, in GHz
      dim2parm = *obseq.phord*0.2
      dim2label = strarr(86)
      dim2label(0:nf-1) = string(dim2parm,format='(F4.1," GHz")')
      dim2label(nf:*) = '        '

      dim3label = [' ',' ',' ',' ']
      CASE dim3 OF
         1: dim3label[0] = 'I'
         2: dim3label[0:1] = ['R','L']
         3: dim3label[0:2] = ['R','L','I']
         4: dim3label = ['R','L','RL','LR']
      ENDCASE

      WIDGET_CONTROL,state.DList(0),SET_VALUE=dim1label(0:dim1-1)
      WIDGET_CONTROL,state.DList(1),SET_VALUE=dim2label(0:dim2-1)
      WIDGET_CONTROL,state.DList(2),SET_VALUE=dim3label(0:dim3-1)
      state.dim1label = dim1label
      state.dim2label = dim2label
      state.dim3label = dim3label
      state.navg = 1
      WIDGET_CONTROL,state.NAvgID,SET_DROPLIST_SELECT=0

      for i = 0, 3 do begin
         ant =  dim1label(state.wIndex(i,0))
         frq =  dim2label(state.wIndex(i,1))
         pstr = dim3label(state.wIndex(i,2))
         labstr = ant+'  '+frq+'  '+strmid(pstr,0,2)+' Poln'
         WIDGET_CONTROL,state.wlab(i),SET_VALUE=labstr
      endfor

   endelse

   ; Pointer into RECENT and OLD arrays, to next time entry to be filled with data
   state.RecentPtr = 0L
   state.OldPtr = 0L

   state.nnow = -1
   state.nval = 0

   ; Turn off math exception handling to eliminate annoying report of
   ; Floating illegal operand every time a NaN is processed.
   !EXCEPT = 0

   ; Number of samples before start of DOSEQ
   state.koff = 0

   ; Set dataflag to indicate that there are no data yet
   state.dataflag = 0
   state.rec = rec-1      ; Point to last record before data

END


PRO Solaid_Event, Event

  base = Event.handler
  stash = WIDGET_INFO(base, /CHILD)
  WIDGET_CONTROL, stash, GET_UVALUE=state, /NO_COPY

  WIDGET_CONTROL,Event.Id,GET_UVALUE=Ev

  ; If the event is one of the drop-list widgets,
  ; set the corresponding state.wIndex to the index of the
  ; selection and update the plot.
  FOR i = 0, 2 DO BEGIN
     IF (Event.Id EQ state.DList(i)) THEN BEGIN
        ; Droplist widgets return their index in the Event.Index
        ; field of the Event structure
        state.wIndex(state.wActive,i) = Event.Index
        Ev = 'UPDATE'       ; Set the Ev variable to update the plot
     ENDIF
  ENDFOR

  IF (Event.Id EQ state.mbar) THEN Ev=Event.Value

  IF (TAG_NAMES(Event, /STRUCTURE_NAME) EQ $
                            'WIDGET_KILL_REQUEST') THEN BEGIN
     IF (state.lun ne 0) THEN free_lun,state.lun
     ptr_free,state.pbigdata
     ptr_free,state.ppol
     ptr_free,state.pindx
     ptr_free,state.pnrpt
     ptr_free,state.pRecent
     ptr_free,state.pOld
     ptr_free,state.ptcycle
     ptr_free,state.puv
     WIDGET_CONTROL, Event.top, /DESTROY
     RETURN
  ENDIF

  CASE Ev OF
  'Open': BEGIN
         filename = pickfile(path=!defaults.datadir,filter='*.arc')
         lun = openarc(filename,a,nrec)
         if (lun eq 0) then begin
            if (state.lun ne 0) then free_lun,state.lun
            WIDGET_CONTROL,Event.Top,/DESTROY
            return
         endif
         seglist = get_segments(a,nrec)

         ; Get indexes of header lines (IHED)
         nadded = findsegentries(seglist,ihed,iseg)
         nhed = n_elements(ihed)
         if (nhed ne 0) then begin
            choice = xchoice(['Select the SCAN to process, or CANCEL',$
             '[typically must be a SOLAR DATA or PCAL scan]'],$
             [seglist[ihed],'Cancel'])
            if (choice ne nhed) then begin
               ; User did not select the cancel button, so proceed
               reads,(seglist[ihed])[choice],format='(13x,i5,1x,i5)',recn,erec
               state.hrec = recn
               state.rec  = recn
               state.nrec = nrec
               state.firstcall = 1
               free_lun,state.lun
               state.lun = lun
               ptr_free,state.filptr
               state.filptr = ptr_new(a)
               state.realtime = 0  ; Indicate we are no longer reading realtime file
               changescan,state
            endif
         endif
      END
  'Realtime': BEGIN

         filename=!defaults.workdir+'DAILY.ARC'

         ; Open the file
         lun = openarc(filename,a,nrec)
         if (lun eq 0) then begin
            WIDGET_CONTROL,Event.Top,/DESTROY
            return
         endif

         ; Check whether there are any data in the file
         lastrec = getlrecn(a,nrec)

         ; Get the last header record in the file, if any
         hrec = lasthrec(a,nrec)

         if (hrec eq -1) then begin
            ; There was something wrong with the header
            ans = WIDGET_MESSAGE('Invalid header record, or no headers found.',/error)
            free_lun,lun
            WIDGET_CONTROL, Event.Top, /DESTROY
            return
         endif
         state.hrec = hrec
         state.rec  = hrec
         state.nrec = nrec
         state.firstcall = 1
         if (state.lun ne 0) then free_lun,state.lun
         state.lun = lun
         ptr_free,state.filptr
         state.filptr = ptr_new(a)
         state.realtime = 1  ; Indicate we are now reading realtime file
         changescan,state
         state.rec = lastrec
      END
  'Quit': BEGIN
         IF (state.lun ne 0) THEN free_lun,state.lun
         WIDGET_CONTROL, Event.top, /DESTROY
         RETURN
      END
  'Snapshot': BEGIN
         xpad = 20 & ypad = 24  ; Number of pixels between plots
         frame = 1 ; Number of pixels between plots and frame box around plots
         wset,state.specid
         spec = tvrd(/true)
         wset,state.wid(0)
         win = tvrd(/true)

         ant  = state.dim1label[state.wIndex[state.wActive,0]]
         frq  = state.dim2label[state.wIndex[state.wActive,1]]
         pstr = state.dim3label[state.wIndex[state.wActive,2]]

         ; Determine size of plot windows, create a background, and
         ; put in text labels for the plots.
         nxspec = n_elements(spec[0,*,0])
         nyspec = n_elements(spec[0,0,*])
         nxplot = n_elements( win[0,*,0])
         nyplot = n_elements( win[0,0,*])
         xsiz = nxspec + 2*xpad
         ysiz = nyspec + 2*nyplot + 4*ypad
         dsav = !d.name

         ; Open a z-buffer in which to create the text labels
         set_plot,'z'
         device,set_res=[xsiz,ysiz]
         erase,ovsa_get_color(200)
         plots,[0,0,xsiz-1,xsiz-1,0],[0,ysiz-1,ysiz-1,0,0],/dev,color=0

         ; Create the label for the dynamic spectrum
         title = 'Dynamic Spectrum for '+state.srcname+ant+'  '+strmid(pstr,0,2)+' Poln'
         xyouts,/dev,xsiz/2,ysiz-ypad+4,title,align=0.5,font=1,color=0,charsize=1.5

         ; Draw box around spectrum
         x0 = xpad-frame
         x1 = x0+nxspec+2*frame
         y0 = ysiz-2
         y1 = ysiz-ypad-nyspec-frame
         plots,[x0,x1,x1,x0,x0],[y0,y0,y1,y1,y0],/dev,color=0

         ; Create labels for each plot in turn, and write them into the z-buffer
         for iwin = 0, 3 do begin
            ant  = state.dim1label[state.wIndex[iwin,0]]
            frq  = state.dim2label[state.wIndex[iwin,1]]
            pstr = state.dim3label[state.wIndex[iwin,2]]
            title = ant+'  '+frq+'  '+strmid(pstr,0,2)+' Poln'
            if (iwin eq state.wActive) then title = '*** '+title+' ***'
            x = (iwin mod 2)*(xpad + nxplot)
            y = (iwin/2)*(ypad + nyplot)
            xyouts,/dev,xpad+nxplot/2 + x,ysiz-2*ypad-y-nyspec+4,$
                 title,align=0.5,font=1,color=0,charsize=1.4

            ; Draw box around plots
            x0 = xpad-frame + x
            x1 = x0+nxplot+2*frame
            y0 = ysiz - nyspec - ypad - 4 - y
            y1 = y0 - nyplot - frame - ypad+4
            plots,[x0,x1,x1,x0,x0],[y0,y0,y1,y1,y0],/dev,color=0
         endfor

         ; Read the z-buffer, then go back to original device
         big = tvrd()
         set_plot,dsav

         ; The z-buffer is a byte device, so create a true-color image
         ; from the one read from the z-buffer
         bkgnd = bytarr(3,xsiz,ysiz)
         for i = 0, 2 do begin
            bkgnd(i,*,*) = big
         endfor

         ; Put the dynamic spectrum into the true-color image
         bkgnd(*,xpad:xpad+nxspec-1,ysiz-ypad-nyspec:ysiz-ypad-1) = spec

         ; Put the plot images into the true-color image
         for iwin = 0, 3 do begin
            wset,state.wid(iwin)
            win = tvrd(/true)
            x = (iwin mod 2)*(xpad + nxplot)
            y = (iwin/2)*(ypad + nyplot)
            bkgnd(*,xpad+x+1:xpad+x+nxplot,ysiz-(2*ypad+nyspec+y+nyplot):ysiz-(2*ypad+nyspec+y+1)) = win
         endfor

         tls = state.header.tls

         ; Gather info for filename
         dsrc = state.dim1label[state.wIndex[state.wActive,0]]
         pol  = state.dim3label[state.wIndex[state.wActive,2]]
         if (strmid(dsrc,0,2) eq 'TP') then begin
            src = 'tpa'+strmid(dsrc,7,1)+pol
         endif else begin
            src = 'ap'+strmid(dsrc,8,1)+strmid(dsrc,10,1)+pol
         endelse
         src = strlowcase(src)

         ; Generate a descriptive filename
         filename = !defaults.webdir+'ovro_'+src+'_sp_'+strmid(tls.date,  0,4)$
                                 +strmid(tls.date,  5,2)+strmid(tls.date,  8,2) $
                             +'_'+strmid(tls.timstr,1,2)+strmid(tls.timstr,4,2)+'.gif'
         ; Convert 24-bit image to 8-bit image
         img = color_quan(bkgnd,1,r,g,b,colors=256)

         ; Write it to disk
         write_gif,filename,img,r,g,b
      END
  'Preferences': BEGIN
         print,'Preferences not enabled.'
      END
  'ZButton': BEGIN
         state.ForceZero = 1 - state.ForceZero
         goto, update
      END
  'LogButton': BEGIN
         state.LogScl = 1 - state.LogScl
         goto, update
      END
  'UpDFlag': BEGIN
         ; Ignore Update button if state.header_ready = -1
         if (state.header_ready ne -1) then begin
           state.Update_Off = 1 - state.Update_Off
           ; If UpDFlag was turned on, start timer
           if (state.Update_Off ne 1) then begin
             if (state.header_ready) then begin
               if (state.lun eq 0) then begin
                  ; Open the file if it is not already open
                  lun = openarc(state.filename,a,nrec)
                  state.filptr = ptr_new(a)
                  state.lun = lun
               endif
               state.rec = state.rec+1
               ChangeScan,state
               state.firstcall = 1
               state.header_ready = 0
             endif
             WIDGET_CONTROL, state.TimerID, TIMER=2
           endif
         endif
      END
  'TPButton': BEGIN
         state.TPcal = 1 - state.TPcal
      END
  'SubSet': BEGIN
         ; Button for Set Background
         state.BgndSet = 1 - state.BgndSet
;         if (state.BgndSet eq 0) then begin
;            WIDGET_CONTROL,state.SubApply,SET_BUTTON=0,SENSITIVE=0
;         endif
         device,cursor_standard=32516
      END
  'SubApply': BEGIN
         ; Button for Apply Background Subtraction
         state.BgndApply = 1 - state.BgndApply
      END
  'LCMode': BEGIN
         ; Button for Set Lightcurve Mode
         state.LCMode = 1
         state.SPMode = 0
      END
  'SPMode': BEGIN
         ; Button for Set Spectral Mode
         state.LCMode = 0
         state.SPMode = 1
      END
  'NoTrack': BEGIN
         ; Button for NOTRACK Mode
         state.TrkMode = 1 - state.TrkMode
      END
  'Draw0' : BEGIN
             state.wActive = 0
             goto, update
            END
  'Draw1' : BEGIN
             state.wActive = 1
             goto, update
            END
  'Draw2' : BEGIN
             state.wActive = 2
             goto, update
            END
  'Draw3' : BEGIN
             state.wActive = 3
             goto, update
            END
  'UPDATE': BEGIN
update:
          ntimes = state.RecentPtr-1
          nant = state.header.nant

          ; Get the date string from the Date label
          WIDGET_CONTROL,state.DateID,GET_VALUE=datstr
          yr = strmid(datstr,6,4)
          day = strmid(datstr,11,3)
          out = cvdoy(yr,day)
          datstr = string(out,format='(I4,"/",I2.2,"/",I2.2)')

          ; Determine number of frequencies from *state.pRecent
          nf = n_elements((*state.pRecent)(0,*,0,0))

          ; Update the spectral plot
          wsav = !d.window
          wset,state.pixid[4]
          xsiz = !d.x_size
          ysiz = !d.y_size
          if (ntimes gt 1) then begin

             ; Check if this is the first dynamic spectrum, in which case
             ; we need to clear the "No Data Yet" text.
             if (state.dataflag eq 0) then begin
                state.dataflag = 1
                erase,ovsa_get_color(255)
             endif
             ; Display the dynamic spectrum
             i = state.wIndex(state.wActive,0)
             j = state.wIndex(state.wActive,2)
             if (i le nant-1) then begin
                ; Total power
                if (state.BgndApply) then begin
                   bgnd = *state.pbgnd
                   spec = fltarr(nf,ntimes)
                   for k = 0, ntimes-1 do begin
                      spec(*,k) = (*state.pRecent)(i,*,j,k) - bgnd(i,*,j)
                   endfor
                endif else begin
                   spec = reform((*state.pRecent)(i,*,j,0:ntimes-1),nf,ntimes)
                endelse
                if (size(spec,/n_dim) eq 1) then spec = spec#replicate(1,20)
                if (state.TPcal eq 1) then begin
                   tpfac = (reform((*state.refcal.pfactors)[j,i,*]*(*state.refcal.pfacupd)[j,i,*]))#(fltarr(ntimes)+1.0)
                   tpoff = (reform((*state.refcal.poffsun)(j,i,*)))#(fltarr(ntimes)+1.0)
                   if (state.BgndApply) then spec = spec/tpfac $
                                        else spec = (spec-tpoff)/tpfac
                endif
             endif else begin
                ; Converts Sin channel selection to Amplitude
                sinchn = (i-nant)*2 + nant
                coschn = sinchn+1
                if (state.BgndApply) then begin
                   bgnd = *state.pbgnd
                   spec1 = fltarr(nf,ntimes)
                   spec2 = fltarr(nf,ntimes)
                   for k = 0, ntimes-1 do begin
                      spec1(*,k) = (*state.pRecent)(sinchn,*,j,k) - bgnd(sinchn,*,j)
                      spec2(*,k) = (*state.pRecent)(coschn,*,j,k) - bgnd(coschn,*,j)
                   endfor
                endif else begin
                   Spec1 = reform((*state.pRecent)(sinchn,*,j,0:ntimes-1),nf,ntimes)
                   Spec2 = reform((*state.pRecent)(coschn,*,j,0:ntimes-1),nf,ntimes)
                endelse
                spec = sqrt(Spec1^2+Spec2^2)
                if (state.TPcal eq 1) then begin
                   ampfac = reform((*state.refcal.pfactors)[j,i,*])#(fltarr(ntimes)+1.0)
                   spec = spec/ampfac
                endif
             endelse
             minsiz = ceil(ntimes*(xsiz*0.9/state.nRecent))
             good = where(finite(spec),ngd)
             if(ngd gt 10) then begin
                smax = max(spec(good))
                smin = min(spec(good))
                srange = smax-smin
                ; If not total power, rescale to more common max value
                if (i ge nant) then begin
                   ; 100-element histogram, binsize SRANGE/100.
                   hst = histogram(spec[good],min=smin,max=smax,binsiz=srange/100)
                   pk = max(hst,ipk)  ; IPK is what we want here
                   ; Rescale to peak value that occurs more than twice in a bin
                   s_max = (n_elements(where(hst[ipk:*] gt 2))+ipk)*srange/100. + smin
                   if (s_max gt 0) then begin
                      smax = s_max
                      srange = smax - smin
                   endif
                endif
                widget_control,state.maxp,GET_VALUE=maxp
                widget_control,state.minp,GET_VALUE=minp
                smax = smin + srange*maxp/100.
                smin = smin + srange*minp/100.
                if (state.logscl eq 1) then begin
                   smin = alog10(smin>0.001)
                   smax = alog10(smax>0.001)
                   spec = alog10(spec>0.001)
                endif
                ; Write the spectrum image to the screen
                tv,bytscl(max=smax,min=smin,congrid(rotate(spec,-5),$
                     xsiz*0.9<minsiz,(ysiz-30)*0.9)),xsiz*0.05,ysiz*0.05+30
                flist = (*state.obseq.phord)*0.200
                time = (*state.ptcycle)(0:ntimes-1)/1000
                if (n_elements(time) gt 3) then begin
                   yran = [flist(n_elements(flist)-1),flist(0)]
                   xran = [time(0),time(0)+3600.]
                   ; Draw the plot box around the image
                   utplot_io,/noerase,/nodata,float(time),flist,xran=xran,$
                             yran=yran,xsty=1,ysty=1,pos=[0.049,0.24,0.949,0.96],$
                             color=ovsa_get_color(0),datstr
                endif
             endif
          endif else begin
             erase,ovsa_get_color(255)
             xyouts,0.5*xsiz,0.5*ysiz,/device,align=0.5,'No data yet',color=0
             state.dataflag = 0
          endelse
          wset,state.specid
          device,copy=[0,0,!d.x_size,!d.y_size,0,0,state.pixid[4]]
          wset,wsav
          if (ntimes gt 0) then begin
             iwin0 = (iwin1 = state.wActive)
             if (state.new eq 1) then begin
                iwin0 = 0
                iwin1 = 3
                state.new = 0
             endif
             ; Loop over the 4 plot windows, or just the active window, depending on state.new
             for iwin = iwin0, iwin1 do begin
                i = state.wIndex(iwin,0)    ; Data channel
                j = state.wIndex(iwin,1)    ; Frequency
                k = state.wIndex(iwin,2)    ; Polarization
                k2 = k
                if (n_elements((*state.precent)[0,0,*,0]) eq 1) then k2 = 2
                wset,state.pixid(iwin)
                m = state.tselect(iwin)
                if (m eq 0) then m = state.RecentPtr-1

                bgnd = *state.pbgnd
             yran = [0,1]
             if (i lt nant) then begin
                ; Total power, so only need "amplitude" array
                phzarr = 0.
                if (state.LCMode) then begin
                   if (state.BgndApply) then begin
                      amparr = fltarr(ntimes)
                      for nt = 0, ntimes-1 do begin
                         amparr(nt) = (*state.pRecent)(i,j,k,nt) - bgnd(i,j,k)
                      endfor
                   endif else begin
                      amparr = reform((*state.pRecent)(i,j,k,0:ntimes-1))
                   endelse
                   if (state.tpcal eq 1) then begin
                      if (state.BgndApply) then amparr = amparr/((*state.refcal.pfactors)[k2,i,j]*(*state.refcal.pfacupd)[k2,i,j]) $
                                           else amparr = (amparr-(*state.refcal.poffsun)[k2,i,j])/((*state.refcal.pfactors)[k2,i,j]*(*state.refcal.pfacupd)[k2,i,j])
                   endif
                endif else if (state.SPMode) then begin
                   if (state.BgndApply) then begin
                      amparr = reform((*state.pRecent)(i,*,k,m) - bgnd(i,*,k))
                   endif else begin
                      amparr = reform((*state.pRecent)(i,*,k,m))
                   endelse
                   if (state.tpcal eq 1) then begin
                      if (state.BgndApply) then amparr = amparr/((*state.refcal.pfactors)[k2,i,*]*(*state.refcal.pfacupd)[k2,i,*]) $
                                           else amparr = (amparr-(*state.refcal.poffsun)[k2,i,*])/((*state.refcal.pfactors)[k2,i,*]*(*state.refcal.pfacupd)[k2,i,*])
                   endif
                endif
             endif else begin
                ; Correlation channels, so obtain amplitude and phase arrays
                sinchn = (i-nant)*2 + nant
                coschn = sinchn+1
                if (state.LCMode) then begin
                   if (state.BgndApply) then begin
                      amparr = (phzarr = fltarr(ntimes))
                      for nt = 0, ntimes-1 do begin
                         amparr(nt) = sqrt($
                              ((*state.pRecent)[sinchn,j,k,nt]-bgnd[sinchn,j,k])^2 $
                             +((*state.pRecent)[coschn,j,k,nt]-bgnd[coschn,j,k])^2)
                         phzarr(nt) = (atan($
                               ((*state.pRecent)[sinchn,j,k,nt]-bgnd[sinchn,j,k]),$
                               ((*state.pRecent)[coschn,j,k,nt]-bgnd[coschn,j,k]))$
                               /!dtor + 360) mod 360.
                      endfor
                   endif else begin
                      amparr = sqrt((*state.pRecent)(sinchn,j,k,0:ntimes-1)^2 $
                             +(*state.pRecent)(coschn,j,k,0:ntimes-1)^2)
                      phzarr = (atan((*state.pRecent)(sinchn,j,k,0:ntimes-1),$
                               (*state.pRecent)(coschn,j,k,0:ntimes-1))$
                               /!dtor + 360) mod 360.
                   endelse
                   amparr = reform(amparr)
                   phzarr = reform(phzarr)
                   if (state.tpcal eq 1) then begin
                      time = (*state.ptcycle)(0:ntimes-1)
                      freq = (*state.obseq.phord)[j]*0.2
                      apply_cal,amparr,phzarr,time,freq,k2,i,state.refcal
                     ; amparr = amparr/(*state.refcal.pfactors)[k2,i,j]
                     ; phzarr = (phzarr - (*state.refcal.pphase)[k2,i-nant,j] + 360.) mod 360.
                   endif
                endif else if (state.SPMode) then begin
                   if (state.BgndApply) then begin
                      amparr = (phzarr = fltarr(nf))
                      ;for ifrq = 0, nf-1 do begin
                      amparr = sqrt($
                              (reform((*state.pRecent)[sinchn,*,k,m])-reform(bgnd[sinchn,*,k]))^2 $
                             +(reform((*state.pRecent)[coschn,*,k,m])-reform(bgnd[coschn,*,k]))^2)
                      phaseref = atan(bgnd[sinchn,*,k],bgnd[coschn,*,k])/!dtor
                      phzarr = (reform(atan((*state.pRecent)(sinchn,*,k,m),$
                               (*state.pRecent)(coschn,*,k,m))$
                               /!dtor) - phaseref + 360) mod 360.
;                         phzarr(ifrq) = (atan($
  ;                             ((*state.pRecent)[sinchn,ifrq,k,m]-bgnd[sinchn,ifrq,k]),$
    ;                           ((*state.pRecent)[coschn,ifrq,k,m]-bgnd[coschn,ifrq,k]))$
     ;                          /!dtor + 360) mod 360.
                      ;endfor
                   endif else begin
                      amparr = sqrt((*state.pRecent)(sinchn,*,k,m)^2 $
                             +(*state.pRecent)(coschn,*,k,m)^2)
                      phzarr = (atan((*state.pRecent)(sinchn,*,k,m),$
                               (*state.pRecent)(coschn,*,k,m))$
                               /!dtor + 360) mod 360.
                   endelse
                   amparr = reform(amparr)
                   phzarr = reform(phzarr)
                   if (state.tpcal eq 1) then begin
                      time = (*state.ptcycle)[m]
                      freq = (*state.obseq.phord)*0.2
                      apply_cal,amparr,phzarr,time,freq,k2,i,state.refcal
                     ; amparr = amparr/reform((*state.refcal.pfactors)[k2,i,*])
                     ; phzarr = (phzarr - reform((*state.refcal.pphase)[k2,i-nant,*]) + 360.) mod 360.
                   endif
                endif
             endelse

             ; Scale the plot according to the finite values in AMPARR
             phzran = [0.,360.]
             farray = where(finite(amparr))
             sizn = size(farray)

             IF (sizn(0) eq 0 or sizn(1) lt 2) THEN BEGIN
                ; Too few points to plot
                plot,[0,1],[0,1],/nodata,background=ovsa_get_color(255),color=0
                xyouts,.5,.5,align=0.5,'No Good Data',color=0
             ENDIF ELSE BEGIN
                goodvals = amparr(farray)
                if (state.forcezero eq 0) then ampran = [min(goodvals),max(goodvals)] $
                                          else ampran = [0,max(goodvals)]
                xsiz = !d.x_size
                ysiz = !d.y_size
                tpos = [50,ysiz*0.15,xsiz,ysiz]
                apos = [50,ysiz*0.40,xsiz,ysiz]
                ppos = [50,ysiz*0.15,xsiz,ysiz*0.4]

                if (state.LCMode) then begin
                   time = (*state.ptcycle)(0:ntimes-1)/1000
                   result = dotimeavg(state.navg,time,amparr,phzarr)
                   ; Force  at least 10 min timerange
                   tran = float([time(0), time(n_elements(time)-1)>(time(0)+600) ])
                   if (i lt nant) then begin
                      UTPLOT,float(time),amparr,yran=ampran,xran=tran,datstr,xsty=1,ysty=1,$
                       ytit='Total Power',pos=tpos,/device,background=ovsa_get_color(255),color=0,psym=10
                   endif else begin
                      UTPLOT,float(time),amparr,yran=ampran,xran=tran,datstr,xsty=5,ysty=1,$
                       ytit='Amplitude',pos=apos,/device,background=ovsa_get_color(255),color=0,psym=10
                      UTPLOT,float(time),phzarr,yran=phzran,xran=tran,datstr,xsty=1,ysty=1,$
                       ytit='Phase',pos=ppos,/device,color=0,/noerase,psym=6,symsiz=0.3
                   endelse
                endif else if (state.SPMode) then begin
                   good = where(strmid(state.dim2label,7,1) eq 'z',n)
                   if (n gt 0) then dim2label = state.dim2label(good)
                   f = fltarr(n)
                   for ifrq = 0, n-1 do begin
                      reads,dim2label(ifrq),frq,format='(f4.1,4x)'
                      f(ifrq) = frq
                   endfor
                   fran = [1.0, 20.0]
                   if (i lt nant) then begin
                      plot,f,amparr,yran=ampran,xran=fran,xsty=1,ysty=1,$
                       ytit='Total Power',pos=tpos,/device,background=ovsa_get_color(255),color=0,psym=-3
                   endif else begin
                      PLOT,f,amparr,yran=ampran,xran=fran,xsty=5,ysty=1,$
                       ytit='Amplitude',pos=apos,/device,background=ovsa_get_color(255),color=0,psym=-3
                      PLOT,f,phzarr,yran=phzran,xran=fran,xsty=1,ysty=1,$
                       ytit='Phase',pos=ppos,/device,color=0,/noerase,psym=6,symsiz=0.3
                   endelse
                endif
             ENDELSE
                ant =  state.dim1label(state.wIndex(iwin,0))
                frq =  state.dim2label(state.wIndex(iwin,1))
                ; Parse the antenna string to find the antenna numbers within it.
                ; Two types of antenna string are, e.g., TP Ant 1 and Amp/Pha 1-2.
                ants = str_sep(ant,'-',/trim)
                n = n_elements(ants)
                if (n eq 2) then begin
                   ; This is a baseline (since two antennas are in it).  The first string
                   ; ants[0] has the antenna number at the end of it, and the second
                   ; string ants[1] is just the antenna number itself.
                   ants[0] = strmid(ants[0],strlen(ants[0])-1,1)  ; Pick off char at end of string
                   iant = where(state.header.aatab eq ants[0])          ; Convert ant numbers to indexes
                   jant = where(state.header.aatab eq ants[1])          ;   into the uv matrix
                   u = (*state.puv)[jant,iant,ntimes-1]*frq
                   v = (*state.puv)[iant,jant,ntimes-1]*frq
                   fs = ((1./(u^2+v^2))^0.5)*206265
                   asec = '"'
                   pa = fix(atan(v,u)/!dtor) & pa = pa[0]
                   if (pa gt 90) then pa = pa - 180 else if (pa le -90) then pa = pa + 180
                   fringes = string(fs,asec,pa,format='(1X,"[",F5.1,a1," at ",I3," deg]")')
                endif else fringes = ''
                tstr = strmid(msec2str((*state.ptcycle)[m]),0,9)
                pstr = state.dim3label(state.wIndex(iwin,2))
                if (state.LCMode) then begin
                   labstr = ant+'  '+frq+'  '+strmid(pstr,0,2)+' Poln'+fringes
                endif else begin
                   labstr = ant+'  '+tstr+'  '+strmid(pstr,0,2)+' Poln'
                endelse
                if (iwin eq state.wActive) then begin
                   labstr = '*** '+labstr+' ***'
                   WIDGET_CONTROL,state.wlab(iwin),SET_VALUE=labstr
                   WIDGET_CONTROL,state.DList(0),SET_DROPLIST_SELECT=state.wIndex(iwin,0)
                   WIDGET_CONTROL,state.DList(1),SET_DROPLIST_SELECT=state.wIndex(iwin,1)
                   WIDGET_CONTROL,state.DList(2),SET_DROPLIST_SELECT=state.wIndex(iwin,2)
                endif
                wset,state.wid[iwin]
                device,copy=[0,0,!d.x_size,!d.y_size,0,0,state.pixid[iwin]]
             endfor
          endif
       END
  'TIMER': BEGIN

         ; Ignore TIMER if UPDATE_OFF flag is set
         if (state.update_off) then GOTO, WRITE_STATE

         ; Check to see if there is a cycle waiting
         rec     = state.rec  ; Current record pointer
         header  = state.header
         obseq   = state.obseq
         cfg     = state.cfg
         koff    = 0
         nws     = header.nws
         nsr     = header.nsr
         nent    = state.nent
         nnow    = state.nnow
         nf      = n_elements(*obseq.phord)
         nrpt    = *state.pnrpt
         bigdata = *state.pbigdata
         pol     = *state.ppol
         a       = *state.filptr
         onidx = where(*obseq.pnd eq 1,non)
         nant = header.nant
         if (not state.firstcall) then data    = *state.pdata

         ; RecentPtr points to the *next* sample.  Get the cycle time of
         ; the previous sample or set to zero
         if (state.RecentPtr gt 0) then begin
            tcycle  = (*state.ptcycle)(state.RecentPtr-1)
         endif else tcycle = 0

         ; Get next (or first) cycle of data
         cycle = get_cycle(a,data,rec,header,obseq,cfg,tcycle)

         n = n_elements(cycle)
         if (n eq 1) then goto,restor  ; No new cycle available yet

         cycles_read = 0
         while(n_elements(cycle) eq n) do begin

            cycles_read = cycles_read + 1
            ; Cycle is full, so save data
            bigdata(0) = cycle    ; Convert CYCLE to channelized floating array
            state.new = 1         ; Indicates that a new cycle should be plotted

            if (state.trkmode eq 0) then flagool,bigdata,header,obseq else flagool,bigdata,header,obseq,/notrack   ; Flag data for out of lock

            ; Create plock array, which gives OOL indication as a function
            ; of frequency, regardless of polarization
            pzlock = bigdata(0,*)
            plock = intarr(nf)
            for i = 0, nf-1 do begin
               for j = 0, nrpt(i)-1 do begin
                  plock(i) = plock(i) or pzlock((*state.pindx)(j,i))
               endfor
            endfor

            ; Update realtime phase lock monitor
            WIDGET_CONTROL,state.plmonID,SET_VALUE=plock

            ; Correct for complex gain and attenuation
            smldata = gaincor(bigdata,pol,header,cfg,obseq,tcycle,$
                           *state.pGParm,*state.pGeometry,uv)

            ; Perform AGC, if there are any ND ON data
            if (non ne 0) then begin
               nddata = gaincor(bigdata,pol,header,cfg,obseq,tcycle,$
                           *state.pGParm,*state.pGeometry,uv,/ndon)
               indx = (*obseq.pharm)[onidx]
               blah = indx
               for i = 0,non-1 do blah[i] = where(*obseq.phord eq indx[i])
               blah = blah[sort(blah)]
               ndinc = reform(nddata[0:nant-1,*,*]) - reform(smldata[0:nant-1,blah,*])
               ndinc[where(finite(ndinc) eq 0)] = 0
               ndinc = total(ndinc,3)
               ndincgm = 10^(total(alog10(ndinc),2)/nant)
               ;print,smldata[1,15,2],ndincgm[1]
               ;wset,0
               ;plots,state.counter,smldata[1,15,2]/40,/dev,psym=1
               ;plots,state.counter,smldata[1,15,2]*500./30./ndincgm[1],/dev,psym=3
               state.counter=state.counter+1
            endif

            if (header.tls.yrday lt 2004.231) then begin
               ; For I-only data, reduce smldata array to only 1 poln
               if (n_elements((*state.pRecent)(0,0,*,0)) eq 1) then begin
                  ; See which ant 1 poln is non-Nan
                  i = where(finite(smldata(0,0,*)),nfinite)
                  if (nfinite ne 1) then i = 2

                  smldata = smldata(*,*,i)
               endif
            endif

            ; Save new cycle in RECENT array
            if (state.RecentPtr lt state.nRecent) then begin
               (*state.pRecent)(*,*,*,state.RecentPtr) = smldata
               (*state.ptcycle)(state.RecentPtr) = tcycle
               (*state.puv)(*,*,state.RecentPtr) = uv
               state.RecentPtr = state.RecentPtr+1
            endif else begin
               ; RecentPtr = nRecent, indicating that the RECENT
               ; array is full, so shift the whole thing left by 10 minutes
               nshift = state.nRecent/6
               *state.pRecent = shift((*state.pRecent),0,0,0,-nshift)
               *state.ptcycle = shift((*state.ptcycle),-nshift)
               *state.puv     = shift((*state.puv),0,0,-nshift)
               state.RecentPtr = state.RecentPtr-nshift
               (*state.pRecent)(*,*,*,state.RecentPtr) = smldata
               (*state.ptcycle)(state.RecentPtr) = tcycle
               (*state.puv)(*,*,state.RecentPtr) = uv
               state.RecentPtr = state.RecentPtr+1
               ; Erase Spectral plot window
               wsav = !d.window
               wset,state.pixid[4]
               erase,ovsa_get_color(255)
               wset,state.specid
               device,copy=[0,0,!d.x_size,!d.y_size,616,150,state.pixid[4]]
               wset,wsav
               goto, restor
            endelse
            if (cycles_read ge 50) then goto, restor
            cycle = get_cycle(a,data,rec,header,obseq,cfg,tcycle)
         endwhile

restor:
         if (n_elements(data) ne 0) then begin
            ptr_free,state.pdata
            state.pdata = ptr_new(data)
            state.firstcall = 0
         endif
         state.rec = rec
         state.tcycle = tcycle

         ; The convoluted code below is due to the fact that GET_CYCLE() returns
         ; the segment code of the last record read (e.g. !SEGM.HEADER for a header),
         ; in which case we want to read the new header and start a new scan.  However,
         ; we cannot check if cycle = !SEGM.HEADER until we ensure that cycle is not
         ; an array, hence the need to check n_elements(cycle)
         if (n_elements(cycle) ne 1) then begin
            WIDGET_CONTROL, state.TimerID, TIMER=2
         endif else begin
            if (cycle ne !SEGM.HEADER) then begin
               ; If the record read is an EOF, then close the .ARC file
               if (cycle eq !SEGM.EOF) then begin
                  IF (state.lun ne 0) THEN free_lun,state.lun
                  state.lun = 0
                  ptr_free,state.filptr
                  state.filptr = ptr_new(0)
                  state.UpDate_Off = 1
                  WIDGET_CONTROL,state.updflg,SET_BUTTON=0
                  ; Set flag so that Update button is ignored.
                  state.header_ready = -1
               endif else begin
                  WIDGET_CONTROL, state.TimerID, TIMER=0.1
               endelse
            endif else begin
               ; We have read a HEADER so act accordingly

               ; If the scan just completed was a PCAL scan, run the daily calibration routine
               if (state.header.tls.scancode eq !SCAN.PCAL) then begin
                  ntimes = state.recentptr-1
;                  dailyphz = daily_cal(state.precent,state.obseq,state.refcal,(*state.ptcycle)[0:ntimes-1]/1000.)
               endif
               if (state.realtime) then begin
                  state.rec = state.rec+1
                  ChangeScan,state
                  state.firstcall = 1
                  WIDGET_CONTROL, state.TimerID, TIMER=2
               endif else begin
                  ; Ready for next scan, but turn off UPDATE and close file (to ensure
                  ; that file can be truncated if necessary).
                  state.UpDate_Off = 1
                  IF (state.lun ne 0) THEN free_lun, state.lun
                  state.lun = 0
                  ptr_free,state.filptr
                  state.filptr = ptr_new(0)
                  WIDGET_CONTROL,state.updflg,SET_BUTTON=0
                  ; Set flag so that next time Update button is selected the
                  ; next header is read.
                  state.header_ready = 1
               endelse
            endelse
         endelse

         ; If we got new data, update the plot
         if (state.new EQ 1) then begin
            ev = 'UPDATE'
            goto, UPDATE
         endif

         goto,WRITE_STATE
      END
   'MOUSEBUTTONS': BEGIN
         if (event.type eq 0) then begin
            wid = !d.window
            device,/cursor_crosshair
            wset,state.specid
            xsiz = !d.x_size
            ysiz = !d.y_size
            wset,wid
            sz = size(*state.pRecent)
            fselect = floor(sz(2) - (Event.Y-ysiz*0.05-30)$
                       *(sz(2)/((ysiz-30)*.9)))>0<(sz(2)-1)
            tselect = floor((Event.X-xsiz*0.05)$
                       *sz[4]/(xsiz*0.9))>0<(state.RecentPtr-1)
            if (state.BgndSet eq 1) then begin
               WIDGET_CONTROL,state.SubApply,SENSITIVE=1
               WIDGET_CONTROL,state.SubSet,SET_BUTTON=0
               state.BgndSet = 0
;               state.BgndApply = 0
               ptr_free,state.pbgnd
               state.pbgnd = ptr_new(get_bgnd(state.pRecent,tselect))
            end
            widget_control,state.dlist(1),set_droplist_select=fselect
            ev = 'UPDATE'
            state.wIndex(state.wActive,1) = fselect
            state.tselect(state.wActive) = tselect
            goto,update
         endif
      END
   'SAMP-AVG': BEGIN
         navg = [1,2,3,4,5,10]
         state.navg = navg(Event.index)
         ev = 'UPDATE'
         goto,update
      END
   ELSE:

  ENDCASE

WRITE_STATE:
  WIDGET_CONTROL, stash, SET_UVALUE=state, /NO_COPY

END

;--------------------------------------------------------------------------


PRO Solaid, filename, srec, erec, GROUP=Group

   IF N_ELEMENTS(Group) EQ 0 THEN GROUP=0

   junk   = { CW_PDMENU_S, flags:0, name:'' }

   defsysv,'!DEFAULTS',EXISTS=exists
   if not exists then defparms

   pshift = [0,0]

   wBase = WIDGET_BASE(GROUP_LEADER=Group, $
      COLUMN=1, $
      MAP=1, $
      MBAR=mbar, $
      /TLB_KILL_REQUEST_EVENTS,$
      TITLE='Solaid', $
      UVALUE='MainBase')

   ExtraBase = WIDGET_BASE(wBase, $
      ROW=1, $
      MAP=1, $
      TITLE='ExtraBase', $
      UVALUE='ExtraBase')

   MenuBase = WIDGET_BASE(wBase, $
      ROW=1, $
      MAP=1, $
      TITLE='MenuBase', $
      UVALUE='MenuBase')

   FileMenuDesc = [ $
      { CW_PDMENU_S,       3, 'File' }, $             ;        0
      { CW_PDMENU_S,       0, 'Open' }, $             ;        1
      { CW_PDMENU_S,       0, 'Realtime' }, $         ;        2
      { CW_PDMENU_S,       2, 'Quit' }  $             ;        3
   ]


   FileMenu = CW_PDMENU( mBar, FileMenuDesc, /RETURN_NAME, $
      UVALUE='FileMenu',/MBAR)

   OptionsMenuDesc = [ $
      { CW_PDMENU_S,       3, 'Options' }, $             ;       0
      { CW_PDMENU_S,       0, 'Snapshot' }, $            ;       1
      { CW_PDMENU_S,       2, 'Preferences' } $          ;       2
   ]


   FileMenu = CW_PDMENU( mBar, OptionsMenuDesc, /RETURN_NAME, $
      UVALUE='OptionsMenu',/MBAR)

   LeftBase  = WIDGET_BASE(ExtraBase, COLUMN=1, MAP=1, $
                         TITLE= 'LeftBase',UVALUE= 'LeftBase')
   RightBase = WIDGET_BASE(ExtraBase, COLUMN=1, MAP=1, $
                         TITLE='RightBase',UVALUE='RightBase')

   wSpec = WIDGET_DRAW(LeftBase, $
      XSIZE=616, YSIZE=150, /BUTTON_EVENTS, UVALUE='MOUSEBUTTONS',FRAME=1)
   LTBase = WIDGET_BASE(LeftBase,ROW=1, MAP=1)
   LBBase = WIDGET_BASE(LeftBase,ROW=1, MAP=1)
   LTLBase = WIDGET_BASE(LTBase,COLUMN=1, MAP=1,FRAME=1)
   LTRBase = WIDGET_BASE(LTBase,COLUMN=1, MAP=1,FRAME=1)
   LBLBase = WIDGET_BASE(LBBase,COLUMN=1, MAP=1,FRAME=1)
   LBRBase = WIDGET_BASE(LBBase,COLUMN=1, MAP=1,FRAME=1)

   wDraw = intarr(4)
   wid   = intarr(4)
   wlab = intarr(4)
   wlab(0) = WIDGET_LABEL(LTLBase,VALUE='Window 1',/DYNAMIC_RESIZE)
   wlab(1) = WIDGET_LABEL(LTRBase,VALUE='Window 2',/DYNAMIC_RESIZE)
   wlab(2) = WIDGET_LABEL(LBLBase,VALUE='Window 3',/DYNAMIC_RESIZE)
   wlab(3) = WIDGET_LABEL(LBRBase,VALUE='Window 4',/DYNAMIC_RESIZE)

   ; Sizes for plot draw widgets
   scrsize = get_screen_size()
   xsiz = 300
   ysiz = (scrsize[1]/4)<200
   wDraw(0) = WIDGET_DRAW(LTLBase, $
      XSIZE=xsiz, YSIZE=ysiz,FRAME=1,$
      UVALUE='Draw0',BUTTON_EVENTS=1)
   wDraw(1) = WIDGET_DRAW(LTRBase, $
      XSIZE=xsiz, YSIZE=ysiz,FRAME=1,$
      UVALUE='Draw1',BUTTON_EVENTS=1)
   wDraw(2) = WIDGET_DRAW(LBLBase, $
      XSIZE=xsiz, YSIZE=ysiz,FRAME=1,$
      UVALUE='Draw2',BUTTON_EVENTS=1)
   wDraw(3) = WIDGET_DRAW(LBRBase, $
      XSIZE=xsiz, YSIZE=ysiz,FRAME=1,$
      UVALUE='Draw3',BUTTON_EVENTS=1)

   ; Create pixmap windows corresponding to each of the draw widget windows
   ; so that window updates are smoother.
   pixid = intarr(5)
   for i = 0, 3 do begin
      window,/free,xsiz=xsiz,ysiz=ysiz,/pixmap
      pixid[i] = !d.window
   endfor
   window,/free,xsiz=616,ysiz=150,/pixmap
   pixid[4] = !d.window

   ; Array of default data indexes to plot windows, corresponding to:
   ; WIndow 1: TP Ant 1, 5 GHz, I Poln
   ; Window 2: AP 1-2,   5 GHz, R Poln
   ; Window 3: AP 1-6,   5 GHz, I Poln
   ; Window 4: AP 2-6,   5 GHz, I Poln
   wIndex = [[0,5,8,11], [18,18,18,18], [2,0,2,2]]
   ButtonBase = WIDGET_BASE(LeftBase, $
      ROW=1, MAP=1, $
      TITLE='ButtonBase', UVALUE='ButtonBase')

   ; Create empty droplists
   DSrcList = WIDGET_DROPLIST(ButtonBase,VALUE=['Empty'], /DYNAMIC_RESIZE,UVALUE='Data Source')
   FrqList  = WIDGET_DROPLIST(ButtonBase,VALUE=['Empty     '], UVALUE='Frequency')
   PolList  = WIDGET_DROPLIST(ButtonBase,VALUE=['Empty'], UVALUE='Polarization')

   ; "Pretty" labelling--these fonts are PC-only
   ; The first label is also the widget to which the Timer messages are attached.
   TimerID = WIDGET_LABEL(RightBase,VALUE='OVRO',FONT='Arial*Bold*50',UVALUE='TIMER')
   l2 = WIDGET_LABEL(RightBase,VALUE='Solar Array',FONT='Arial*Bold*25')
   DateID = WIDGET_LABEL(RightBase,VALUE='Date:',/DYNAMIC_RESIZE,FONT='Arial*Bold*14')
   NameID = WIDGET_LABEL(RightBase,VALUE='Name:',/DYNAMIC_RESIZE,FONT='Arial*Bold*14')

   NavgID = WIDGET_DROPLIST(RightBase,VALUE=['None','2 Samp', '3 Samp', $
        '4 Samp','5 Samp','10 Samp'],$
        TITLE='Sample Averaging',FRAME=1,UVALUE='SAMP-AVG')

   SubLabel = WIDGET_LABEL(RightBase,VALUE='Background Subtraction')
   SubBase = WIDGET_BASE(RightBase,/NONEXCLUSIVE,/ROW,/FRAME)
   SubSet = WIDGET_BUTTON(SubBase,VALUE='Set',UVALUE='SubSet')
   SubApply  = WIDGET_BUTTON(SubBase,VALUE='Apply',UVALUE='SubApply',SENSITIVE=0)
   zbase = WIDGET_BASE(RightBase,/NONEXCLUSIVE,/ROW)
   zeros = WIDGET_BUTTON(zBase,VALUE='Force Zero',UVALUE='ZButton')
   updflg = WIDGET_BUTTON(zBase,VALUE='Update',UVALUE='UpDFlag')
   trkflg = WIDGET_BUTTON(zBase,VALUE='NoTrack',UVALUE='NoTrack')
   LBase = WIDGET_BASE(RightBase,/NONEXCLUSIVE,/ROW)
   tpcal = WIDGET_BUTTON(LBase,VALUE='Apply Calib',UVALUE='TPButton')
   logbutton = WIDGET_BUTTON(LBase,VALUE='Log Scale',UVALUE='LogButton')
   maxbase = CW_FSLIDER(RightBase,min=0.,max=100.,title='Max Percent',VALUE=100.,UVALUE='MAXP',format='(f5.1)')
   minbase = CW_FSLIDER(RightBase,min=0.,max=100.,title='Min Percent',VALUE=0.  ,UVALUE='MINP',format='(f5.1)')
   ModeBase = WIDGET_BASE(RightBase,/EXCLUSIVE,/ROW,/FRAME)
   LCMode = WIDGET_BUTTON(ModeBase,VALUE='Lightcurves',UVALUE='LCMode')
   SPMode = WIDGET_BUTTON(ModeBase,VALUE='Spectra',UVALUE='SPMode')
   plmonID = CW_PLMonitor(RightBase)
   WIDGET_CONTROL,zeros,SET_BUTTON=1
   WIDGET_CONTROL,updflg,SET_BUTTON=1
   WIDGET_CONTROL,LCMode,SET_BUTTON=1
   WIDGET_CONTROL,tpcal,SET_BUTTON=0
   WIDGET_CONTROL, wBase, /REALIZE


   ; Use 256-color Pseudo-color
   device,decomposed=0

   ; Fix color table as RAINBOW, but with white as color 0 and black as color 255
   loadct,13
   tvlct,r,g,b,/get
   ;r(0)   = (g(0)   = (b(0)   =   0))
   ;r(255) = (g(255) = (b(255) = 255))
   ncol=!d.table_size
   r(0)   = (g(0)   = (b(0)   =   0))
   r(ncol-1) = (g(ncol-1) = (b(ncol-1) = 255))

   tvlct,r,g,b
   !p.background=ovsa_get_color(255)

   ; Clear all of the windows to white
   WIDGET_CONTROL, wSpec, GET_VALUE=specid
   for i = 0, 3 do begin
      WIDGET_CONTROL, wDraw(i), GET_VALUE=winid
      wid(i) = winid
      wset,wid(i)
      !p.background=ovsa_get_color(255)
      erase
      wset,pixid[i]
      !p.background=ovsa_get_color(255)
      erase
   endfor
   wset,specid
   !p.background=ovsa_get_color(255)
   erase
   wset,pixid[4]
   !p.background=ovsa_get_color(255)
   erase

   !p.background=0

   ;======================= Open and read datafile ======================

   ; If no filename, assume standard DAILY.ARC file
   IF (n_elements(filename) EQ 0) THEN BEGIN
      filename=!defaults.workdir+'DAILY.ARC'
      erec = 0
   ENDIF

   ; Open the file
   lun = openarc(filename,a,nrec)
   if (lun eq 0) then begin
      WIDGET_CONTROL,wBase,/DESTROY
      return
   endif

   ; Check whether there are any data in the file
   lastrec = getlrecn(a,nrec)

   ; Wait loop for the case of an empty file
   if (lastrec eq 0) then begin
      id = progmeter(/init,button='Abort',label='File empty.  Waiting 10 s for data')
      while (lastrec eq 0) do begin
         ; Do wait loop while waiting for data
         for i = 0, 9 do begin
            res = progmeter(id,(10-i)/10.)
            if (res eq 'Cancel') then begin
               ; User bailed out--clean up
               res = progmeter(id,/destroy)
               free_lun,lun
               WIDGET_CONTROL, wBase, /DESTROY
               return
            endif
            wait,1
         endfor
         ; Get the current last record
         lastrec = getlrecn(a,nrec)
      endwhile
      res = progmeter(id,/destroy)
   endif

   ; Get the last header record in the file, if any
   if (n_elements(srec) eq 0) then hrec = lasthrec(a,nrec) else hrec = srec

   if (hrec eq -1) then begin
      ; There was something wrong with the header
      ans = WIDGET_MESSAGE('Invalid header record, or no headers found.',/error)
      free_lun,lun
      WIDGET_CONTROL, wBase, /DESTROY
      return
   endif

   ; Run the procedure to read header information from the scan and
   ; declare pointers to data arrays
   rec = newscan(a,nrec,hrec,header,obseq,cfg,traj,refcal,gparm,geometry,pRecent,pOld,cycle,pshift=pshift)
   if (rec eq -1) then begin
      ; NEWSCAN returned an error
      ans = WIDGET_MESSAGE('Error in file structure or missing segment.',/error)
      free_lun,lun
      WIDGET_CONTROL, wBase, /DESTROY
      return
   endif

   ; If the EREC argument was passed in and is non-zero, this signifies that we
   ; want to read the file as a non-realtime file
   realtime = 1
   if (n_elements(erec) ne 0 and erec ne 0) then begin
      lastrec = rec-1
      realtime = 0
   endif
   srcname = traj.srcname

   tls = header.tls
   WIDGET_CONTROL,DateID,SET_VALUE='Date: '+tls.yrday+tls.timstr
   if (srcname ne '') then WIDGET_CONTROL,NameID,SET_VALUE='Name: '+srcname $
                      else WIDGET_CONTROL,NameID,SET_VALUE='Name: <Unknown>'

   dim1label = replicate('           ',36)
   dim2label = strarr(86)
   dim3label = [' ',' ',' ',' ']

   nent = 0
   if (size(geometry,/tname) ne 'STRUCT') then begin
      pGeometry = ptr_new(0)
      pGParm   = ptr_new(0)
      pRecent  = ptr_new(0)
      pOld     = ptr_new(0)
      ptcycle  = ptr_new(0)
      puv      = ptr_new(0)
      nRecent  = 0
      nOld = 0
      pnrpt    = ptr_new(0)
      pbigdata = ptr_new(0)
      ppol     = ptr_new(0)
      pindx    = ptr_new(0)
      plmonID  = 0L
   endif else begin
      pGParm  = ptr_new(gparm)
      pGeometry = ptr_new(geometry)
      ; Get number of times that will fit in RECENT and OLD
      nRecent = n_elements((*pRecent)(0,0,0,*))
      nOld    = n_elements((*pOld)(0,0,0,*))
      ptcycle = ptr_new(lonarr(nRecent))
      pnrpt   = cycle.pnrpt
      pbigdata = cycle.pbigdata
      ppol    = cycle.ppol
      pindx   = cycle.pindx

      nent = header.nws*obseq.nx            ; Total number of entries in one cycle

      ; Activate the phase lock monitor
      nf = n_elements(*obseq.phord)
      plock = intarr(nf)
      flist = *obseq.phord*200
      WIDGET_CONTROL,plmonID,SET_UVALUE=flist

      nant = header.nant
      puv     = ptr_new(fltarr(nant,nant,nRecent))
      ; Set up droplist widgets according to dimensions in data
      dim1 = nant + nant*(nant-1)/2
      dim3 = n_elements((*pRecent)(0,0,*,0))

      ; Ensure that the default indexes for the 4 plot windows is within range
      ; of the actual data
      fmin = min(abs(*obseq.phord - 25),imin)  ; Closest H to 25
      wIndex(*,0) = [0,nant,nant+2,2*nant+1]<(dim1-1)    ; This should be 12, 15, 26
      wIndex(*,1) = imin                       ; Index of closest H to 25
      wIndex(*,2) = [0,0,1,1]<(dim3-1)

      antlist = [1,2,4,5,6,7,8]
      aatab = header.aatab
      iants = intarr(nant)
      for i = 0, nant-1 do begin
         iants[i] = where(antlist eq aatab[i])
      endfor

      ; Generate string labels for drop-down channel list
      lchoff = header.lchoff
      nndw = 1 + header.ndbw + header.nfgw
      for j = 0, nant-1 do begin
         for i = 0, j do begin
            k = lchoff(iants[i],iants[j]) - nndw
            if (i eq j) then begin
               ; Case of total power channel
               dim1label(k) = 'TP Ant '+string(aatab(i),format='(i1)')+'   '
            endif else begin
               ; Case of correlated data
               if (k gt (nant-1)) then k = nant + (k-nant)/2
               str = string(aatab(i),aatab(j),format='(i1,"-",i1)')
               dim1label(k) = 'Amp/Pha '+str
            endelse
         endfor
      endfor

      ; Dimension 2 labels (for buttons and plot labelling) and parameters
      ; Parameters are frequency, in GHz
      dim2parm = *obseq.phord*0.2
      dim2label(0:nf-1) = string(dim2parm,format='(F4.1," GHz")')
      dim2label(nf:*) = '        '

      CASE dim3 OF
         1: dim3label[0] = 'I'
         2: dim3label[0:1] = ['R','L']
         3: dim3label[0:2] = ['R','L','I']
         4: dim3label = ['R','L','RL','LR']
      ENDCASE

      dim2 = nf
      WIDGET_CONTROL,DSrcList,SET_VALUE=dim1label(0:dim1-1)
      WIDGET_CONTROL, FrqList,SET_VALUE=dim2label(0:dim2-1)
      WIDGET_CONTROL, PolList,SET_VALUE=dim3label(0:dim3-1)
      for i = 0, 3 do begin
         ant =  dim1label(wIndex(i,0))
         frq =  dim2label(wIndex(i,1))
         pstr = dim3label(wIndex(i,2))
         labstr = ant+'  '+frq+'  '+strmid(pstr,0,2)+' Poln'
         WIDGET_CONTROL,wlab(i),SET_VALUE=labstr
      endfor
   endelse

   ; Pointer into RECENT and OLD arrays, to next time entry to be filled with data
   RecentPtr = 0L
   OldPtr = 0L

   ; Read the total power factors
;   tpfac = read_tpfac(dim2parm)
   tpfac = 0

   state = { $ ; Widget IDs
              wDraw:    wDraw, $ ; Array of 4 draw widgets
               wlab:     wlab, $ ; Array of 4 label widgets for the draw widgets
               mBar:     mBar, $ ; Widget ID of menu bar
              DList: [DSrcList,FrqList,PolList], $  ; Array of 3 drop_list widgets
             specid:   specid, $ ; Dynamic spectrum draw widget
                wid:      wid, $ ; The array of window IDs for the 4 draw widgets
              pixid:    pixid, $ ; The array of pixmap window IDs for 5 draw and spec widgets
            TimerID:  TimerID, $ ; The ID of the widget being used for the timer
            plmonID:  plMonID, $ ; The ID of phase lock monitor
             DateID:   DateID, $ ; The ID of the date widget
             NameID:   NameID, $ ; The ID of the sourcename widget
             NAvgID:   NAvgID, $ ; The ID of the sample average widget
             UpDFlg:   UpDFlg, $ ; The ID of the Update button widget
              ; Constants
               navg:        1, $ ; The value of number of samples to average currently in effect
               header_ready:0, $
              ; Pointers to storage
            pRecent:  pRecent, $ ; The RECENT (1 hour, full resolution) data
               pOld:     pOld, $ ; The OLD (full scan, averaged) data
            ptcycle:  ptcycle, $ ; The times corresponding to the RECENT array
                puv:      puv, $ ; The array of u,v points of size (NANT,NANT,nRecent)
              pnrpt:    pnrpt, $
           pbigdata: pbigdata, $
               ppol:     ppol, $
              pindx:    pindx, $
             tcycle:       0L, $
          firstcall:        1, $
              pdata: ptr_new(0), $
              ; File information
           filename: filename,   $ ; The name of the file
                lun:      lun,   $ ; The logical unit number of the file (or zero if not open)
             filptr: ptr_new(a), $ ; The pointer to the associated variable for the file
;             ptpfac: ptr_new(tpfac), $ ; Total power calibration factors (4,nf)
			pshift: pshift,  $
           rec:lastrec, hrec:hrec, nrec:nrec, dataflag:0, $
           maxp:maxbase,minp:minbase, $
           pbgnd:ptr_new(0), $
           SubSet:SubSet, SubApply:SubApply, BgndSet:0, BgndApply:0, $
           LCMode:1, SPMode:0, LogScl:0, TrkMode:0,$
           header:header, cfg:cfg, obseq:obseq, srcname:srcname, $
           pGeometry:pGeometry, pGParm:pGParm, refcal:refcal, $
           dim1label:dim1label, dim2label:dim2label, dim3label:dim3label, $
           nnow:-1, nval:0, nent:nent, new:0, wIndex:wIndex, wActive:0, $
           tselect:[0,0,0,0], $
           koff:0, written:0, counter:0,$
           ForceZero:1, TPcal:0, Update_Off:0, realtime:realtime, $
           nRecent:nRecent, nOld:nOld, RecentPtr:0L, OldPtr:0L}

   WIDGET_CONTROL, WIDGET_INFO(wBase,/CHILD), SET_UVALUE=state, /NO_COPY

   ; Set a 1 s timer to get things rolling
   WIDGET_CONTROL, TimerID, TIMER=1

;   XMANAGER,CATCH=0

   XMANAGER, 'Solaid', wBase, /NO_BLOCK



END
