; docformat = 'rst'
;
; NAME:
;   XColors
;
; PURPOSE:
;   The purpose of this routine is to interactively change color tables
;   in a manner similar to XLOADCT. No common blocks are used so
;   multiple copies of XCOLORS can be on the display at the same
;   time (if each has a different TITLE). XCOLORS has the ability
;   to notify a widget event handler, an object method, or an IDL
;   procedure if and when a new color table has been loaded. Brewer
;   color tables can also be accessed from this program, if the file
;   fsc_brewer.tbl can be found somewhere in your IDL path.
;
;******************************************************************************************;
;                                                                                          ;
;  Copyright (c) 1997-2012, by Fanning Software Consulting, Inc. All rights reserved.      ;
;                                                                                          ;
;  Redistribution and use in source and binary forms, with or without                      ;
;  modification, are permitted provided that the following conditions are met:             ;
;                                                                                          ;
;      * Redistributions of source code must retain the above copyright                    ;
;        notice, this list of conditions and the following disclaimer.                     ;
;      * Redistributions in binary form must reproduce the above copyright                 ;
;        notice, this list of conditions and the following disclaimer in the               ;
;        documentation and/or other materials provided with the distribution.              ;
;      * Neither the name of Fanning Software Consulting, Inc. nor the names of its        ;
;        contributors may be used to endorse or promote products derived from this         ;
;        software without specific prior written permission.                               ;
;                                                                                          ;
;  THIS SOFTWARE IS PROVIDED BY FANNING SOFTWARE CONSULTING, INC. ''AS IS'' AND ANY        ;
;  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES    ;
;  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT     ;
;  SHALL FANNING SOFTWARE CONSULTING, INC. BE LIABLE FOR ANY DIRECT, INDIRECT,             ;
;  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED    ;
;  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;         ;
;  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND             ;
;  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT              ;
;  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS           ;
;  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                            ;
;******************************************************************************************;
;+
; The purpose of this program is to interactively change color tables
; in a manner similar to XLoadCT. No common blocks are used so
; multiple copies of XColors can be on the display at the same
; time (if each has a different `Title`). XColors has the ability
; to notify a widget event handler, an object method, or an IDL
; procedure if and when a new color table has been loaded. Brewer
; color tables can also be accessed from this program, if the file
; fsc_brewer.tbl can be found somewhere in your IDL path.
;
; Events are sent to widgets if the `NotifyID` keyword is used. Object
; methods are called if the `NotifyObj` keyword is used. This program
; is a non-blocking widget unless the `Block` keyword is set.
;
; .. image:: xcolors.png
;
; :Categories:
;    Graphics
;
; :Examples:
;    To load a color table into 100 colors, starting at color index
;     50 and send an event to the widget identified at info.drawID
;     in the widget heirarchy of the top-level base event.top, type::
;        XCOLORS, NCOLORS=100, BOTTOM=50, NOTIFYID=[info.drawID, event.top]
;
; :Author:
;       FANNING SOFTWARE CONSULTING::
;           David W. Fanning
;           1645 Sheely Drive
;           Fort Collins, CO 80526 USA
;           Phone: 970-221-0438
;           E-mail: david@idlcoyote.com
;           Coyote's Guide to IDL Programming: http://www.idlcoyote.com
;
; :History:
;    Change History::
;       Written by David W. Fanning, 15 April 97.
;       Added OBJECT_DATA keyword so that I can get additional information
;           about the state of the color table tool into object methods. 21 October 2008. DWF.
;       Add REVERSE keyword and Reverse Color Table button. 12 April 2009. DWF.
;       In looking for a Brewer color table file, I replaced all FILE_WHICH
;           commands with cgFindPathTo commands. 28 April 2009. DWF.
;       Made sure all "NOTIFY" data structures have both a "REVERSED" and
;           "BREWER" field in them to indicate the status of the XCOLORS program. Also
;           inproved the documentation and made it more accurate. 20 Sept 2009. DWF.
;       Still a few problems getting the Brewer color tables completely integrated.
;           Fixed several bugs with updating color table names and type. 14 Oct 2009. DWF.
;       Modified the program to work correctly with a user-supplied color table file. 29 Sept 2010. DWF.
;       Fixed a problem I noticed when starting the program with reversed color tables. The
;            initial colors were incorrect on subsequent calls. Also made a modification so that
;            color index -1 as input is handled properly (ignored). 26 November 2010. DWF.
;       Added WINDOW and WINID keywords. 26 January 2011. DWF.
;       Changed several Get_Decomposed calls to the more generic cgSetColorState. 15 Jan 2012. DWF.
;
; :Copyright:
;     Copyright (c) 1997-2012, Fanning Software Consulting, Inc.
;-------------------------------------------------------------------------------------------

;+
; This routines is identical to the IDL Congrid command, except that it
; handles a problem with floating divides by zero properly.
;
; :Params:
;    arr: in, required
;       The input array to be resized.
;    x: in, required
;       The X dimension of the output.
;    y: in, optional
;       The Y dimension of the output.
;    z: in, optional
;       The Z dimension of the output.
;
; :Keywords:
;    cubic: in, optional, type=boolean, default=0
;       Set this keyword to perform cubic convolution interpolation rather than nearest neighbor
;       interpolation.
;    interp: in, optional, type=boolean, default=0
;       Set this keyword to do bilinear interpolation rather than nearest neighbor interpolation.
;    minus_one: in, optional, type=boolean, default=0
;       Set this keyword to prevent extrapolating one row or column beyond the bounds of the input.
;-
Function XColors_Congrid, arr, x, y, z, CUBIC = cubic, INTERP=int, MINUS_ONE=m1

    ON_ERROR, 2      ;Return to caller if error
    s = Size(arr)

    if ((s[0] eq 0) or (s[0] gt 3)) then $
      Message, 'Array must have 1, 2, or 3 dimensions.'

    ;;  Supply defaults = no interpolate, and no minus_one.
    if (N_ELEMENTS(int) le 0) then int = 0 else int = KEYWORD_SET(int)
    if (N_ELEMENTS(m1) le 0) then m1 = 0 else m1 = KEYWORD_SET(m1)
    if (N_ELEMENTS(cubic) eq 0) then cubic = 0
    if (cubic ne 0) then int = 1 ;Cubic implies interpolate


    case s[0] of
        1: begin                ; *** ONE DIMENSIONAL ARRAY
            ; DWF modified: Check divide by zero.
            srx = float(s[1] - m1)/((x-m1) > 1e-6) * findgen(x) ;subscripts
            if (int) then $
              return, INTERPOLATE(arr, srx, CUBIC = cubic) else $
              return, arr[ROUND(srx)]
        endcase
        2: begin                ; *** TWO DIMENSIONAL ARRAY
            if (int) then begin
                srx = float(s[1] - m1) / ((x-m1) > 1e-6) * findgen(x)
                sry = float(s[2] - m1) / ((y-m1) > 1e-6) * findgen(y)
                return, INTERPOLATE(arr, srx, sry, /GRID, CUBIC=cubic)
            endif else $
              return, POLY_2D(arr, $
                              [[0,0],[(s[1]-m1)/(float(x-m1) > 1e-6),0]], $ ;Use poly_2d
                              [[0,(s[2]-m1)/(float(y-m1) > 1e-6)],[0,0]],int,x,y)

        endcase
        3: begin                ; *** THREE DIMENSIONAL ARRAY
            srx = float(s[1] - m1) / ((x-m1) > 1e-6) * findgen(x)
            sry = float(s[2] - m1) / ((y-m1) > 1e-6) * findgen(y)
            srz = float(s[3] - m1) / ((z-m1) > 1e-6) * findgen(z)
            return, interpolate(arr, srx, sry, srz, /GRID)
        endcase
    endcase

    return, arr_r
END ; ***************************************************************


;+
; Define a structure for notifying an object method.
;-
PRO XColors_NotifyObj__Define

   ; Structure definition module for object notification.

struct = {  XColors_NotifyObj, $  ; The structure name.
            object:Obj_New(),  $  ; The object to notify.
            method:'' }           ; The name of the object method to call.

END ; ***************************************************************


