;+
; NAME:
;   FSC_DROPLIST
;
; PURPOSE:
;
;   The purpose of this compound widget is to provide an alternative
;   to the DROPLIST widget offered in the IDL distribution. What has
;   always annoyed me about a droplist is that you can't get the current
;   "value" of a droplist easily. This compound widget makes this and
;   other tasks much easier.
;
; AUTHOR:
;
;   FANNING SOFTWARE CONSULTING
;   David Fanning, Ph.D.
;   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/
;
; CATEGORY:
;
;   General programming.
;
; CALLING SEQUENCE:
;
;   droplistObj = FSC_Droplist(parent, Title='Animals: ", Value=['Dog'. 'Cat', 'Coyote'], Index=2)
;
;   The return value of the FSC_Droplist (droplistObj in this example) is
;   an object reference. Interaction with the droplist will occur through
;   object methods.
;
; INPUT PARAMETERS:
;
;   parent -- The parent widget ID of the compound widget. Required.
;
; INPUT KEYWORDS:
;
; Any keyword that is appropriate for the Widget_Droplist function can be used.
; In addition, these keywords are explicitly defined.
;
;   EVENT_FUNC -- Set this keyword to the name of an Event Handler Function.
;   EVENT_PRO -- Set this keyword to the name of an Event Handler Procedure.
;   FORMAT -- A format specifier for the "format" of the values in the droplist.
;   INDEX -- The index number of the current selection.
;   SPACES -- A two-element array that indicates the number of blank spaces to be added
;             to the the beginning and end of the formatted values. If a single number
;             is provided, this number of blank spaces is added to both the beginning
;             and the end of the value.
;   TITLE -- The title of the droplist widget.
;   UNAME -- The user name of the droplist widget. (Only available in IDL 5.2 and higher.)
;   UVALUE -- The normal "user value" of the droplist.
;   VALUE -- An array of the droplist "selections". May be any data type.
;
; COMMON BLOCKS:
;
;   None.
;
; EVENT STRUCTURE:
;
;   An event is returned each time the droplist value is changed. The event structure
;   is defined like this:
;
;   event = { FSC_DROPLIST_EVENT, $ ; The name of the event structure.
;             ID: 0L, $             ; The ID of the compound widget's top-level base.
;             TOP: 0L, $            ; The widget ID of the top-level base of the hierarchy.
;             HANDLER: 0L, $        ; The event handler ID. Filled out by IDL.
;             INDEX: 0L, $          ; The index number of the current selection.
;             SELECTION:Ptr_New() $ ; A pointer to the current selection "value".
;             SELF:Obj_New() }      ; The object reference of the compound widget.
;
; PUBLIC OBJECT METHODS:
;
;   GetID -- A function with no arguments that returns the widget identifier
;      of the droplist widget.
;
;      droplistID = droplistObj->GetID()
;
;   GetIndex -- A function with no arguments that returns the index
;      number of the current droplist selection.
;
;      currentIndex = droplistObj->GetIndex()
;
;   GetSelection -- A function with no arguments that returns the current
;      droplist selection.
;
;      currentSelection = droplistObj->GetSelection()
;
;   GetUValue -- A function with no arguments that returns the "user value"
;      of the compound widget i.e., the value set with the UVALUE keyword).
;
;      myUValue = droplistObj->GetUValue()
;
;   GetValues -- A function with no arguments that returns the "values" or
;      "selections" for the droplist.
;
;      possibleSelections = droplistObj->GetValues()
;
;   Resize -- A procedure that sets the X screen size of the droplist. It is
;      defined like this:
;
;      PRO Resize, newSize, ParentSize=parentSize
;
;      The "newSize" keyword is the new X screen size. If this argument is
;      missing, the screen X size of the compound widget's parent is used.
;      The parentSize keyword is an output keyword that returns the X screen
;      size of the compound widget's parent.
;
;      droplistObj->Resize, 400
;
;      Note that not all devices (e.g., X Windows devices) support droplist resizing.
;
;   SetIndex -- A procedure that sets the current droplist selection based on
;      the given index. This is equivalent to Widget_Control, droplistID, Set_Droplist_Select=newIndex
;
;      droplistObj->SetIndex, newIndex
;
;   SetSelection -- Whereas a regular droplist widget can only be set by index
;      number, this compound widget can also be set by a "selection". The new selection
;      can be any data type and corresponds to one of the "values" of the droplist.
;
;      droplistObj->SetSelection, newSelection
;
;   SetValues -- Sets the possible selections of the droplist widget. The CurrentIndex keyword
;      will allow the current index of the selection to be changed to:
;
;      newChoices = ['dog', 'cat', 'coyote']
;      droplistObj->SetValues, newChoices, CurrentIndex=2
;
;
; EXAMPLE:
;
;   An example program is provided at the end of the FSC_DROPLIST code. To run it,
;   type these commands:
;
;      IDL> .Compile FSC_DROPLIST
;      IDL> Example
;
; MODIFICATION HISTORY:
;
;   Written by: David W Fanning, 17 Jan 2000. DWF.
;   Added FORMAT and SPACES keywords 28 April 2000. DWF.
;   Fixed a small problem with event processing when the EVENT_FUNC keyword
;      was used. 29 Dec 2000. DWF.
;   Attached the UNAME value to the TLB of the compound widget instead
;      of to the droplist widget itself. 11 Jan 2001. DWF.
;   Fixed a problem when the droplist was part of a modal widget and used the
;      EVENT_PRO keyword. 27 Oct 2003. DWF.
;   Added a SetValue method for setting all the values in the droplist at once. 12 Nov 2004. DWF.
;   Fixed type on line 346/ 6 Feb 2008. DWF.
;-
;******************************************************************************************;
;  Copyright (c) 2008, 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.                            ;
;******************************************************************************************;
PRO FSC_Droplist_Kill_Notify, droplistID

