;+
; NAME:
;     PLMONITOR
; PURPOSE:
;     Gives graphical indication of phase lock status for each antenna,
;     and allows access to more information by clicking on the appropriate
;     LED.
; CATEGORY:
;     OVRO APC DIAGNOSTIC
; CALLING SEQUENCE:
;     id     = PLMonitor(/INIT,[GROUP=group])
;     status = PLMonitor(id,plock)
;     status = PLMonitor(id,/DESTROY)
; INPUTS:
;     id        the widget ID returned by a previous, initializing
;                  call to PROGMETER (that used the /INIT switch).
;                  This input is ignored if INIT keyword is set.
;     plock     The phase lock status for a single cycle of data (the
;                  routine accumulates its own statistics)
; OPTIONAL (KEYWORD) INPUT PARAMETERS:
;     init      a keyword switch that governs which of two calling
;                  sequences is being used.  If set, a new widget is
;                  created.  If not set, an existing widget is to be
;                  updated.
;     destroy   a keyword switch that destroys the widget created by
;                  a previous call to PROGMETER (that used the /INIT switch)
;     group     the top base id of the calling program, so that the widget
;                  will be destroyed if the calling program exits.
;                  This input is ignored if INIT keyword is not set.
; ROUTINES CALLED:
; OUTPUTS:
;     id        the widget id of the compound widget (only when INIT
;                  keyword is set).
;     status    the status of the cancel button (only when INIT keyword
;                  is not set).  If the cancel button has been pressed,
;                  status='Cancel' and if not an empty string ('') is returned.
;                  If an error in calling sequence occurs (ID or VALUE
;                  is not supplied) then status='Cancel' also.
; COMMENTS:
;     To use the routine, call it with the /INIT switch to create the
;     widget, then call it repeatedly without the /INIT switch to update
;     it.  When done with the widget, it should be destroyed.  Action is
;     similar to the PROGMETER routine.  Here is an example:
;
;          id = PLMonitor(/INIT,Group=GroupID,label='Progress Meter')
;          ; Loop over data cycles
;          for i = 0, ncycles do begin
;             <Get a cycle of data, strip off phaselock into array plock>
;             if (id NE 0) then status = PLMonitor(id,plock)
;             if (status EQ 'Quit') then status = PLMonitor(id,/DESTROY)
;          endfor
;
; SIDE EFFECTS:
; RESTRICTIONS:
;     The Phase Lock Monitor widget must be explicitly destroyed.
; MODIFICATION HISTORY:
;     Written 29-Jul-1998 by Dale E. Gary
;     20-Aug-1998  DG
;       Change to display an LED for each oscillator, and make phase lock
;       history show one oscillator at a time.  Also update the
;       phase lock history window each sample instead of just in response
;       to a mouse click.
;     11-Jan-2000  DG
;       Eliminated !UNIX variable by using !DEFAULTS.FONT
;-

;-----------------------------------------------------------------
; This is a helper procedure called by PROGMETER when setting the
; value of the widget.  It actually draws the proportional bar and
; writes a label in XOR mode onto the bar.

pro PLMonitor_setvalue, id, plock

   ; Get state variables so that draw window id is known
   stash = WIDGET_INFO(id,/CHILD)
   WIDGET_CONTROL, stash, GET_UVALUE=state, /NO_COPY

   wsav = !d.window
   ; Set the draw window as the current window
   wset,state.win

   ; Get list of phaselocks for each antenna
   nant = 5
   nosc = 3
   nfrq = n_elements(plock)
   pl = intarr(nant,nfrq)
   label = ['Ant 1','Ant 2','Ant 4','Ant 5','Ant 6']
   ; Loop over antennas
   for iant = 0, nant-1 do begin
      ; This splits the phase lock bits into a list for each antenna
      pl(iant,*) = ISHFT(plock,-iant) and '1'x
      state.accum(iant,*) = ISHFT(state.accum(iant,*),1)+pl(iant,*)
   endfor

   ; These are lists of indexes into PL array for the Low, Middle, and
   ; High oscillators, respectively
   lo = where(state.flist le 2400)
   mid = where(state.flist gt 2400 and state.flist le 8000)
   hi = where(state.flist gt 8000)

   ; ON array determines whether the red LED should be on.  One entry
   ; for each oscillator of each antenna.
   on = bytarr(nant,nosc)
   ; "Clever" code to set the ON array to 1 if more than 25% of frequencies are bad
   if (lo(0) ne -1) then $
      on(*,0) = total(pl(*,lo),2) GT n_elements(lo)*0.25
   if (mid(0) ne -1) then $
      on(*,1) = total(pl(*,mid),2) GT n_elements(mid)*0.25
   if (hi(0) ne -1) then $
      on(*,2) = total(pl(*,hi),2) GT n_elements(hi)*0.25

   ; Save current font setting and use system font
   old_font = !p.font
   !p.font = 0
   ; Turn on "decomposed" color (16 million colors) for true-color display
   device,decomposed=1

   ; For each on=1, set the red LED, otherwise set the green LED
   for i = 0, nant-1 do begin
      for j = 0, nosc-1 do begin
         if (on(i,j) eq 1) then img = state.rled else img = state.gled
         tv,img,true=1,40*i,40*(nosc-1-j)

         ; If iant and iosc are set, it means an LED is selected so
         ; draw a box in XOR mode
         if (i eq state.iant and j eq state.iosc) then begin
            device,set_gr=6    ; XOR mode
            k = nosc-1-j
            plots,/device,40*(i-0.1+[.4,1.02,1.02,.4,.4]),$
                                  40*(k-0.05+[.4,.4,1.02,1.02,.4])
            device,set_gr=3    ; Normal mode
         endif
      endfor
      xyouts,9+40*i,5,/device,label(i),color=0
   endfor

   ; Convert back to 256 colors, restore font setting, restore active window
   device,decomposed=0
   !p.font=old_font
   wset,wsav

   ; Resave state variables
   WIDGET_CONTROL,stash,SET_UVALUE=state,/NO_COPY