;+
; Set the new colors for the program.
;
; :Params:
;     info: in, required, type=structure
;         The information structure for the widget program, containing all the
;         information needed to run the program.
;-
PRO XColors_Set, info

    TVLCT, r, g, b, /Get

       ; Is the color table reversed?

    reverseSet = Widget_Info(info.reverseID, /BUTTON_SET)

       ; Make sure the current bottom index is less than the current top index.

    IF info.currentbottom GE info.currenttop THEN BEGIN
       temp = info.currentbottom
       info.currentbottom = info.currenttop
       info.currenttop = temp
    ENDIF

    IF reverseSet THEN BEGIN
        r(info.bottom:info.currentbottom) = info.topcolor(0)
        g(info.bottom:info.currentbottom) = info.topcolor(1)
        b(info.bottom:info.currentbottom) = info.topcolor(2)
        r(info.currenttop:info.top) = info.bottomcolor(0)
        g(info.currenttop:info.top) = info.bottomcolor(1)
        b(info.currenttop:info.top) = info.bottomcolor(2)
    ENDIF ELSE BEGIN
        r(info.bottom:info.currentbottom) = info.bottomcolor(0)
        g(info.bottom:info.currentbottom) = info.bottomcolor(1)
        b(info.bottom:info.currentbottom) = info.bottomcolor(2)
        r(info.currenttop:info.top) = info.topcolor(0)
        g(info.currenttop:info.top) = info.topcolor(1)
        b(info.currenttop:info.top) = info.topcolor(2)
    ENDELSE

    red = info.r
    green = info.g
    blue = info.b
    number = ABS((info.currenttop-info.currentbottom) + 1)

    gamma = info.gamma
    index = Findgen(info.ncolors)
    distribution = index^gamma > 10e-6
    index = Round(distribution * (info.ncolors-1) / (Max(distribution) > 10e-6))

    IF info.currentbottom GE info.currenttop THEN BEGIN
       temp = info.currentbottom
       info.currentbottom = info.currenttop
       info.currenttop = temp
    ENDIF

    IF info.reversed EQ 0 THEN BEGIN
       IF reverseSet THEN BEGIN
           r(info.currentbottom:info.currenttop) = Reverse(XColors_Congrid(red(index), number, /Minus_One))
           g(info.currentbottom:info.currenttop) = Reverse(XColors_Congrid(green(index), number, /Minus_One))
           b(info.currentbottom:info.currenttop) = Reverse(XColors_Congrid(blue(index), number, /Minus_One))
       ENDIF ELSE BEGIN
           r(info.currentbottom:info.currenttop) = XColors_Congrid(red(index), number, /Minus_One)
           g(info.currentbottom:info.currenttop) = XColors_Congrid(green(index), number, /Minus_One)
           b(info.currentbottom:info.currenttop) = XColors_Congrid(blue(index), number, /Minus_One)
       ENDELSE
    ENDIF ELSE BEGIN
       r(info.currentbottom:info.currenttop) = $
          Reverse(XColors_Congrid(red(index), number, /Minus_One))
       g(info.currentbottom:info.currenttop) = $
          Reverse(XColors_Congrid(green(index), number, /Minus_One))
       b(info.currentbottom:info.currenttop) = $
          Reverse(XColors_Congrid(blue(index), number, /Minus_One))
    ENDELSE

    TVLCT, r, g, b
    WSet, info.windowindex
    cgSetColorState, 0, Current=theState
    TV, info.colorimage
    Device, Decomposed=theState
    WSet, info.thisWindow

    (*info.colorInfoPtr).R = r
    (*info.colorInfoPtr).G = g
    (*info.colorInfoPtr).B = b
    (*info.colorInfoPtr).name = info.ctname
    (*info.colorInfoPtr).index = info.index
    (*info.colorInfoPtr).type = info.colortabletype
    (*info.colorInfoPtr).reversed = reverseSet
    IF (*info.colorInfoPtr).type EQ 'BREWER' $
        THEN (*info.colorInfoPtr).brewer = 1 $
        ELSE (*info.colorInfoPtr).brewer = 0

       ; Don't bother with notification if this is just a color
       ; protection event.

    IF info.from EQ 'PROTECT' THEN RETURN

       ; Are there widgets to notify?

    s = SIZE(info.notifyID)
    IF s(0) EQ 1 THEN count = 0 ELSE count = s(2)-1
    FOR j=0,count DO BEGIN
       colorEvent = { XCOLORS_LOAD, $            ;
                      ID:info.notifyID(0,j), $   ;
                      TOP:info.notifyID(1,j), $
                      HANDLER:0L, $
                      R:r, $
                      G:g, $
                      B:b, $
                      index:info.index, $
                      name:info.ctname, $
                      type:info.colortabletype, $
                      brewer:info.brewer, $
                      reversed:info.reversed }
       IF Widget_Info(info.notifyID(0,j), /Valid_ID) THEN $
          Widget_Control, info.notifyID(0,j), Send_Event=colorEvent
    ENDFOR

       ; Is there an object to notify?

    nelements = SIZE(info.notifyobj, /N_Elements)
    FOR j=0,nelements-1 DO BEGIN
       IF Obj_Valid((info.notifyobj)[j].object) THEN BEGIN
          IF N_Elements(*info.xcolorsData) EQ 0 THEN BEGIN
             s = Size(*info.extra)
             IF s[s[0]+1] EQ 0 THEN BEGIN
                IF info.object_data $
                   THEN Call_Method, (info.notifyobj)[j].method, $
                         (info.notifyobj)[j].object, $
                         XCOLORS_DATA=*info.colorInfoPtr $
                   ELSE Call_Method, (info.notifyobj)[j].method, ($
                         info.notifyobj)[j].object
             ENDIF ELSE BEGIN
                IF info.object_data $
                   THEN Call_Method, (info.notifyobj)[j].method, $
                          (info.notifyobj)[j].object,_Strict_Extra=*info.extra, $
                          XCOLORS_DATA=*info.colorinfoptr $
                   ELSE Call_Method, (info.notifyobj)[j].method, $
                          (info.notifyobj)[j].object,_Strict_Extra=*info.extra
             ENDELSE
          ENDIF ELSE BEGIN
            s = Size(*info.extra)
            IF s[s[0]+1] EQ 0 THEN BEGIN
                IF info.object_data $
                   THEN Call_Method, (info.notifyobj)[j].method, $
                      (info.notifyobj)[j].object, DATA=*info.xcolorsData, $
                      XCOLORS_DATA=*info.colorinfoptr $
                   ELSE Call_Method, (info.notifyobj)[j].method, $
                      (info.notifyobj)[j].object, DATA=*info.xcolorsData
            ENDIF ELSE BEGIN
                IF info.object_data $
                   THEN Call_Method, (info.notifyobj)[j].method, $
                      (info.notifyobj)[j].object, DATA=*info.xcolorsData, $
                      _Strict_Extra=*info.extra, XCOLORS_DATA=*info.colorinfoptr  $
                   ELSE Call_Method, (info.notifyobj)[j].method, $
                      (info.notifyobj)[j].object, DATA=*info.xcolorsData, $
                      _Strict_Extra=*info.extra
            ENDELSE
          ENDELSE
       ENDIF
    ENDFOR

       ; Is there a procedure to notify?

    IF info.notifyPro NE "" THEN BEGIN
       IF N_Elements(*info.xcolorsData) EQ 0 THEN BEGIN
          s = Size(*info.extra)
          IF s[s[0]+1] EQ 0 THEN BEGIN
             Call_Procedure, info.notifyPro
          ENDIF ELSE BEGIN
             Call_Procedure, info.notifyPro, _Strict_Extra=*info.extra
          ENDELSE
       ENDIF ELSE BEGIN
          s = Size(*info.extra)
          IF s[s[0]+1] EQ 0 THEN BEGIN
             Call_Procedure, info.notifyPro, DATA=*info.xcolorsData
          ENDIF ELSE BEGIN
             Call_Procedure, info.notifyPro, DATA=*info.xcolorsData, _Strict_Extra=*info.extra
          ENDELSE
       ENDELSE
    ENDIF

END ; ***************************************************************