; This procedure is called automatically when the droplist is destroy. The
; purpose is to destroy the self object so there are no memory leaks.

Widget_Control,droplistID, Get_UValue=self
Obj_Destroy, self
END ;--------------------------------------------------------------------------


FUNCTION FSC_Droplist::EventHandler, event

; This is the event handler method of the object. The purpose is to set the
; object's current selection and index number. If an event handler has
; been specified, a FSC_DROPLIST event is sent to the proper event handler.

   ; Set the current selection and index number.

*self.selection = (*self.value)[event.index]
self.index = event.index

   ; If we need to send an event package the event up. Include both
   ; the index number (what normal droplist events produce), the current
   ; selection, and the self object reference.

thisEvent = {FSC_Droplist_EVENT, ID:self.tlb, TOP:event.top, HANDLER:event.handler, $
 INDEX:self.index, SELECTION:self.selection, SELF:self}

   ; Send the event if requested. If it is requested, no events returned.

IF self.event_pro NE "" THEN BEGIN
   Call_Procedure, self.event_pro, thisEvent
   thisEvent = 0
   IF Obj_Valid(self) EQ 0 THEN RETURN, thisEvent ; Modal widget destroyed.
ENDIF

IF self.event_func NE "" THEN BEGIN
   ok = Call_Function(self.event_func, thisEvent)
   thisEvent = 0
ENDIF

RETURN, thisEvent
END ;--------------------------------------------------------------------------



FUNCTION FSC_Droplist_Events, event

; This is the droplist event handler. The purpose of this procedure is to
; get the self object reference and call the object's own event method.
; The event handler method will return 0 if an event handler procedure or
; function has been called. Otherwise, it will return the event.

Widget_Control, event.id, Get_UValue=self
theEvent = self->EventHandler(event)
RETURN, theEvent
END ;--------------------------------------------------------------------------



PRO FSC_Droplist::Sensitive, value

; This method makes the droplist sensitive (value=1) or insensitive (value=0).

Widget_Control, self.droplistID, Sensitive=value
END ;--------------------------------------------------------------------------



PRO FSC_Droplist::Resize, newSize,  ParentSize=parentSize