end

;-----------------------------------------------------------------
; This is a helper function called by PLMONITOR to check whether
; any of the LEDs have been clicked, and if so, to spawn a window
; containing more information.  Once clicked, the window will
; continue to update until the oscillator is clicked again.

function PLMonitor_getvalue, id

   stash = WIDGET_INFO(id,/CHILD)
   WIDGET_CONTROL, stash, GET_UVALUE=state, /NO_COPY

   ; These are lists of indexes into ACCUM array for the Low, Middle, and
   ; High oscillators, respectively
   lo = where(state.flist le 2400)
   mid = where(state.flist gt 2400 and state.flist le 8000)
   hi = where(state.flist gt 8000)

   ant = [1,2,4,5,6]
   osc = ['Low','Mid',' Hi']
   nosc = 3

   ; Set the return value to the id of the draw widget
   ret = state.drawid
   event = WIDGET_EVENT(state.drawid,/nowait)
   while (event.id eq state.drawid) do begin
      ; Throw away all but the last event
      if (event.type eq 0) then begin
         iant = (Event.X/40)<4
         iosc = 2-(Event.Y/40)<2

         ; If LED that was clicked on matches the current state, turn
         ; window updating off
         if (iant eq state.iant and iosc eq state.iosc) then begin
            ; Plot a box around the previously selected LED in XOR mode, which
            ; should erase it
            wset,state.win
            device,set_gr=6
            k = nosc-1-state.iosc
            plots,/device,40*(state.iant-0.1+[.4,1.02,1.02,.4,.4]),$
                                  40*(k-0.05+[.4,.4,1.02,1.02,.4])
            device,set_gr=3
            state.iant = -1
            state.iosc = -1
         endif else begin
            ; Plot a box around the previously selected LED in XOR mode, which
            ; should erase it
            if (state.iant ne -1) then begin
               wset,state.win
               device,set_gr=6
               k = nosc-1-state.iosc
               plots,/device,40*(state.iant-0.1+[.4,1.02,1.02,.4,.4]),$
                               40*(k-0.05+[.4,.4,1.02,1.02,.4])
               device,set_gr=3
            endif
            state.iant = iant
            state.iosc = iosc
            ; Plot a box around the newly selected LED in XOR mode, which
            ; should set it
            wset,state.win
            device,set_gr=6
            k = nosc-1-state.iosc
            plots,/device,40*(state.iant-0.1+[.4,1.02,1.02,.4,.4]),$
                                  40*(k-0.05+[.4,.4,1.02,1.02,.4])
            device,set_gr=3
         endelse
      endif
      event = WIDGET_EVENT(state.drawid,/nowait)
   endwhile
   iant = state.iant
   iosc = state.iosc

   if (iant eq -1) then begin
      out = ' '
      WIDGET_CONTROL,state.base2,MAP=0
   endif else begin
      WIDGET_CONTROL,state.base2,MAP=1
     CASE iosc OF
        0: ilist = lo
        1: ilist = mid
        2: ilist = hi
      ENDCASE

      ; Set OOL to the appropriate Out of Lock list, if any
      if (ilist(0) eq -1) then OOL = -1 else $
                      OOL = where(reform(state.accum(iant,ilist)) ne 0)
      flist = state.flist
      IF (OOL(0) NE -1) THEN BEGIN
         out = strarr(n_elements(OOL)+3)
         out(0) = string(ant(iant),osc(iosc),format='("Antenna ",I1,1X,A3," Oscillator")')
         out(1) = 'F (MHz)  History (octal)
         out(2) = '-------  -------
         for i = 0, n_elements(OOL)-1 do begin
            out(i+3) = string(flist(ilist(OOL(i))),state.accum(iant,ilist(OOL(i))) $
                      and 'FFFF'X, format='(I7,2x,o7)')
         endfor
      ENDIF ELSE BEGIN
         out = string(ant(iant),osc(iosc),format='("No OOL for Antenna ",I1,1X,A3," Oscillator")')
      ENDELSE