;+
; The event handler for the TOP slider. Modify the color table when
; the slider is moved.
;
; :Params:
;     event: in, required
;         The event structure passed from the Window Manager.
;-
PRO XCOLORS_TOP_SLIDER, event

       ; Get the info structure from storage location.

    Widget_Control, event.top, Get_UValue=info, /No_Copy

       ; Update the current top value of the slider.

    currentTop = event.value
    Widget_Control, info.botSlider, Get_Value=currentBottom
    currentBottom = currentBottom + info.bottom
    currentTop = currentTop + info.bottom

       ; Error handling. Is currentBottom = currentTop?

    IF currentBottom EQ currentTop THEN BEGIN
       currentBottom = (currentTop - 1) > 0
       thisValue = (currentBottom-info.bottom)
       IF thisValue LT 0 THEN BEGIN
          thisValue = 0
          currentBottom = info.bottom
       ENDIF
       Widget_Control, info.botSlider, Set_Value=thisValue
    ENDIF

       ; Error handling. Is currentBottom > currentTop?

    IF currentBottom GT currentTop THEN BEGIN

       bottom = currentTop
       top = currentBottom
       bottomcolor = info.topColor
       topcolor = info.bottomColor
       reversed = 1

    ENDIF ELSE BEGIN

       bottom = currentBottom
       top = currentTop
       bottomcolor = info.bottomColor
       topcolor = info.topColor
       reversed = 0

    ENDELSE

       ; Create a pseudo structure.

    pseudo = {currenttop:top, currentbottom:bottom, reversed:reversed, $
       bottomcolor:bottomcolor, topcolor:topcolor, gamma:info.gamma, index:info.index, $
       top:info.top, bottom:info.bottom, ncolors:info.ncolors, r:info.r, $
       g:info.g, b:info.b, notifyID:info.notifyID, colorimage:info.colorimage, $
       windowindex:info.windowindex, from:'TOP', notifyObj:info.notifyObj, extra:info.extra, $
       thisWindow:info.thisWindow, notifyPro:info.notifyPro, xcolorsData:info.xcolorsData, $
       colorInfoPtr:info.colorInfoPtr, colornames:info.colornames, ctname:info.ctname, $
       needColorInfo:info.needColorInfo, colortabletype:info.colortabletype, $
       object_data:info.object_data, reverseID:info.reverseID, brewer:info.brewer}

       ; Update the colors.

    XColors_Set, pseudo

    info.currentTop = currentTop

       ; Put the info structure back in storage location.

    Widget_Control, event.top, Set_UValue=info, /No_Copy

END ; ************************************************************************



;+
; The event handler for the BOTTOM slider. Modify the color table when
; the slider is moved.
;
; :Params:
;     event: in, required
;         The event structure passed from the Window Manager.
;-
PRO XCOLORS_BOTTOM_SLIDER, event

       ; Get the info structure from storage location.

    Widget_Control, event.top, Get_UValue=info, /No_Copy

       ; Update the current bottom value of the slider.

    currentBottom = event.value + info.bottom
    Widget_Control, info.topSlider, Get_Value=currentTop
    ;currentBottom = currentBottom + info.bottom
    currentTop = currentTop + info.bottom

       ; Error handling. Is currentBottom = currentTop?

    IF currentBottom EQ currentTop THEN BEGIN
       currentBottom = currentTop
       Widget_Control, info.botSlider, Set_Value=(currentBottom-info.bottom)
    ENDIF

       ; Error handling. Is currentBottom > currentTop?

    IF currentBottom GT currentTop THEN BEGIN

       bottom = currentTop
       top = currentBottom
       bottomcolor = info.topColor
       topcolor = info.bottomColor
       reversed = 1

    ENDIF ELSE BEGIN

       bottom = currentBottom
       top = currentTop
       bottomcolor = info.bottomColor
       topcolor = info.topColor
       reversed = 0

    ENDELSE

       ; Create a pseudo structure.

    pseudo = {currenttop:top, currentbottom:bottom, reversed:reversed, $
       bottomcolor:bottomcolor, topcolor:topcolor, gamma:info.gamma, index:info.index, $
       top:info.top, bottom:info.bottom, ncolors:info.ncolors, r:info.r, $
       g:info.g, b:info.b, notifyID:info.notifyID, colorimage:info.colorimage, $
       windowindex:info.windowindex, from:'BOTTOM', notifyObj:info.notifyObj, extra:info.extra, $
       thisWindow:info.thisWindow, notifyPro:info.notifyPro, xcolorsData:info.xcolorsData, $
       colorInfoPtr:info.colorInfoPtr, colornames:info.colornames, ctname:info.ctname, $
       needColorInfo:info.needColorInfo, colortabletype:info.colortabletype, $
       object_data:info.object_data, reverseID:info.reverseID, brewer:info.brewer}

       ; Update the colors.

    XColors_Set, pseudo

    info.currentBottom = currentBottom

       ; Put the info structure back in storage location.

    Widget_Control, event.top, Set_UValue=info, /No_Copy

END ; ************************************************************************




;+
; The event handler for the GAMMA slider. Modify the color table when
; the slider is moved.
;
; :Params:
;     event: in, required
;         The event structure passed from the Window Manager.
;-
PRO XCOLORS_GAMMA_SLIDER, event

       ; Get the info structure from storage location.

    Widget_Control, event.top, Get_UValue=info, /No_Copy

       ; Get the gamma value from the slider.

    Widget_Control, event.id, Get_Value=gamma
    gamma = 10^((gamma/50.0) - 1)

       ; Update the gamma label.

    Widget_Control, info.gammaID, Set_Value=String(gamma, Format='(F6.3)')

       ; Make a pseudo structure.

    IF info.currentBottom GT info.currentTop THEN $
       pseudo = {currenttop:info.currentbottom, currentbottom:info.currenttop, $
          reversed:1, bottomcolor:info.topcolor, topcolor:info.bottomcolor, $
          gamma:gamma, top:info.top, bottom:info.bottom, index:info.index, $
          ncolors:info.ncolors, r:info.r, g:info.g, b:info.b, $
          notifyID:info.notifyID, colorimage:info.colorimage, extra:info.extra, $
          windowindex:info.windowindex, from:'SLIDER', notifyObj:info.notifyObj, $
          thisWindow:info.thisWindow, notifyPro:info.notifyPro, xcolorsData:info.xcolorsData, $
          colorInfoPtr:info.colorInfoPtr, colornames:info.colornames, ctname:info.ctname, $
          needColorInfo:info.needColorInfo, colortabletype:info.colortabletype, $
          object_data:info.object_data, reverseID:info.reverseID, brewer:info.brewer} $
    ELSE $
       pseudo = {currenttop:info.currenttop, currentbottom:info.currentbottom, $
          reversed:0, bottomcolor:info.bottomcolor, topcolor:info.topcolor, $
          gamma:gamma, top:info.top, bottom:info.bottom, index:info.index, $
          ncolors:info.ncolors, r:info.r, g:info.g, b:info.b, $
          notifyID:info.notifyID, colorimage:info.colorimage, extra:info.extra, $
          windowindex:info.windowindex, from:'SLIDER', notifyObj:info.notifyObj, $
          thisWindow:info.thisWindow, notifyPro:info.notifyPro, xcolorsData:info.xcolorsData, $
          colorInfoPtr:info.colorInfoPtr, colornames:info.colornames, ctname:info.ctname, $
          needColorInfo:info.needColorInfo, colortabletype:info.colortabletype, $
          object_data:info.object_data, reverseID:info.reverseID, brewer:info.brewer}

       ; Load the colors.

    XColors_Set, pseudo

    info.gamma = gamma

       ; Put the info structure back in storage location.

    Widget_Control, event.top, Set_UValue=info, /No_Copy

END ; ************************************************************************