; This method resizes the droplist. If the variable "newSize" is not provided,
; the new size is set by the parent widget's X screen size. This makes it possible
; to have the droplist sized to fit it's parent base widget.

   ; Get the parent widget's geometry and X screen size.

parentGeometry = Widget_Info(self.parent, /Geometry)
parentSize = parentGeometry.scr_xsize

   ; Has a size been provided? In not, use the parent's X screen size.

IF N_Elements(newSize) EQ 0 THEN BEGIN
   newSize = parentGeometry.scr_xsize
ENDIF

   ; Resize the droplist widget.

Widget_Control, self.droplistID, Scr_XSize=newSize
END ;--------------------------------------------------------------------------



PRO FSC_Droplist::SetSelection, selection

; This method sets the current selection of the droplist by means of
; the selection "value". With a normal droplist you must set the selection
; by specifying an index number.

   ; Find the selection in the value. Return it's index number.

IF N_Elements(selection) EQ 0 THEN selection = (*self.value)[0]
index = Where(StrUpCase( Strtrim(*self.value,2) ) EQ StrUpCase( StrTrim(selection,2) ), count)

   ; If you can't find the selection, print an error message.

IF count EQ 0 THEN BEGIN
   uname = Widget_Info(self.droplistID,/UName)
   ok = cgErrorMsg('No item with name "' + uname + ':' + $
      StrTrim(selection,2) + '" exists. Returning...')
   RETURN
ENDIF

   ; Set the current index and selection for the object.

index = 0 > index < (N_Elements(*self.value) - 1)
self.index = index
*self.selection = selection

   ; Set the current selection for the droplist widget.

Widget_Control, self.droplistID, Set_Droplist_Select=self.index
END ;--------------------------------------------------------------------------



PRO FSC_Droplist::SetIndex, index

; This method sets the current selection by means of its index number.

   ; Make sure the index number is there and valid.

IF N_Elements(index) EQ 0 THEN index = 0
self.index = 0 > index < (N_Elements(*self.value) - 1)

   ; Set the current selection.

*self.selection = (*self.value)[index]

   ; Set the selection on the droplist widget.

Widget_Control, self.droplistID, Set_Droplist_Select=self.index

END ;--------------------------------------------------------------------------



PRO FSC_Droplist::SetValues, theValues, CurrentIndex=currentIndex

; This method allows all the values in the droplist to be changed simultaneously.

  IF Ptr_Valid(self.value) THEN *self.value = theValues ELSE self.value = Ptr_New(theValues)
  Widget_Control, self.droplistID, Set_Value=*self.value

  IF N_Elements(currentIndex) EQ 0 THEN currentIndex = self.index ELSE self.index = currentIndex < (N_Elements(theValues)-1)
  IF currentIndex GT (N_Elements(*self.value)-1) THEN BEGIN
     currentIndex = 0
     self.index = currentIndex
  ENDIF
  IF Ptr_Valid(self.selection) THEN *self.selection = (*self.value)[self.index] ELSE $
    self.selection = Ptr_New((*self.value)[self.index])
  Widget_Control, self.droplistID, Set_Droplist_Select=self.index
END ;--------------------------------------------------------------------------



FUNCTION FSC_Droplist::GetIndex

; This method returns the index number of the current selection.

RETURN, self.index
END ;--------------------------------------------------------------------------



FUNCTION FSC_Droplist::GetSelection

; This method returns the "value" of the current selection.

RETURN, *self.selection
END ;--------------------------------------------------------------------------



FUNCTION FSC_Droplist::GetValues

; This method returns the current "values" or "selections" of the droplist.

RETURN, *self.value
END ;--------------------------------------------------------------------------



FUNCTION FSC_Droplist::GetUValue

; This method returns the "user value" of the droplist.

RETURN, *self.storage
END ;--------------------------------------------------------------------------



FUNCTION FSC_Droplist::GetID

; This method returns the droplist widget identifier.

RETURN, self.droplistID
END ;--------------------------------------------------------------------------



PRO FSC_Droplist::CLEANUP

; This is the object's cleanup method. Free's up all the pointers used in the
; object.

