;+
; NAME:
;     CW_PLMONITOR
; PURPOSE:
;     Compound widget.  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:
;     PLMonID = CW_PLMonitor(parent,[GROUP=group])
; INPUTS:
;     parent    the widget ID of the base widget of the parent base,
;                  in which to embed the widget
; OPTIONAL (KEYWORD) INPUT PARAMETERS:
;     group     the top base id of the calling program, so that the widget
;                  will be destroyed if the calling program exits.
; ROUTINES CALLED:
; OUTPUTS:
;     PLMonID   the widget id of the compound widget.
; COMMENTS:
;     To use the routine, call it as above, then update it with the
;     WIDGET_CONTROL routine via SET_VALUE and SET_UVALUE.  The required
;     values are:
;        UVALUE   an array containing the list of frequencies in MHz for
;                   the current observing sequence.  The following is
;                   typical:
;                     WIDGET_CONTROL,PLMonID,SET_UVALUE=(*obseq.phord)*200
;        VALUE    a 2-d array of size (NANT,NF) where NANT is the number
;                   of antennas (currently 5) and NF is the number of
;                   entries given in the frequency list (UVALUE).  Each
;                   entry represents whether the latest sample for a given
;                   antenna and frequency was in phase lock (0) or out of
;                   lock (1).
;     The phase lock history is accumulated internally in the routine.
; SIDE EFFECTS:
; RESTRICTIONS:
; 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.
;     12-Sep-1998  DG
;       Completely rewritten as a compound widget, for use in the SOLAID
;       interface.
;     15-Sep-1998  DG
;       Added new LED colors black and yellow.
;     15-Dec-1998  DG
;       Fixed bug where code failed when an oscillator had exactly one
;       used harmonic (problem was that the TOTAL routine fails when
;       and array has only one element).
;     09-Jan-1999  DG
;       Added UINT data type to fix OCTAL display of unsigned ACCUM value,
;       which broke on introduction of IDL 5.2.
;     11-Jan-2000  DG
;       Eliminate !UNIX system variable, and use FONT from !DEFAULTS
;     11-Apr-2000  DG
;       Make location of LED jpeg files independent of operating system
;       through the use of $SSW environment variable and concat_dir() routine.
;     23-May-2000  DG
;       Updated to work with antenna 7.  If fewer than 6 ants are used,
;       missing ants will have black LEDs.  When antenna 3 comes on line,
;       this routine will have to be updated once more.
;     15-Mar-2003  DG
;       Updated to add antenna 8.
;     14-Jul-2004  DG
;       Fixed bug that made Ant 8 display not respond properly to mouse clicks.
;-

;-----------------------------------------------------------------
; Handles updating the text widget for displaying the OOL history of the
; selected antenna/oscillator.

pro update_text,id

   ; Get state variables so that draw window id is known
   stash = WIDGET_INFO(id,/CHILD)
   WIDGET_CONTROL, stash, GET_UVALUE=state, /NO_COPY

   ; **Change this when another antenna is added.
   ant = [1,2,4,5,6,7,8]
   osc = ['Low','Mid',' Hi']
   iant = state.iant
   iosc = state.iosc
   accum = *state.paccum
   WIDGET_CONTROL,id,GET_UVALUE=flist

   ; These are lists of indexes into PL array for the Low, Middle, and
   ; High oscillators, respectively
   lo = where(flist le 2400)
   mid = where(flist gt 2400 and flist le 8000)
   hi = where(flist gt 8000)

   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(accum(iant,ilist)) ne 0)
      IF (OOL(0) NE -1) THEN BEGIN
         out = strarr(n_elements(OOL)+3)
         out(0) = string(ant(iant),osc(iosc),format='("Antenna ",I1,2X,A3," Osc")')
         out(1) = 'F (MHz)  History'
         out(2) = '-------  -------'
         for i = 0, n_elements(OOL)-1 do begin
            out(i+3) = string(flist(ilist(OOL(i))),uint(accum(iant,ilist(OOL(i))) $
                      and 'FFFF'X), format='(I7,2x,o7)')
         endfor
      ENDIF ELSE BEGIN
         out = [string(ant(iant),osc(iosc),format='("Antenna ",I1,2X,A3," Osc")'),'No OOL']
      ENDELSE
   endelse

   WIDGET_CONTROL,state.textid,SET_VALUE=out

   ; Resave state variables
   WIDGET_CONTROL,stash,SET_UVALUE=state,/NO_COPY

return
end

;-----------------------------------------------------------------
; This is a helper procedure called when setting the value of the
; compound widget.  It actually draws the updated LEDs according to
; the current value of PLOCK, and adds the current value to the
; ACCUM array (whose pointer is STATE.pACCUM)