;+
; The event handler for the REVERSE button. Modify the color table when
; the button is selectd.
;
; :Params:
;     event: in, required
;         The event structure passed from the Window Manager.
;-
PRO XCOLORS_REVERSE_BUTTON, event

       ; Get the info structure from storage location.

    Widget_Control, event.top, Get_UValue=info, /No_Copy

    ; To get the initial colors correct. We have to load the colors as if they
    ; were not reversed.
    IF info.index GE 0 THEN BEGIN
    cgLoadCT, info.index, BREWER=info.brewer, RGB_TABLE=c
    info.r = c[*,0]
    info.g = c[*,1]
    info.b = c[*,2]
    ENDIF ELSE TVLCT, info.r, info.g, info.b

    ; Is the button set or not?
    buttonSet = Widget_Info(event.id, /BUTTON_SET)

    ; Make a pseudo structure.
    IF buttonSet THEN  BEGIN

        IF info.currentBottom GT info.currentTop THEN $
           pseudo = {currenttop:info.currentbottom, currentbottom:info.currenttop, $
              reversed:1, bottomcolor:info.topcolor, topcolor:info.bottomcolor, $
              gamma:info.gamma, top:info.top, bottom:info.bottom, index:info.index, $
              ncolors:info.ncolors, r:info.r, g:info.g, b:info.b, $
              notifyID:info.notifyID, colorimage:info.colorimage, extra:info.extra, $
              windowindex:info.windowindex, from:'SLIDER', notifyObj:info.notifyObj, $
              thisWindow:info.thisWindow, notifyPro:info.notifyPro, xcolorsData:info.xcolorsData, $
              colorInfoPtr:info.colorInfoPtr, colornames:info.colornames, ctname:info.ctname, $
              needColorInfo:info.needColorInfo, colortabletype:info.colortabletype, $
              object_data:info.object_data, reverseID:info.reverseID, brewer:info.brewer} $
        ELSE $
           pseudo = {currenttop:info.currenttop, currentbottom:info.currentbottom, $
              reversed:1, bottomcolor:info.bottomcolor, topcolor:info.topcolor, $
              gamma:info.gamma, top:info.top, bottom:info.bottom, index:info.index, $
              ncolors:info.ncolors, r:info.r, g:info.g, b:info.b, $
              notifyID:info.notifyID, colorimage:info.colorimage, extra:info.extra, $
              windowindex:info.windowindex, from:'SLIDER', notifyObj:info.notifyObj, $
              thisWindow:info.thisWindow, notifyPro:info.notifyPro, xcolorsData:info.xcolorsData, $
              colorInfoPtr:info.colorInfoPtr, colornames:info.colornames, ctname:info.ctname, $
              needColorInfo:info.needColorInfo, colortabletype:info.colortabletype, $
              object_data:info.object_data, reverseID:info.reverseID, brewer:info.brewer}

    ENDIF ELSE BEGIN

        IF info.currentBottom GT info.currentTop THEN $
           pseudo = {currenttop:info.currentbottom, currentbottom:info.currenttop, $
              reversed:0, bottomcolor:info.topcolor, topcolor:info.bottomcolor, $
              gamma:info.gamma, top:info.top, bottom:info.bottom, index:info.index, $
              ncolors:info.ncolors, r:info.r, g:info.g, b:info.b, $
              notifyID:info.notifyID, colorimage:info.colorimage, extra:info.extra, $
              windowindex:info.windowindex, from:'SLIDER', notifyObj:info.notifyObj, $
              thisWindow:info.thisWindow, notifyPro:info.notifyPro, xcolorsData:info.xcolorsData, $
              colorInfoPtr:info.colorInfoPtr, colornames:info.colornames, ctname:info.ctname, $
              needColorInfo:info.needColorInfo, colortabletype:info.colortabletype, $
              object_data:info.object_data, reverseID:info.reverseID, brewer:info.brewer} $
        ELSE $
           pseudo = {currenttop:info.currenttop, currentbottom:info.currentbottom, $
              reversed:0, bottomcolor:info.bottomcolor, topcolor:info.topcolor, $
              gamma:info.gamma, top:info.top, bottom:info.bottom, index:info.index, $
              ncolors:info.ncolors, r:info.r, g:info.g, b:info.b, $
              notifyID:info.notifyID, colorimage:info.colorimage, extra:info.extra, $
              windowindex:info.windowindex, from:'SLIDER', notifyObj:info.notifyObj, $
              thisWindow:info.thisWindow, notifyPro:info.notifyPro, xcolorsData:info.xcolorsData, $
              colorInfoPtr:info.colorInfoPtr, colornames:info.colornames, ctname:info.ctname, $
              needColorInfo:info.needColorInfo, colortabletype:info.colortabletype, $
              object_data:info.object_data, reverseID:info.reverseID, brewer:info.brewer}

    ENDELSE

       ; Load the colors.

    XColors_Set, pseudo

       ; Put the info structure back in storage location.

    Widget_Control, event.top, Set_UValue=info, /No_Copy

END ; ************************************************************************



;+
; The event handler for the color table selector. Modify the color table when
; the selector is clicked.
;
; :Params:
;     event: in, required
;         The event structure passed from the Window Manager.
;-
PRO XCOLORS_COLORTABLE, event

       ; Get the info structure from storage location.

    Widget_Control, event.top, Get_UValue=info, /No_Copy

    LoadCT, event.index, File=info.file, /Silent, $
       NColors=info.ncolors, Bottom=info.bottom

    TVLct, r, g, b, /Get
    info.r = r(info.bottom:info.top)
    info.g = g(info.bottom:info.top)
    info.b = b(info.bottom:info.top)
    info.topcolor = [r(info.top), g(info.top), b(info.top)]
    info.bottomcolor = [r(info.bottom), g(info.bottom), b(info.bottom)]

       ; Update the slider positions and values.

    IF 1 - info.nosliders THEN BEGIN
       Widget_Control, info.botSlider, Set_Value=0
       Widget_Control, info.topSlider, Set_Value=info.ncolors-1
       Widget_Control, info.gammaSlider, Set_Value=50
       Widget_Control, info.gammaID, Set_Value=String(1.0, Format='(F6.3)')
    ENDIF
    info.currentBottom = info.bottom
    info.currentTop = info.top
    info.gamma = 1.0
    info.index = event.index
    info.ctname = (*info.colornames)[event.index]

       ; Create a pseudo structure.

    pseudo = {currenttop:info.currenttop, currentbottom:info.currentbottom, $
       reversed:info.reversed, windowindex:info.windowindex, index:event.index, $
       bottomcolor:info.bottomcolor, topcolor:info.topcolor, gamma:info.gamma, $
       top:info.top, bottom:info.bottom, ncolors:info.ncolors, r:info.r, $
       g:info.g, b:info.b, notifyID:info.notifyID, colorimage:info.colorimage, $
       from:'LIST', notifyObj:info.notifyObj, thisWindow:info.thisWindow, $
       notifyPro:info.notifyPro, xcolorsData:info.xcolorsData, extra:info.extra, $
       colorInfoPtr:info.colorInfoPtr, colornames:info.colornames, ctname:info.ctname, $
       needColorInfo:info.needColorInfo, colortabletype:info.colortabletype, $
       object_data:info.object_data, reverseID:info.reverseID, brewer:info.brewer}

       ; Update the colors.

    XColors_Set, pseudo

       ; Put the info structure back in storage location.

    Widget_Control, event.top, Set_UValue=info, /No_Copy

END ; ************************************************************************



;+
; The event handler for updating the colors on a 24-bit display.
;
; :Params:
;     event: in, required
;         The event structure passed from the Window Manager.
;-
PRO XCOLORS_PROTECT_COLORS, event

       ; Get the info structure from storage location.

    Widget_Control, event.top, Get_UValue=info, /No_Copy

       ; Create a pseudo structure.

    pseudo = {currenttop:info.currenttop, currentbottom:info.currentbottom, $
       reversed:info.reversed, $
       bottomcolor:info.bottomcolor, topcolor:info.topcolor, gamma:info.gamma, $
       top:info.top, bottom:info.bottom, ncolors:info.ncolors, r:info.r, index:info.index, $
       g:info.g, b:info.b, notifyID:info.notifyID, colorimage:info.colorimage, $
       windowindex:info.windowindex, from:'PROTECT', notifyObj:info.notifyObj, extra:info.extra, $
       thisWindow:info.thisWindow, notifyPro:info.notifyPro, xcolorsData:info.xcolorsData, $
       colorInfoPtr:info.colorInfoPtr, colornames:info.colornames, ctname:info.ctname, $
       needColorInfo:info.needColorInfo, colortabletype:info.colortabletype, $
       object_data:info.object_data, reverseID:info.reverseID, brewer:info.brewer}

       ; Update the colors.

    XColors_Set, pseudo

       ; Put the info structure back in storage location.

    Widget_Control, event.top, Set_UValue=info, /No_Copy

END ; ************************************************************************



;+
; The event handler for the CANCEL button. Update to original color table.
;
; :Params:
;     event: in, required
;         The event structure passed from the Window Manager.
;-
PRO XCOLORS_CANCEL, event
    Widget_Control, event.top, Get_UValue=info, /No_Copy

       ; Update the colors.

    XColors_Set, info.cancelStruct
    Widget_Control, event.top, Set_UValue=info, /No_Copy
    Widget_Control, event.top, /Destroy