Ptr_Free, self.value
Ptr_Free, self.selection
Ptr_Free, self.storage
END ;--------------------------------------------------------------------------



FUNCTION FSC_Droplist::INIT, $
   parent, $                    ; The widget ID of the droplist's parent widget. Required.
   debug=debug, $               ; Turns full error handling on for INIT catch error handler.
   Event_Func=event_func, $     ; The name of an event handler function.
   Event_Pro=event_pro, $       ; The name of an event handler procedure.
   _Extra=extra, $              ; A keyword that allows any droplist keyword to be used in initialization.
   Format=format, $             ; The format of the "value" of the droplist.
   Index=index, $               ; The index number of the current selection.
   Spaces=spaces, $             ; Number of blank spaces to add to formatted "values".
   Title=title, $               ; The text that goes in the TITLE of the droplist.
   UName=uname, $               ; A user name. Left for the user of the cw_droplist program.
   UValue=storage, $            ; A user value. Left for the user of the cw_droplist program.
   Value=value                  ; The "value" or selections of the droplist. May be any data type.

   ; Catch any errors.

Catch, theError
IF theError NE 0 THEN BEGIN
   Catch, /Cancel
   ok = cgErrorMsg(!Error_State.Msg, Traceback=Keyword_Set(debug))
   RETURN, 0
ENDIF

; This is the object's initialization method.

IF N_Elements(parent) EQ 0 THEN BEGIN
   ok = cgErrorMsg('Parent parameter must be provided. Returning...')
   RETURN, 0
ENDIF

   ; Check for presence of keywords.

IF N_Elements(title) EQ 0 THEN title = 'Selection: '
IF N_Elements(value) EQ 0 THEN value = ['Dog', 'Cat', 'Coyote']
IF N_Elements(storage) EQ 0 THEN storage = ""
IF N_Elements(index) EQ 0 THEN index = 0
IF N_Elements(event_pro) EQ 0 THEN event_pro = ""
IF N_Elements(event_func) EQ 0 THEN event_func = ""
IF N_Elements(uname) EQ 0 THEN uname = ""
IF N_Elements(spaces) EQ 0 THEN BEGIN
   forwardSpace = ""
   trailingSpace = ""
ENDIF ELSE BEGIN
   IF N_Elements(spaces) EQ 2 THEN BEGIN
      IF spaces[0] LE 0 THEN forwardSpace = "" ELSE forwardSpace = String(Replicate(32B,spaces[0]))
      IF spaces[1] LE 0 THEN trailingSpace = "" ELSE trailingSpace = String(Replicate(32B,spaces[1]))
   ENDIF ELSE BEGIN
      IF spaces[0] LE 0 THEN forwardSpace = "" ELSE forwardSpace = String(Replicate(32B,spaces[0]))
      IF spaces[0] LE 0 THEN trailingSpace = "" ELSE trailingSpace = String(Replicate(32B,spaces[0]))
   ENDELSE
ENDELSE
index = 0 > index < (N_Elements(value) - 1)

   ; Create the droplist widget. UName only available for IDL 5.2 and higher.

IF Float(!Version.Release) GE 5.2 THEN BEGIN

   IF Keyword_Set(format) THEN theValue = String(value, Format=format) ELSE theValue = StrTrim(value,2)
   self.tlb = Widget_Base(parent, UValue=storage, UName=uname)
   self.droplistID = Widget_Droplist(self.tlb, $
         Title=title, $
         Value=forwardSpace + theValue + trailingSpace, $
         _Extra=extra, $
         Kill_Notify='FSC_Droplist_Kill_Notify', $
         Event_Func='FSC_Droplist_Events', $
         UValue=self)

 ENDIF ELSE BEGIN

   IF Keyword_Set(format) THEN theValue = String(value, Format=format) ELSE theValue = StrTrim(value,2)
   self.tlb = Widget_Base(parent, UValue=storage)
   self.droplistID = Widget_Droplist(self.tlb, $
         Title=title, $
         Value=forwardSpace + theValue + trailingSpace, $
         _Extra=extra, $
         Kill_Notify='FSC_Droplist_Kill_Notify', $
         Event_Func='FSC_Droplist_Events', $
         UValue=self)