pro CW_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
   WIDGET_CONTROL,state.drawid,GET_VALUE=win
   ; Set the draw window as the current window
   wset,win

   ; Get list of phaselocks for each antenna
   nant = 7   ; **Change this when another antenna is added.
   nosc = 3
   nfrq = n_elements(plock)

   ; Check that current ACCUM array is consistent with PLOCK, and
   ; reinit ACCUM array if not
   if (n_elements(*state.paccum) ne nant*nfrq) then begin
      ptr_free,state.paccum
      state.paccum = ptr_new(intarr(nant,nfrq))
   endif
   accum = *state.paccum
   WIDGET_CONTROL,id,GET_UVALUE=flist

   pl = intarr(nant,nfrq)

   ; **Change this when another antenna is added
   label = ['Ant 1','Ant 2','Ant 4','Ant 5','Ant 6','Ant 7','Ant 8']

   ; 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
      accum(iant,*) = ISHFT(accum(iant,*),1)+pl(iant,*)
   endfor

   *state.paccum = accum

   ; These are lists of indexes into PL array for the Low, Middle, and
   ; High oscillators, respectively
   lo = where(flist le 2400,nlo)
   mid = where(flist gt 2400 and flist le 8000,nmid)
   hi = where(flist gt 8000,nhi)

   ; 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 a color code if the number of
   ; out of locks are in specific ranges.  Color codes are 1=green, 2=amber,
   ; 3= red, and corresponding ranges are 0-25%, 25-75%, and >75% OOL
   if (nlo gt 0) then begin
      if (nlo eq 1) then totOOL = pl(*,lo[0]) $
                     else totOOL = total(pl[*,lo],2)
      green = where(totOOL LT nlo*0.25)
      if (green(0) ne -1) then on(green,0) = 1
      amber = where(totOOL GE nlo*0.25 AND totOOL LE nlo*0.75)
      if (amber(0) ne -1) then on(amber,0) = 2
      red = where(totOOL GT nlo*0.75)
      if (red(0) ne -1) then on(red,0) = 3
   endif
   if (nmid gt 0) then begin
      if (nmid eq 1) then totOOL = pl[*,mid[0]] $
                     else totOOL = total(pl[*,mid],2)
      green = where(totOOL LT nmid*0.25)
      if (green(0) ne -1) then on(green,1) = 1
      amber = where(totOOL GE nmid*0.25 AND totOOL LE nmid*0.75)
      if (amber(0) ne -1) then on(amber,1) = 2
      red = where(totOOL GT nmid*0.75)
      if (red(0) ne -1) then on(red,1) = 3
   endif
   if (nhi gt 0) then begin
      if (nhi eq 1) then totOOL = pl[*,hi[0]] $
                     else totOOL = total(pl[*,hi],2)
      green = where(totOOL LT nhi*0.25)
      if (green(0) ne -1) then on(green,2) = 1
      amber = where(totOOL GE nhi*0.25 AND totOOL LE nhi*0.75)
      if (amber(0) ne -1) then on(amber,2) = 2
      red = where(totOOL GT nhi*0.75)
      if (red(0) ne -1) then on(red,2) = 3
   endif

   ; 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

   LEDsiz = state.LEDsiz

   ; 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
         CASE on(i,j) OF
            0: img = state.bled
            1: img = state.gled
            2: img = state.yled
            3: img = state.rled
          ELSE: img = state.bled
         ENDCASE
         tv,img,true=1,LEDsiz*i,LEDsiz*(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,LEDsiz*(i-0.1)+40*[.4,1.02,1.02,.4,.4]-8,$
                          LEDsiz*(k-0.05)+40*[.4,.4,1.02,1.02,.4]-6
            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

   ; Save new OUT string array to the STATE variable
;   ptr_free,state.pout
;   state.pout = ptr_new(out)

   ; Resave state variables
   WIDGET_CONTROL,stash,SET_UVALUE=state,/NO_COPY

   update_text,id

end

;-----------------------------------------------------------------
; GET_VALUE function.  I really have no need to get the state of
; the widget, so for now I just return the antenna and oscillator
; values, or [-1,-1] if not set.

function CW_PLMonitor_getvalue, id

   stash = WIDGET_INFO(id,/CHILD)
   WIDGET_CONTROL, stash, GET_UVALUE=state, /NO_COPY

   iant = state.iant
   iosc = state.iosc

   WIDGET_CONTROL,stash,SET_UVALUE=state,/NO_COPY

return,[iant,iosc]
end

;-----------------------------------------------------------------
; This is the event handler for PLMONITOR

function CW_PLMonitor_Event, Event

   base = Event.Handler
   stash = WIDGET_INFO(base,/CHILD)
   WIDGET_CONTROL, stash, GET_UVALUE=state, /NO_COPY

   nosc = 3
   LEDsiz = state.LEDsiz

   CASE event.id OF

   ; This is the only type of event expected.  When user clicks on an
   ; LED, this code updates the box around the selected LED (turning it
   ; off if necessary), and then calls the GET_VALUE routine to update
   ; the text widget, if necessary.
   state.drawid: BEGIN
      if (event.type eq 0) then begin
         wsav = !d.window
         WIDGET_CONTROL,state.drawid,GET_VALUE=win
         iant = (Event.X/LEDsiz)<6
         iosc = 2-(Event.Y/LEDsiz)<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,win
            device,set_gr=6
            k = nosc-1-state.iosc
            plots,/device,LEDsiz*(state.iant-0.1)+40*[.4,1.02,1.02,.4,.4]-8,$
                                  LEDsiz*(k-0.05)+40*[.4,.4,1.02,1.02,.4]-6
            device,set_gr=3
            state.iant = -1
            state.iosc = -1
            WIDGET_CONTROL,state.base2,MAP=0
         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,win
               device,set_gr=6
               k = nosc-1-state.iosc
               plots,/device,LEDsiz*(state.iant-0.1)+40*[.4,1.02,1.02,.4,.4]-8,$
                               LEDsiz*(k-0.05)+40*[.4,.4,1.02,1.02,.4]-6
               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,win
            device,set_gr=6
            k = nosc-1-state.iosc
            plots,/device,LEDsiz*(state.iant-0.1)+40*[.4,1.02,1.02,.4,.4]-8,$
                                  LEDsiz*(k-0.05)+40*[.4,.4,1.02,1.02,.4]-6
            device,set_gr=3
            WIDGET_CONTROL,state.base2,MAP=1
         endelse
         wset,wsav
      endif
   END
   ELSE:
   ENDCASE


   ret = 0L
;   { PLMonitor_Event, ID:base, TOP:Event.Top, HANDLER:0L, VALUE:out }
   WIDGET_CONTROL,stash,SET_UVALUE=state,/NO_COPY

   update_text,base

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 CW_PLMonitor,parent,GROUP=group

;   ; Get top of OVSA tree
   ovsa_init = concat_dir(concat_dir(concat_dir(concat_dir(concat_dir('$SSW','radio'),'ovsa'),'idl'),'icons'),'')
   if (strmid(ovsa_init,0,1) eq '$') then begin
      ans = dialog_message('CW_PLMonitor: $SSW environment variable not defined?',/error)
;      ovsa_init = 'D:\rsi\idl52\lib\apc\icons\'
   endif
   ; Check that LED images are available
   read_jpeg,ovsa_init+'RedLED.jpg',rled
   read_jpeg,ovsa_init+'GreenLED.jpg',gled
   read_jpeg,ovsa_init+'AmberLED.jpg',yled
   read_jpeg,ovsa_init+'BlackLED.jpg',bled

   ; Adjust the size of the LED images
   rled = rled(*,7:44,6:43)
   gled = gled(*,7:44,6:43)
   yled = yled(*,7:44,6:43)
   bled = bled(*,7:44,6:43)

   ; 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(parent, /COLUMN, TITLE=label,GROUP = group, $
                      EVENT_FUNC='CW_PLMonitor_Event', $
                      FUNC_GET_VALUE='CW_PLMonitor_getvalue', $
                      PRO_SET_VALUE ='CW_PLMonitor_setvalue')

   ; **Change this when another antenna is added
   nant = 7
   l1 = WIDGET_LABEL(base,VALUE='Ant 1  Ant 2  Ant 4  Ant 5  Ant 6  Ant 7  Ant 8')

   ; Set up the draw widget
   LEDsiz = 30
   drawid = WIDGET_DRAW(base,xsize=nant*ledsiz+8,ysize=3*ledsiz+8,BUTTON_EVENTS=1)

   font = !DEFAULTS.FONT
;   'Lucida Console*12'
;   if (!UNIX) then font = ''

   base2 = WIDGET_BASE(base,/COLUMN,MAP=0)
   TextID = WIDGET_TEXT(base2,VALUE='', $
      UVALUE='TextID', $
      FONT=font, $
      /SCROLL, $
      XSIZE=17, $
      YSIZE=10)

   ; Save some info that will be needed in the other functions
   state = {drawid:drawid, base2:base2, $
            bled:bled, rled:rled, gled:gled, yled:yled,$
            textid:textid, paccum:ptr_new(0), iant:-1, iosc:-1,$
            LEDsiz:LEDsiz}

   WIDGET_CONTROL, WIDGET_INFO(base, /CHILD), SET_UVALUE=state, /NO_COPY

   return,base

end