END ; ************************************************************************



;+
; The event handler for the QUIT button. Destroy the widget.
;
; :Params:
;     event: in, required
;         The event structure passed from the Window Manager.
;-
PRO XCOLORS_DISMISS, event
Widget_Control, event.top, /Destroy
END ; ************************************************************************



;+
; The event handler for the BREWER colors button. Switch between IDL
; and Brewer color tables.
;
; :Params:
;     event: in, required
;         The event structure passed from the Window Manager.
;-
PRO XCOLORS_SWITCH_COLORS, event

   Widget_Control, event.top, Get_UValue=info, /No_Copy
   Widget_Control, event.id, GET_UVALUE=colortypes
   thisType = colortypes[event.index]
   parent = Widget_Info(event.id, /PARENT)
   oldList = info.tableList

   CASE thisType OF


        'IDL': BEGIN
           info.file = Filepath(SubDir=['resource','colors'], 'colors1.tbl')
           info.brewer = 0
           END

        'USER-DEFINED': BEGIN
           info.file = info.userfile
           info.brewer = 0
           END

        'BREWER': BEGIN
           info.file = cgFindPathTo('fsc_brewer.tbl')
           info.brewer = 1
           END
   ENDCASE

   ; Load the appropriate colors and the new color table names into
   ; the list widget.
    colorNames=''
    LoadCT, Get_Names=colorNames, File=info.file
    colorNamesIndex = StrArr(N_Elements(colorNames))
    FOR j=0,N_Elements(colorNames)-1 DO $
       colorNamesIndex[j] = StrTrim(j,2) + ' - ' + colorNames[j]
    info.tableList = Widget_List(info.tableListBase, Value=colorNamesIndex, YSize=12 + (12*Keyword_Set(nosliders)), Scr_XSize=256, $
       Event_Pro='XColors_ColorTable')
    Widget_Control, oldList, /Destroy

    ; Update the color table type and names in the info structure.
    info.colorTableType = thisType
    *info.colornames = colorNames

    Widget_Control, event.top, Set_UValue=info, /No_Copy
END ; *****************************************************************



;+
; The cleanup routine for the widget program. Called when the widget
; dies.
;
; :Params:
;     tlb: in, required
;         The widget identifier of the widget that just died.
;-
PRO XCOLORS_CLEANUP, tlb
    Widget_Control, tlb, Get_UValue=info, /No_Copy
    IF N_Elements(info) NE 0 THEN BEGIN
       Ptr_Free, info.colornames
       Ptr_Free, info.xcolorsData
       Ptr_Free, info.extra
       IF info.needColorInfo EQ 0 THEN Ptr_Free, info.colorInfoPtr
    ENDIF
END ; ************************************************************************