ENDELSE

   ; Set the current selection on the droplist.

selection = Value[index]
Widget_Control, self.droplistID, Set_Droplist_Select=index

   ; Populate the object.

self.storage = Ptr_New(storage)
self.index = index
self.title = title
self.parent = parent
self.selection = Ptr_New(selection)
self.event_func = event_func
self.event_pro = event_pro
self.value = Ptr_New(value)

RETURN, 1
END ;--------------------------------------------------------------------------



PRO FSC_Droplist__DEFINE

   struct = { FSC_Droplist, $          ; The FSC_DROPLIST object class definition.
              title:"", $              ; The droplist title.
              value:Ptr_New(), $       ; The values or selections on the droplist.
              parent:0L, $             ; The widget identifer of the parent widget.
              tlb:0L, $                ; The top-level base of the compound widget. .
              droplistID:0L, $         ; The droplist widget identifier.
              selection:Ptr_New(), $   ; The current droplist selection.
              storage:Ptr_New(), $     ; The storage locaton for the "user value".
              event_pro:"", $          ; The name of an event handler procedure.
              event_func:"", $         ; The name of an event handler function.
              index:0 $                ; The index number of the current selecton.
            }
END ;--------------------------------------------------------------------------



FUNCTION FSC_Droplist, parent, Title=title, Value=value, _Extra=extra

   ; This is the compound widget function call. It's purpose is to
   ; create the compound widget object and return it.

RETURN, Obj_New('FSC_Droplist', parent, Title=title, Value=value, _Extra=extra)
END ;--------------------------------------------------------------------------



PRO Example_Events, event
Widget_Control, event.top, Get_UValue=droplists
thisEvent = Tag_Names(event, /Structure_Name)
CASE thisEvent OF
   'FSC_DROPLIST_EVENT': BEGIN
      thisDroplist = event.self->GetUValue()
      Print, ""
      Print, thisDroplist + ' Selection: ', *event.selection
      Print, thisDroplist + ' Index Number: ', event.index
      ENDCASE
   'WIDGET_BUTTON': BEGIN
      Widget_Control, event.id, Get_UValue=thisValue
      CASE thisValue OF ; Set the droplists by "selection".
         'SET_DROPLIST1': droplists[0]->SetSelection, 'Coyote'
         'SET_DROPLIST2': droplists[1]->SetSelection, 99.8
         ELSE: Widget_Control, event.top, /Destroy
      ENDCASE
      ENDCASE
ENDCASE
END ;---------------------------------------------------------------------------


PRO Example

; Droplist 1 is filled with string selections.
; Droplist 2 is filled with number selections.

tlb = Widget_Base(Title='FSC_Droplist Example', Column=1)
animals = ['Dog', 'Cat', 'Opposum', 'Coyote']
numbers = [45.6, 18.3, 21.5, 99.8]

   ; Create the droplists.

droplist1 = FSC_Droplist(tlb, Value=animals, Index=3, UValue='DROPLIST 1')
droplist2 = FSC_Droplist(tlb, Value=numbers, Index=1, UValue='DROPLIST 2', $
   Format='(F5.2)', Spaces=[2,0], Debug=1)

   ; Other widgets.

button = Widget_Button(tlb, Value='Set Droplist 1 to "Coyote"', UValue='SET_DROPLIST1')
button = Widget_Button(tlb, Value='Set Droplist 2 to 99.8', UValue='SET_DROPLIST2')
button = Widget_Button(tlb, Value='Quit', UValue='QUIT')

   ; Resize the droplists.

droplist1->Resize, 200
droplist2->Resize, 200

Widget_Control, tlb, /Realize, Set_UValue=[droplist1, droplist2]
XManager, 'Example', tlb, Event_Handler='Example_Events', /No_Block

; Test of SetValues method:
;      Wait, 2
;      newChoices = ['dog', 'cat', 'coyote']
;      droplist1->SetValues, newChoices, CurrentIndex=2

END ;---------------------------------------------------------------------------