;      print,out,format='(a)'
   endelse
   WIDGET_CONTROL,state.textid,SET_VALUE=out
   WIDGET_CONTROL,stash,SET_UVALUE=state,/NO_COPY

return,ret
end

;-----------------------------------------------------------------
; This is the main routine.  It consists of three separate routines,
; actually, separated by if-then clauses, one routine for each of
; the three calling sequences.  If the /INIT switch is set, the
; widget is created.  If neither /INIT nor /DESTROY switches are
; set, the value of the widget is updated and the button state is
; checked.  If the /DESTROY switch is set, the widget is destroyed.
;

function PLMonitor,id,plock,INIT=init,DESTROY=destroy,GROUP=group

   ; Section of code to be run if /INIT keyword is *not* set

   if (not keyword_set(init)) then begin

      ; First verify that an ID was given

      if (n_elements(id) eq 0) then begin
         msg = ['No widget ID specified in call to PLMONITOR.',$
                'Must first call PLMONITOR with /INIT to obtain ID.']
         ans = widget_message(msg,/error)
         return,'Quit'
      endif

      ; ID was given, so see if /DESTROY keyword is set.  If so, destroy
      ; the widget and return

      if (keyword_set(destroy)) then begin
         xkill,id
         return,'Quit'
      endif

      ; Neither /INIT nor /DESTROY were set, so actually do something
      ; useful with the PLOCK data

      if (n_elements(plock) eq 0) then begin
         msg = ['No phase lock data specified in call to PLMONITOR.',$
                'Must supply a cycle of phase lock data.  See documentation.']
         ans = widget_message(msg,/error)
         return,'Quit'
      endif

      ; Set value and get button ID

      WIDGET_CONTROL,id,/REALIZE
      if (plock(0) ne -1) then WIDGET_CONTROL,id,SET_VALUE=plock
      WIDGET_CONTROL,id,GET_VALUE=drawid
      ret = ''

      return,ret
   endif

   ; If /INIT keyword was not set, the program returns to caller before
   ; reaching this point.  This is the section of code to be run if /INIT
   ; keyword *is* set.

   flist = id
   if (n_elements(plock) eq 0) then begin
      msg = ['No phase lock data specified in call to PLMONITOR.',$
             'Must supply a cycle of phase lock data.  See documentation.']
      ans = widget_message(msg,/error)
      return,'Quit'
   endif

   ; Check that LED images are available
   read_jpeg,'RedLED.jpg',rled
   read_jpeg,'GreenLED.jpg',gled

   ; Set some defaults if not given in calling sequence

   label = 'Phase Lock Monitor'
   if (not keyword_set(group)) then group = 0

   ; Set the base widget, specifying my own routines to be run
   ; if WIDGET_CONTROL,GET_VALUE or SET_VALUE are called.

   base = WIDGET_BASE(/COLUMN, TITLE=label,GROUP = group, $
                      FUNC_GET_VALUE='PLMonitor_getvalue', $
                      PRO_SET_VALUE ='PLMonitor_setvalue')

   ; Set up the draw widget and optionally the button widget

   drawid = WIDGET_DRAW(base,xsize=210,ysize=130,BUTTON_EVENTS=1)
;   bbase = WIDGET_BASE(base,/ROW,/ALIGN_CENTER,ysize=25)
;   button = WIDGET_BUTTON(bbase,value='Dismiss',UVALUE='BUTTON')

   font = !DEFAULTS.FONT

   base2 = WIDGET_BASE(base,/COLUMN,MAP=0)
   TextID = WIDGET_TEXT(base2,VALUE='', $
      UVALUE='TextID', $
      FONT=font, $
      /SCROLL, $
      XSIZE=30, $
      YSIZE=10)

   ; Realize (draw) them
   WIDGET_CONTROL,base,group=group

   WIDGET_CONTROL,base,/REALIZE
   WIDGET_CONTROL,base,TLB_SET_YOFFSET=100
   ; Get the window id of the draw widget
   WIDGET_CONTROL,drawid,GET_VALUE=win

   ; This is going to have to be changed when either more or fewer
   ; antennas are available.
   nant = 5
   nfrq = n_elements(flist)
   accum = intarr(nant,nfrq)

   ; Save some info that will be needed in the other functions
   state = {win:win, drawid:drawid, base2:base2, rled:rled, gled:gled, flist:flist,$
            textid:textid, accum:accum, iant:-1, iosc:-1}

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

   WIDGET_CONTROL,base,SET_VALUE=plock

   ; The widget is created and drawn to the screen, so return the base
   ; ID so that the widget can be accessed by later calls to update it
   ; or destroy it.

   return,base

end