;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; This is a procedure to load color tables into a restricted color range of the physical
; color table. It is a highly simplified, but much more powerful, version of XLoadCT.
;
; :Keywords:
;     block: in, optional, type=boolean, default=0
;        If this keyword is set, the program will try to block the
;        IDL command line. Note that this is only possible if no other
;        widget program is currently blocking the IDL command line. It
;        is much more reliable to make XCOLORS a modal widget (see the MODAL
;        keyword), although this can generally only be done when XCOLORS
;        is called from another widget program.
;     brewer: in, optional, type=boolean, default=0
;        Set this keyword if you wish to use the Brewer Colors, as explained
;        in this reference: http://www.idlcoyote.com/color_tips/brewer.html. The
;        file, `fsc_brewer.tbl <http://www.idlcoyote.com/programs/fsc_brewer.tbl>`
;        must be found somewhere in your IDL path for this option to be available.
;        Note that if this file is found, the Brewer colors are automatically added
;        to the program as an option. In this case, the BREWER keyword just makes sure
;        this is the initial user choice.
;     bottom: in, optional, type=integer, default=0
;        The lowest color index of the colors to be changed.
;     colorinfo: out, optional
;        This output keyword will return either a pointer to a color information structure
;        (if the program is called in a non-modal fashion) or a color information structure
;        (if the program is called in modal or blocking fashion). The color information
;         structure is an anonymous structure defined like this::
;
;             struct = { R: BytArr(!D.Table_Size), $ ; The current R color vector.
;                        G: BytArr(!D.Table_Size), $ ; The current G color vector.
;                        B: BytArr(!D.Table_Size), $ ; The current B color vector.
;                        NAME: "", $                 ; The name of the current color table.
;                        INDEX: 0, $                 ; The index number of the current color table.
;                        TYPE: "", $                 ; The type of color table (e.g, BREWER or IDL).
;                        BREWER: 0, $                ; Set to 1 if using BREWER color tables, else to 0.
;                        REVERSED: 0B }              ; Set to 1 if the color table is reversed.
;
;        If a pointer to the structure is obtained, you will be responsible
;        for freeing it to prevent memory leakage::
;
;             XColors, ColorInfo=colorInfoPtr
;             Print, "Color Table Name: ", (*colorInfoPtr).Name
;             Ptr_Free, colorInfoPtr
;
;        Note that that Name field will be "Unknown" and the Index field will
;        be -1 until a color table is actually selected by the user. You are
;        responsible for checking this value before you use it.
;
;        When called in modal or blocking fashion, you don't have to worry about freeing
;        the pointer, since no pointer is involved::
;
;             XColors, /Block, ColorInfo=colorInfoData
;             Help, colorInfoData, /Structure
;             Print, "Color Table Name: ", colorInfoData.Name
;     data: in, optional
;        This keyword can be set to any valid IDL variable. If
;        the variable is defined, the specified object method or notify
;        procedure will be passed this variable via a DATA keyword. This
;        keyword is defined primarily so that Notify Procedures are compatible
;        with the XLOADCT way of passing data. It is not strictly required,
;        since the _EXTRA keyword inheritance mechanism will allow passing
;        of *any* keyword parameter defined for the object or procedure that is
;        to be notified.
;     drag: in, optional, type=boolean, default=0
;        Set this keyword if you want colors loaded as you drag
;        the sliders. Default is to update colors only when you release
;        the sliders. Use of this keyword is greatly discouraged.
;     file: in, optional, type=string, default="colors1.tbl"
;        A name of a color table file. The supplied colors1.tbl file is used by default.
;     group_leader: in, optional, type=long
;        The group leader identifier for this program. When the group
;          leader is destroyed, this program will be destroyed.
;     index: in, optional, type=integer
;        The index of the color table to start up. If provided, a color
;        table of this index number is loaded prior to display. Otherwise,
;        the current color table is used. Set this keyword if you wish
;        to have the index number of the event structure correct when
;        the user CANCELs out of the progam.
;     modal: in, optional, type=boolean, default=0
;        Set this keyword (along with the GROUP_LEADER keyword) to
;        make the XCOLORS dialog a modal widget dialog. Note that NO
;        other events can occur until the XCOLORS program is destroyed
;        when in modal mode.
;     ncolors: in, optional, type=integer, default=256
;        Set this keyword to the number of colors to load when a color table
;        is selected.
;     nosliders: in, optional, type=boolean, default=0
;        If this keyword is set, the color stretch and color gamma
;        sliders are not displayed. This would be appropriate, for example,
;        for programs that just load pre-defined color tables.
;     notifyid: in, optional
;        A 2-column by n-row array that contains the IDs of widgets
;        that should be notified when XCOLORS loads a color table. The first
;        column of the array is the widgets that should be notified. The
;        second column contains IDs of widgets that are at the top of the
;        hierarchy in which the corresponding widgets in the first column
;        are located. (The purpose of the top widget IDs is to make it
;        possible for the widget in the first column to get the "info"
;        structure of the widget program.) An XCOLORS_LOAD event will be
;        sent to the widget identified in the first column. The event
;        structure is defined like this::
;
;           event = {XCOLORS_LOAD, ID:0L, TOP:0L, HANDLER:0L, $
;              R:BytArr(!D.TABLE_SIZE < 256), G:BytArr(!D.TABLE_SIZE < 256), $
;              B:BytArr(!D.TABLE_SIZE < 256), INDEX:0, NAME:"", $
;              TYPE:"", BREWER:0, REVERSED:0}
;
;        The ID field will be filled out with NOTIFYID[0, n] and the TOP
;        field will be filled out with NOTIFYID[1, n]. The R, G, and B
;        fields will have the current color table vectors, obtained by
;        exectuing the command TVLCT, r, g, b, /Get. The INDEX field will
;        have the index number of the just-loaded color table. The name
;        field will have the name of the currently loaded color table.
;        The TYPE field with be "BREWER" if a Brewer color table was loaded,
;        or "IDL" otherwise. The BREWER field will be set to 1 if a Brewer
;        color table was loaded, or to 0 otherwise. The REVERSED field will
;        be set to 1 if the color table is reversed, or to 0 otherwise.
;
;        Note that XCOLORS can't initially tell *which* color table is
;        loaded, since it just uses whatever colors are available when it
;        is called. Thus, it stores a -1 in the INDEX field to indicate
;        this "default" value. Programs that rely on the INDEX field of
;        the event structure should normally do nothing if the value is
;        set to -1. This value is also set to -1 if the user hits the
;        CANCEL button. (Note the NAME field will initially be "Unknown").
;
;        Typically the XCOLORS button will be defined like this::
;
;             xcolorsID = Widget_Button(parentID, Value='Load New Color Table...', $
;                Event_Pro='Program_Change_Colors_Event')
;
;        The event handler will be written something like this::
;
;             PRO Program_Change_Colors_Event, event
;
;                ; Handles color table loading events. Allows colors be to changed.
;
;             Widget_Control, event.top, Get_UValue=info, /No_Copy
;             thisEvent = Tag_Names(event, /Structure_Name)
;             CASE thisEvent OF
;
;                'WIDGET_BUTTON': BEGIN
;
;                     ; Color table tool.
;
;                   XColors, NColors=info.ncolors, Bottom=info.bottom, $
;                      Group_Leader=event.top, NotifyID=[event.id, event.top]
;                   ENDCASE
;
;                'XCOLORS_LOAD': BEGIN
;
;                     ; Update the display for 24-bit displays.
;
;                   Device, Get_Visual_Depth=thisDepth
;                   IF thisDepth GT 8 THEN BEGIN
;                   WSet, info.wid
;
;                    ...Whatever display commands are required go here. For example...
;
;                    TV, info.image
;
;                 ENDIF
;                 ENDCASE
;
;              ENDCASE
;
;              Widget_Control, event.top, Set_UValue=info, /No_Copy
;              END
;
;     notifyobj: in, optional, type=structure
;        A vector of structures (or a single structure), with each element of the vector
;        defined as follows::
;
;             struct = {XCOLORS_NOTIFYOBJ, object:Obj_New(), method:''}
;
;        where the Object field is an object reference, and the Method field
;        is the name of the object method that should be called when XCOLORS
;        loads its color tables::
;
;             ainfo = {XCOLORS_NOTIFYOBJ, a, 'Draw'}
;             binfo = {XCOLORS_NOTIFYOBJ, b, 'Display'}
;             XColors, NotifyObj=[ainfo, binfo]
;
;        Note that the XColors program must be compiled before these structures
;        are used. Alternatively, you can put this program, named
;        "xcolors_notifyobj__define.pro" (*three* underscore characters in this
;        name!) in your PATH::
;
;             PRO XCOLORS_NOTIFYOBJ__DEFINE
;              struct = {XCOLORS_NOTIFYOBJ, OBJECT:Obj_New(), METHOD:''}
;             END
;
;        Or, you can simply define this structure as it is shown here in your code.
;
;        "Extra" keywords added to the XCOLORS call are passed along to
;        the object method, which makes this an alternative way to get information
;        to your methods. If you expect such keywords, your methods should be defined
;        with an _Extra keyword.
;
;        If you set the /OBJECT_DATA keyword, the same structure defined for the
;        COLORINFO keyword above will be passed to your object method via an
;        XCOLORS_DATA keyword that you will have to define for the method.
;
;     notifypro: in, optional, type=string
;        The name of a procedure to notify or call when the color
;        tables are loaded. If the DATA keyword is also defined with a valid
;        IDL variable, it will be passed to this program via an DATA keyword.
;        But note that *any* keyword appropriate for the procedure can be used
;        in the call to XCOLORS. For example, here is a procedure that re-displays
;        an image in the current graphics window::
;
;             PRO REFRESH_IMAGE, Image=image, _Extra=extra, WID=wid
;             IF N_Elements(wid) NE 0 THEN WSet, wid
;             cgImage, image, _Extra=extra
;             END
;
;        This program can be invoked with this series of commands::
;
;             IDL> Window, /Free
;             IDL> cgImage, image, Position=[0.2, 0.2, 0.8, 0.8]
;             IDL> XColors, NotifyPro='Refresh_Image', Image=image, WID=!D.Window
;
;        Note that "extra" keywords added to the XCOLORS call are passed along to
;        your procedure, which makes this an alternative way to get information
;        to your procedure. If you expect such keywords, your procedure should
;        be defined with an _Extra keyword as illustrated above.
;     object_data: in, optional, type=boolean, default=0
;        Set this keyword if you wish color information to be
;        supplied to your object notification method via an XCOLORS_DATA
;        keyword. This keyword is ignored unless the NOTIFYOBJ keyword is
;        also used. The color information is supplied as a structure and is
;        defined in the COLORINFO keyword definition above.
;     reverse: in, optional, type=boolean, default=0
;        If this keyword is set, the color table is reversed and the
;        Reverse Color Table button is set on.
;     title: in, optional, type='string'
;        This is the window title. It is "Load Color Tables" by default. The program
;        is registered with the name 'XCOLORS:' plus the TITLE string. The "register
;        name" is checked before the widgets are defined. If a program with that name
;        has already been registered you cannot register another with that name. This
;        means that you can have several versions of XCOLORS open simultaneously as long
;        as each has a unique title or name. For example, like this::
;
;            IDL> XColors, NColors=100, Bottom=0, Title='First 100 Colors'
;            IDL> XColors, NColors=100, Bottom=100, Title='Second 100 Colors'
;     window: in, optional, type=boolean, default=0
;        Set this keyword to send the colors to a Coyote Graphics cgWindow program.
;     winid: in, optional, type=integer
;        The window index number of a Coyote Graphics cgWindow program to receive the color vectors.
;     xoffset: in, optional, type=integer
;        This is the X offset of the program on the display. The program will be placed
;        approximately in the middle of the display by default.
;     yoffset: in, optional, type=integer
;        This is the Y offset of the program on the display. The program will be placed
;        approximately in the middle of the display by default.
;     _extra: in, optional
;        The keyword inheritance mechanism will pick up and pass along to any method or procedure
;        to be notified any keywords that are defined for that procedure. Note that you should be
;        sure that keywords are spelled correctly. Any mis-spelled keyword will be ignored.
;
;---------------------------------------------------------------------------------------
PRO XCOLORS, $
    Block=block, $
    Brewer=brewer, $
    Bottom=bottom, $
    ColorInfo=colorinfoPtr, $
    Data=xColorsData, $
    Drag=drag, $
    File=file, $
    Group_Leader=group_leader, $
    Index=index, $
    Modal=modal, $
    NColors=ncolors, $
    NoSliders=nosliders, $
    NotifyID=notifyID, $
    NotifyObj=notifyObj, $
    NotifyPro=notifyPro, $
    Object_Data=object_data, $
    Reverse=reverse, $
    Title=title, $
    Window=window, $
    WinID=winID, $
    XOffset=xoffset, $
    YOffset=yoffset, $
    _EXTRA=extra


    On_Error, 1

       ; Current graphics window.

    thisWindow = !D.Window

       ; Check keyword parameters. Define defaults.

    IF N_Elements(title) EQ 0 THEN title = 'Load Color Tables'
    IF N_Elements(drag) EQ 0 THEN drag = 0

    IF N_Elements(file) EQ 0 THEN BEGIN
        file = Filepath(SubDir=['resource','colors'], 'colors1.tbl')
        userfile = ""
    ENDIF ELSE userfile = file

       ; Try to locate the brewer file. Check resource/colors directory, then look for it
       ; in the IDL path if it is not found there.

    brewerfile = Filepath(SubDir=['resource','colors'], 'fsc_brewer.tbl')
    IF File_Test(brewerfile, /READ) EQ 0 THEN brewerfile = cgFindPathTo('fsc_brewer.tbl')
    IF brewerfile EQ "" THEN BEGIN
        locatedBrewerFile = 0
    ENDIF ELSE BEGIN
        locatedBrewerFile = 1
        IF Keyword_Set(brewer) THEN file = brewerfile
    ENDELSE
    IF locatedBrewerFile AND Keyword_Set(brewer) $
       THEN colortabletype = 'BREWER' $
       ELSE IF userfile NE "" THEN colortabletype = 'USER-DEFINED' ELSE colortabletype = 'IDL'
    object_data = Keyword_Set(object_data)
    IF N_Elements(notifyID) EQ 0 THEN notifyID = [-1L, -1L]
    IF StrUpCase(colortabletype) EQ 'BREWER' THEN brewer = 1 ELSE brewer = 0

    ; Is the window keyword set? If so, you will be sending this to
    ; an FSC_Window to load the colors.
    IF Keyword_Set(window) THEN BEGIN

          ; Does a window object exist somewhere?
          DefSysV, '!FSC_WINDOW_LIST', EXISTS=exists
          IF exists THEN BEGIN
               theList = !FSC_WINDOW_LIST
               IF Obj_Valid(theList) THEN BEGIN
                    structs = theList -> Get_Item(/ALL, /DEREFERENCE)
                    IF Size(structs, /TNAME) EQ 'POINTER' THEN RETURN
                    IF N_Elements(winID) EQ 0 THEN BEGIN
                        winID = N_Elements(structs) - 1
                    ENDIF ELSE BEGIN
                        index = Where(structs.wid[*] EQ winID, count)
                        IF count GT 0 THEN winID = index[0] ELSE BEGIN
                            Message, 'Cannot find an FSC_Window with window index ' + StrTrim(winID, 2) + '.'
                        ENDELSE
                    ENDELSE
                    thisWindowStruct = structs[winID]
                    IF Obj_Valid(thisWindowStruct.windowObj) THEN BEGIN
                         thisStruct = {XCOLORS_NOTIFYOBJ, thisWindowStruct.windowObj, 'LoadColors'}
                         object_data = 1
                        IF N_Elements(notifyObj) EQ 0 THEN BEGIN
                           notifyObj = thisStruct
                        ENDIF ELSE BEGIN
                           notifyObj = [notifyObj, thisStruct]
                        ENDELSE
                    ENDIF
               ENDIF
           ENDIF
    ENDIF

    IF N_Elements(notifyObj) EQ 0 THEN BEGIN
       notifyObj = {object:Obj_New(), method:'', wid:-1}
    ENDIF
    IF Size(notifyObj, /Type) NE 8 THEN BEGIN
       ok = Dialog_Message(['Arguments to the NotifyObj keyword must', $
          'be structures. Returning...'])
       RETURN
    END
    nelements = Size(notifyObj, /N_Elements)
    FOR j=0,nelements-1 DO BEGIN
       tags = Tag_Names(notifyObj[j])
       check = Where(tags EQ 'OBJECT', count1)
       check = Where(tags EQ 'METHOD', count2)
       IF (count1 + count2) NE 2 THEN BEGIN
          ok = Dialog_Message('NotifyObj keyword has incorrect fields. Returning...')
       RETURN
       ENDIF
    ENDFOR
    IF N_Elements(notifyPro) EQ 0 THEN notifyPro = ""
    IF N_Elements(xcolorsData) EQ 0 THEN xdata = Ptr_New(/Allocate_Heap) ELSE $
       xdata = Ptr_New(xcolorsData)
    IF N_Elements(extra) EQ 0 THEN extra = Ptr_New(/Allocate_Heap) ELSE $
       extra = Ptr_New(extra)
    IF Arg_Present(colorinfoPtr) THEN needcolorInfo = 1 ELSE needcolorInfo = 0
    noblock = 1 - Keyword_Set(block)
    block = Keyword_Set(block)

       ; Find the center of the display.

    DEVICE, GET_SCREEN_SIZE=theScreenSize
    IF theScreenSize[0] GT 2000 THEN theScreenSize[0] = theScreenSize[0]/2
    xCenter = FIX(theScreenSize[0] / 2.0)
    yCenter = FIX(theScreenSize[1] / 2.0)

    IF N_ELEMENTS(xoffset) EQ 0 THEN xoffset = xCenter - 150
    IF N_ELEMENTS(yoffset) EQ 0 THEN yoffset = yCenter - 200

    registerName = 'XCOLORS:' + title

       ; Only one XCOLORS with this title.

    IF XRegistered(registerName) GT 0 THEN BEGIN
       Ptr_Free, xdata
       Ptr_Free, extra
       IF N_Elements(colorInfoPtr) NE 0 THEN Ptr_Free, colorInfoPtr
       RETURN
    ENDIF

       ; Create the top-level base. No resizing.

    IF Keyword_Set(modal) AND N_Elements(group_leader) NE 0 THEN BEGIN
       tlb = Widget_Base(Column=1, Title=title, TLB_Frame_Attr=1, $
          XOffSet=xoffset, YOffSet=yoffset, Base_Align_Center=1, $
          Modal=1, Group_Leader=group_leader)
       modal = 1

    ENDIF ELSE BEGIN
       tlb = Widget_Base(Column=1, Title=title, TLB_Frame_Attr=1, $
          XOffSet=xoffset, YOffSet=yoffset, Base_Align_Center=1, Space=5)
       modal = 0
       IF N_Elements(group_leader) EQ 0 THEN group_leader = -1L
    ENDELSE

       ; Create a draw widget to display the current colors.

       draw = Widget_Draw(tlb, XSize=256, YSize=40, Retain=2)

    IF N_Elements(bottom) EQ 0 THEN bottom = 0
    IF N_Elements(ncolors) EQ 0 THEN ncolors = (256 < !D.Table_Size) - bottom
    IF (ncolors + bottom) GT 256 THEN ncolors = 256 - bottom

       ; Load colors in INDEX if specified.
    IF userfile NE "" THEN file = userfile
    IF N_Elements(index) GE 1 THEN BEGIN
       IF index GE 0 THEN BEGIN
            LoadCT, index, File=file, /Silent, NColors=ncolors, Bottom=bottom
       ENDIF ELSE index = -1
    ENDIF ELSE index = -1


       ; Create a pointer to the color information.

    TVLCT, rr, gg, bb, /Get
    IF Keyword_Set(reverse) THEN BEGIN
       rr = Reverse(rr)
       gg = Reverse(gg)
       bb = Reverse(bb)
       TVLCT, rr, gg, bb
    ENDIF
    colorInfoPtr = Ptr_New({R:rr, G:gg, B:bb, Name:'Unknown', $
       Index:index, Type:colortabletype, Reversed:Keyword_Set(reverse), $
       Brewer:Keyword_Set(locatedBrewerFile)})

       ; Calculate top parameter.

    top = ncolors + bottom - 1

       ; Create sliders to control stretchs and gamma correction.

    IF 1 - Keyword_Set(nosliders) THEN BEGIN
       sliderbase = Widget_Base(tlb, Column=1, XPad=0, YPad=0, /Frame)
       botSlider = Widget_Slider(sliderbase, Value=0, Min=0, $
          Max=ncolors-1, XSize=256,Event_Pro='XColors_Bottom_Slider', $
          Title='Stretch Bottom', Drag=drag)
       topSlider = Widget_Slider(sliderbase, Value=ncolors-1, Min=0, $
          Max=ncolors-1, XSize=256, Event_Pro='XColors_Top_Slider', $
          Title='Stretch Top', Drag=drag)
       gammaID = Widget_Label(sliderbase, Value=String(1.0, Format='(F6.3)'))
       gammaSlider = Widget_Slider(sliderbase, Value=50.0, Min=0, Max=100, $
          Drag=drag, XSize=256, /Suppress_Value, Event_Pro='XColors_Gamma_Slider', $
          Title='Gamma Correction')
    ENDIF ELSE BEGIN
       botSlider = 0L
       topSlider = 0L
       gammaID = 0L
       gammaSlider = 0L
    ENDELSE

    ; A reverse button.
    cbase = WIDGET_BASE(tlb, Row=1, BASE_ALIGN_CENTER=1, FRAME=1, SCR_XSIZE=256)
    reverseBase = WIDGET_BASE(cbase, Row=1, XPAD=0, YPAD=0, SPACE=0, /NONEXCLUSIVE)
    reverseStr = StrUpCase(!Version.OS_Family) EQ 'WINDOWS' ? 'Reverse Color Table' : 'Reverse Colors'
    reverseID = Widget_Button(reversebase, Value=reverseStr, EVENT_PRO='XColors_Reverse_Button')
    IF Keyword_Set(reverse) THEN Widget_Control, reverseID, SET_BUTTON=1

       ; A row for additional control.
    IF locatedBrewerFile THEN BEGIN
        IF userfile NE "" THEN BEGIN
           colorvalues = [' User-Defined Colors ', ' Brewer Colors ']
           coloruvalue = ['USER-DEFINED', 'BREWER']
        ENDIF ELSE BEGIN
           colorvalues = [' IDL Colors ', ' Brewer Colors ']
           coloruvalue = ['IDL','BREWER']
        ENDELSE
        colorType = Widget_Droplist(cbase, Value=colorvalues, $
            /DYNAMIC_RESIZE, EVENT_PRO='XCOLORS_SWITCH_COLORS', UVALUE=coloruvalue)
        IF Keyword_Set(Brewer) THEN Widget_Control, colorType, Set_Droplist_Select=1
    ENDIF

       ; Get the colortable names for the list widget.

    colorNames=''
    LoadCT, Get_Names=colorNames, File=file
    IF index NE -1 THEN ctname = colorNames[index] ELSE ctname = 'Unknown'
    colorNamesIndex = StrArr(N_Elements(colorNames))
    FOR j=0,N_Elements(colorNames)-1 DO $
       colorNamesIndex[j] = StrTrim(j,2) + ' - ' + colorNames[j]
    tableListBase = Widget_Base(tlb, XPad=0, YPad=0)
    tablelist = Widget_List(tableListBase, Value=colorNamesIndex, YSize=12 + (12*Keyword_Set(nosliders)), $
        Scr_XSize=256, Event_Pro='XColors_ColorTable')

       ; Dialog Buttons
    dialogbase = WIDGET_BASE(tlb, Row=1, BASE_ALIGN_CENTER=1)
    cancel = Widget_Button(dialogbase, Value='Cancel', $
       Event_Pro='XColors_Cancel', UVALUE='CANCEL')
    dismiss = Widget_Button(dialogbase, Value='Accept', $
       Event_Pro='XColors_Dismiss', UVALUE='ACCEPT')

    Widget_Control, tlb, /Realize

       ; If you used INDEX, then position this color table near the top of the list.
    IF index NE -1 THEN BEGIN
       Widget_Control, tablelist, Set_List_Top=(0 > (index-3))
       Widget_Control, tablelist, Set_List_Select=index
    ENDIF

       ; Get window index number of the draw widget.

    Widget_Control, draw, Get_Value=windowIndex

       ; Put a picture of the color table in the window.

    bar = BINDGEN(ncolors) # REPLICATE(1B, 10)
    bar = BYTSCL(bar, TOP=ncolors-1) + bottom
    bar = XColors_Congrid(bar, 256, 40, /INTERP)
    WSet, windowIndex
    cgSetColorState, 0, Current=theState
    TV, bar
    Device, Decomposed=theState

       ; Get the colors that make up the current color table
       ; in the range that this program deals with.

    TVLCT, rr, gg, bb, /Get
    r = rr(bottom:top)
    g = gg(bottom:top)
    b = bb(bottom:top)

    topColor = [rr(top), gg(top), bb(top)]
    bottomColor = [rr(bottom), gg(bottom), bb(bottom)]
    colornames = Ptr_New(colornames)

       ; Create a cancel structure.

    cancelstruct = {currenttop:top, currentbottom:bottom, $
       reversed:Keyword_Set(reverse), windowindex:windowindex, $
       bottomcolor:bottomcolor, topcolor:topcolor, gamma:1.0, $
       top:top, bottom:bottom, ncolors:ncolors, r:r, $
       g:g, b:b, notifyID:notifyID, index:index, $
       colorimage:bar, from:'CANCEL', notifyObj:notifyObj, extra:extra, $
       thisWindow:thisWindow, notifyPro:notifyPro, xcolorsData:xData, $
       colorInfoPtr:colorInfoPtr, colornames:colornames, ctname:ctname, $
       needColorInfo:needColorInfo, colortabletype:colortabletype, $
       object_data:object_data, reverseID:reverseID, brewer:Keyword_Set(locatedBrewerFile)}


       ; Create an info structure to hold information to run the program.

    info = {  windowIndex:windowIndex, $         ; The WID of the draw widget.
              botSlider:botSlider, $             ; The widget ID of the bottom slider.
              currentBottom:bottom, $            ; The current bottom slider value.
              currentTop:top, $                  ; The current top slider value.
              topSlider:topSlider, $             ; The widget ID of the top slider.
              gammaSlider:gammaSlider, $         ; The widget ID of the gamma slider.
              gammaID:gammaID, $                 ; The widget ID of the gamma label
              ncolors:ncolors, $                 ; The number of colors we are using.
              gamma:1.0, $                       ; The current gamma value.
              file:file, $                       ; The name of the user-supplied or default color table file.
              userfile:userfile, $               ; The name, if any, of a user-supplied color table file.
              bottom:bottom, $                   ; The bottom color index.
              top:top, $                         ; The top color index.
              topcolor:topColor, $               ; The top color in this color table.
              bottomcolor:bottomColor, $         ; The bottom color in this color table.
              reversed:Keyword_Set(reverse), $   ; A reverse color table flag.
              reverseID:reverseID, $             ; The reverseID button.
              nosliders:Keyword_set(nosliders), $; A no slider flag.
              notifyID:notifyID, $               ; Notification widget IDs.
              notifyObj:notifyObj, $             ; An vector of structures containng info about objects to notify.
              notifyPro:notifyPro, $             ; The name of a procedure to notify.
              r:r, $                             ; The red color vector.
              g:g, $                             ; The green color vector.
              b:b, $                             ; The blue color vector.
              extra:extra, $                     ; A pointer to extra keywords.
              oindex:index, $                    ; The original color table number.
              index:index, $                     ; The current color table number.
              thisWindow:thisWindow, $           ; The current graphics window when this program is called.
              xcolorsData:xdata, $               ; A pointer to the xcolorData variable passed into the program.
              rstart:r, $                        ; The original red color vector.
              gstart:g, $                        ; The original green color vector.
              bstart:b, $                        ; The original blue color vector.
              colorInfoPtr:colorInfoPtr, $       ; A pointer to the color information.
              colornames:colornames, $           ; A pointer to the names of the color tables.
              ctname:ctname, $                   ; The current color table name.
              needColorInfo:needColorInfo, $     ; A flag that indicates color information is requested.
              cancelStruct:cancelStruct, $       ; The cancel structure.
              drag:drag, $                       ; Need additional information for switching to BREWER colors and visa versa.
              modal:modal, $
              brewer:brewer, $                   ; Using a brewer color table file.
              group_leader:group_leader, $
              block:block, $
              title:title, $
              tableListBase:tableListBase, $     ; The base holding the color table list.
              tableList:tableList, $
              object_data:object_data, $         ; Flag to indicate data should be sent with object notification.
              colortabletype:colortabletype, $   ; The type of color table, e.g, BREWER or IDL.
              colorimage:bar }                   ; The color table image.

       ; Turn color protection on.

    IF !D.NAME NE 'MAC' THEN Widget_Control, draw, Draw_Expose_Events=1

       ; Store the info structure in the user value of the top-level base.
    Widget_Control, tlb, Set_UValue=info, /No_Copy
    Widget_Control, tlb, /Managed
    WSet, thisWindow

    XManager, registerName, tlb, Group=(group_leader GE 0) ? group_leader : xx, No_Block=noblock, Cleanup="XColors_Cleanup"

       ; Return the colorInfo information as a structure if this program
       ; was called as a modal widget.
    IF (Keyword_Set(modal) AND N_Elements(group_leader) NE 0 AND needColorInfo) OR (noblock EQ 0 AND needColorInfo) THEN BEGIN
       colorStruct = *colorInfoPtr
       Ptr_Free, colorInfoPtr
       colorInfoPtr = colorStruct
    ENDIF

END
