;
; NOSA HEADER START
;
; The contents of this file are subject to the terms of the NASA Open 
; Source Agreement (NOSA), Version 1.3 only (the "Agreement").  You may 
; not use this file except in compliance with the Agreement.
;
; You can obtain a copy of the agreement at
;   docs/NASA_Open_Source_Agreement_1.3.txt
; or 
;   https://cdaweb.gsfc.nasa.gov/WebServices/NASA_Open_Source_Agreement_1.3.txt.
;
; See the Agreement for the specific language governing permissions
; and limitations under the Agreement.
;
; When distributing Covered Code, include this NOSA HEADER in each
; file and include the Agreement file at 
; docs/NASA_Open_Source_Agreement_1.3.txt.  If applicable, add the 
; following below this NOSA HEADER, with the fields enclosed by 
; brackets "[]" replaced with your own identifying information: 
; Portions Copyright [yyyy] [name of copyright owner]
;
; NOSA HEADER END
;
; Copyright (c) 2010-2021 United States Government as represented by the
; National Aeronautics and Space Administration. No copyright is claimed
; in the United States under Title 17, U.S.Code. All Other Rights 
; Reserved.
;
;


;+
; This program provides a GUI for choosing datasets from 
; <a href="https://cdaweb.gsfc.nasa.gov/">CDAWeb</a>.
; 
; @copyright Copyright (c) 2010-2020 United States Government as 
;     represented by the National Aeronautics and Space Administration.
;     No copyright is claimed in the United States under Title 17,
;     U.S.Code. All Other Rights Reserved.
;
; @author B. Harris
;-


pro twinscolorbar
end

function IDLFFDICOMEXISLICENSED ; stub for idl 8.8
    return, 0
end


;+
; Selects a dataview.
;
; @param tlb {in} {type=int}
;            widget id of top level base.
; @param dvList {in} {type=int}
;            widget id of dataview list widget.
; @param dvIndex {in} {type=int}
;            index of dataview to select
;-
pro spdfSelectDataview, $
    tlb, dvList, dvIndex
    compile_opt idl2

    widget_control, dvList, set_combobox_select=dvIndex
    widget_control, dvList, $
        send_event={id:dvList, top:tlb, handler:dvList, index:dvIndex}
end


;+
; Responds to a "dataview selected" event.
;
; @param event {in} {type=widget event}
;            widget_list event.
;-
pro spdfDataviewSelected, $
    event
    compile_opt idl2

    widget_control, event.top, get_uvalue=state

    *state.selectedDataview = state.dataviews[event.index]->getId()

    if *state.selectedDataview eq 'cnofs' then begin

        reply = dialog_message( $
                    ['The required authentication for this dataview is not yet implemented.', 'Please choose a different dataview.'], $
                    title='Dataview Selection', /center, /information)

        spdfSelectDataview, event.top, state.dataviewList, 0

        return
    endif

    widget_control, /hourglass

    observatoryGroups = state.cdas->getObservatoryGroups($
        dataview = *state.selectedDataview, $
        authenticator=state.authenticator, $
        httpErrorReporter=state.errorDialog)

    *state.observatoryGroups = strarr(n_elements(observatoryGroups))

    for i = 0, n_elements(observatoryGroups) - 1 do begin

        (*state.observatoryGroups)[i] = observatoryGroups[i]->getName()
    endfor

    obj_destroy, observatoryGroups

    widget_control, state.observatoryGroupList, $
        set_value=*state.observatoryGroups


    instrumentTypes = state.cdas->getInstrumentTypes($
        dataview = *state.selectedDataview, $
        authenticator=state.authenticator, $
        httpErrorReporter=state.errorDialog)

    *state.instrumentTypes = strarr(n_elements(instrumentTypes))

    for i = 0, n_elements(instrumentTypes) - 1 do begin

        (*state.instrumentTypes)[i] = instrumentTypes[i]->getName()
    endfor

    obj_destroy, instrumentTypes

    widget_control, state.instrumentTypeList, $
        set_value=*state.instrumentTypes

end


;+
; Finds the specified dataset description within the given array of
; descriptions.
;
; @private
;
; @param descriptions {in} {type=SpdfDatasetDescription}
;            Array of descriptions to search (for example, 
;            state.datasets).
; @param id {in} {type=string}
;            Dataset identifier.
; @returns Dataset description from descriptions with the specified id
;              or a NULL object reference if the description was not
;              found.
;-
function spdfGetDatasetDescription, $
    descriptions, id
    compile_opt idl2

    for i = 0, n_elements(descriptions) - 1 do begin

        if descriptions[i]->getId() eq id then return, descriptions[i]
    endfor

    return, obj_new()
end


;+
; Responds to a dataset tree event.
;
; @param event {in} {type=widget event}
;            event triggering the execution of this procedure
;-
pro spdfDatasetTreeEvent, $
    event
    compile_opt idl2

    ;
    ; Note: In general, the selected item's tree index cannot be used
    ; to locate the corresponding SpdfDatasetDescription in 
    ; state.datasets because "pointer datasets" are not in the tree but
    ; are in state.datasets and the tree may be sorted differently.
    ;
    widget_control, event.top, get_uvalue=state

    selectedItem = widget_info(state.datasetTree, /tree_select)

    if selectedItem[0] eq -1 then return

    widget_control, selectedItem[0], get_uvalue=selectedValue

    ; Only dataset tree values are of type 7 (string) so ignore 
    ; all others

    if size(selectedValue, /type) ne 7 then return

    *state.selectedDataset = $
        spdfGetDatasetDescription(*state.datasets, selectedValue)

    if tag_names(event, /structure_name) eq 'WIDGET_CONTEXT' then begin

        widget_displaycontextmenu, event.ID, event.X, event.Y, $
            state.datasetContextMenu
    end
end


;+
; Responds to a request to view a dataset's notes.
;
; @param event {in} {type=widget event}
;            event triggering the execution of this procedure
;-
pro spdfViewNotes, $
    event
    compile_opt idl2

    widget_control, event.top, get_uvalue=state

    selectedDataset = *state.selectedDataset

    fullNotesUrl = selectedDataset->getNotes()

    spdfShowAsOnlineHelp, fullNotesUrl
end


;+
; Responds to a request to view a dataset's DOI/SPASE description.
;
; @param event {in} {type=widget event}
;            event triggering the execution of this procedure
;-
pro spdfViewDoi, $
    event
    compile_opt idl2

    widget_control, event.top, get_uvalue=state

    selectedDataset = *state.selectedDataset

    resourceId = selectedDataset->getResourceId()

    if strlen(resourceId) gt 0 then begin

        url = 'https://hpde.io' + strmid(resourceId, 7) + '.html'

        spdfShowAsOnlineHelp, url
    endif else begin

        msg = 'There is no DOI/SPASE information available for ' + $
              selectedDataset->getId()

        reply = dialog_message(msg, title='No Information Available', $
                    /center, /information)
    endelse
    
end


;+
; Displays the given URL using the IDL ONLINE_HELP procedure.  Refer to
; the ONLINE_HELP documentation for the types of files that are supported.
;
; @param url {in} {type=string}
;     URL of file to download and display with ONLINE_HELP.  This url
;     may contain an HTML fragment identifier.
;-
pro spdfShowAsOnlineHelp, $
    url
    compile_opt idl2

    urlComponents = $
        strsplit(url, '#', count=componentCount, /extract)

    urlWithoutFrag = urlComponents[0]

    if componentCount eq 2 then begin

        fragId = urlComponents[1]
    endif

    pathComponents = $
        strsplit(urlWithoutFrag, '/', count=componentCount, /extract)

    filename = pathComponents[componentCount - 1]

    proxySettings = obj_new('SpdfHttpProxy')

    notes = obj_new('IDLnetURL', $
                    proxy_authentication = $
                        proxySettings.getAuthentication(), $
                    proxy_hostname = proxySettings.getHostname(), $
                    proxy_port = proxySettings.getPort(), $
                    proxy_username = proxySettings.getUsername(), $
                    proxy_password = proxySettings.getPassword())

    localNotes = notes->get(filename=filename, url=urlWithoutFrag)

; save filename so it can be deleted when this program Exits

    if n_elements(fragId) ne 0 then begin

        online_help, fragId, book=localNotes, /full_path
    endif else begin

        online_help, book=localNotes, /full_path
    endelse

end


;+
; Responds to an inventory window close event.
;
; @param event {in} {type=widget_button}
;            event triggering the execution of this procedure.
;-
pro spdfCloseInventory, $
    event
    compile_opt idl2

    widget_control, event.top, /destroy
end


;+
; Responds to a request to view a dataset's inventory.
;
; @param event {in} {type=widget event}
;            event triggering the execution of this procedure
;-
pro spdfViewInventory, $
    event

    widget_control, event.top, get_uvalue=state

    selectedDataset = *state.selectedDataset

    inventory = state.cdas->getInventory( $
                    dataview = *state.selectedDataview, $
                    selectedDataset->getId(), $
                    authenticator=state.authenticator, $
                    httpErrorReporter=state.errorDialog)

    intervals = inventory->getTimeIntervals()

    intervalStrings = strarr(2, n_elements(intervals))

    for i = 0, n_elements(intervals) - 1 do begin

        intervalStrings[0, i] = intervals[i]->getCdawebStart()
        intervalStrings[1, i] = intervals[i]->getCdawebStop()
    endfor

    title = inventory->getId() + ' Data Inventory'

    inventoryWin = $
        widget_base(title=title, /column, group_leader=event.top)

    label = widget_label(inventoryWin, value=title);

    table = widget_table(inventoryWin, $
                column_labels = ['Start Time', 'Stop Time'], $
                /no_row_headers, value=intervalStrings)

    closeButton = widget_button(inventoryWin, /align_left, $
                      event_pro='spdfCloseInventory', $
                      value='Close', tooltip='Close inventory window')

    widget_control, inventoryWin, /realize

    xmanager, 'SpdfViewInventory', inventoryWin, /no_block

    obj_destroy, intervals
    obj_destroy, inventory
end


;+
; Responds to a request to set a default time value.
;
; @param event {in} {type=widget event}
;            event triggering the execution of this procedure
;-
pro spdfDefaultTime, $
    event
    compile_opt idl2

    widget_control, event.top, get_uvalue=state

    selectedItem = widget_info(state.datasetTree, /tree_select)

    if selectedItem[0] eq -1 then begin

        reply = dialog_message( $
                    'Please choose a Dataset/Variable', $
                    title='Missing Dataset Selection', /center, /error)
        return
    endif

    if ~widget_info(selectedItem[0], /tree_folder) then begin

        datasetWidget = widget_info(selectedItem[0], /parent)
    endif else begin

        datasetWidget = selectedItem[0]
    endelse

    widget_control, datasetWidget, get_uvalue=datasetId

    selectedDataset = $
        spdfGetDatasetDescription(*state.datasets, datasetId)

    timeInterval = selectedDataset->getTimeInterval()

    timeInterval->setStart, timeInterval->getStop() - 1

    widget_control, state.startTime, $
        set_value = timeInterval->getCdawebStart()
    widget_control, state.stopTime, $
        set_value = timeInterval->getCdawebStop()
end


;+
; Responds to an event by doing nothing (ignoring it).
;
; @param event {in} {type=widget event}
;            event triggering the execution of this procedure.
;-
pro spdfIgnoreSelected, $
    event
    compile_opt idl2

end


;+
; Responds to a "save data" button event.
;
; @param event {in} {type=widget event}
;            event triggering the execution of this function.
; @returns 0
;-
function spdfSaveDataButton, $
    event
    compile_opt idl2

    widget_control, event.top, get_uvalue=state

    *state.saveData = event.select

    return, 0
end


;+
; Responds to an event that initiates a search for datasets that
; satisfies the users previous selections.
;
; @param event {in} {type=widget_button}
;            event triggering the execution of this procedure.
;-
pro spdfFindDatasets, $
    event
    compile_opt idl2

    widget_control, event.top, get_uvalue=state

    selectedObservatoryGroups = $
        widget_info(state.observatoryGroupList, /list_select)

    if selectedObservatoryGroups[0] eq -1 then begin

        reply = dialog_message( $
                    'Please choose one or more Mission Groups', $
                    title='Mission Selection', /center, /error)
        return
    endif

    observatoryGroups = ptr_new()

    if selectedObservatoryGroups[0] ne -1 then begin

        observatoryGroups = strarr(n_elements(selectedObservatoryGroups))

        for i = 0, n_elements(observatoryGroups) - 1 do begin

            observatoryGroups[i] = $
                (*state.observatoryGroups)[selectedObservatoryGroups[i]]
        endfor
    endif

    selectedInstrumentTypes = $
        widget_info(state.instrumentTypeList, /list_select)

    if selectedInstrumentTypes[0] eq -1 then begin

        reply = dialog_message( $
                    'Please choose one or more Instrument Types', $
                    title='Mission Selection', /center, /error)
        return
    endif

    instrumentTypes = ptr_new()

    if selectedInstrumentTypes[0] ne -1 then begin

        instrumentTypes = strarr(n_elements(selectedInstrumentTypes))

        for i = 0, n_elements(instrumentTypes) - 1 do begin

            instrumentTypes[i] = $
                (*state.instrumentTypes)[selectedInstrumentTypes[i]]
        endfor
    endif

    oldRootNode = widget_info(state.datasetTree, /child)

    if oldRootNode ne 0 then widget_control, oldRootNode, /destroy

    if n_elements(*state.datasets) then begin

        *state.selectedDataset = obj_new()
        obj_destroy, *state.datasets
    endif

    rootNode = widget_tree(state.datasetTree, value='Datasets', $
                   event_pro='spdfIgnoreSelected', $
                   /folder, /expanded)

    widget_control, /hourglass

    datasets = state.cdas->getDatasets( $
                   dataview = *state.selectedDataview, $
                   observatoryGroups = observatoryGroups, $
                   instrumentTypes = instrumentTypes, $
                   authenticator=state.authenticator, $
                   httpErrorReporter=state.errorDialog)

    if ~obj_valid(datasets[0]) then begin

        reply = dialog_message( $
                    ['No datasets were found matching the selection criteria.',$
                     'Please change the selection criteria.'], $
                    title='Datasets Not Found', /center, /information)

        obj_destroy, datasets
        return
    endif

    *state.datasets = datasets

    datasetIds = strarr(n_elements(datasets))
    for i = 0, n_elements(datasets) - 1 do begin

        datasetIds[i] = datasets[i]->getId()
    endfor
    sortedDatasetIndexes = sort(datasetIds)

    widget_control, state.datasetTree, update=0

    for i = 0, n_elements(datasets) - 1 do begin

        datasetId = datasets[sortedDatasetIndexes[i]]->getId()

        datasetLabel = datasets[sortedDatasetIndexes[i]]->getLabel()
        datasetTimeRange = datasets[sortedDatasetIndexes[i]]->getTimeInterval()

        datasetTitle = datasetId + ': ' + $
            datasetTimeRange->getCdawebStart() + ' - ' + $
            datasetTimeRange->getCdawebStop() + ': ' + datasetLabel

        variables = state.cdas->getVariables( $
                        datasetId, $
                        dataview = *state.selectedDataview, $
                        authenticator=state.authenticator, $
                        httpErrorReporter=state.errorDialog)

        if obj_valid(variables[0]) then begin

            ; Not merely a pointer dataset (we do not show pointer
            ; datasets in the tree).
            ;
            ; Putting a uvalue of datasets[i] in the tree widget
            ; makes destroying the widget more difficult
            ;
            datasetNode = widget_tree(rootNode, value=datasetTitle, $
                              /folder, uvalue=datasetId, $
                              event_pro='spdfDatasetTreeEvent')

            for j = 0, n_elements(variables) - 1 do begin

                varName = variables[j]->getName()
                varDescription = variables[j]->getLongDescription()
                if varDescription eq '' then begin

                    varDescription = variables[j]->getShortDescription()
                endif

                varTitle = varName + ': ' + varDescription

                varNode = widget_tree(datasetNode, value=varTitle, $
                              event_pro='spdfIgnoreSelected', $
                            uvalue={datasetId:datasetId, varName:varName})
            endfor
        endif
        obj_destroy, variables
    endfor

    widget_control, state.datasetTree, update=1

end


;+
; Determines if the given string is a valid date/time value.
;
; @param value {in} {type=string}
;            value to be tested.
; @returns true if the given string is a valid date/time value.
;             Otherwise false.
;-
function spdfIsValidDate, $
    value
    compile_opt idl2

    return, stregex(value, $
                '[12][0-9]{3}[-/][01][0-9][-/][0-3][0-9]([ T][0-2][0-9]:[0-5][0-9]:[0-5][0-9])?', $
                /boolean)
end


;+
; Gets the dataset and variable selection values.
;
; @param datasetTree {in} {type=int}
;            id of dataset tree widget.
; @param selectedDatasetId {out} {type=string}
;            id of selected dataset.
; @param selectedVarNames {out} {type=strarr}
;            names of selected variables.
; @returns 1 if a valid selections was made.  0 if the selection
;              was invalid.
;-
function spdfGetDatasetSelection, $
    datasetTree, selectedDatasetId, selectedVarNames
    compile_opt idl2

    selectedItems = widget_info(datasetTree, /tree_select)

    if selectedItems[0] eq -1 then begin

        reply = dialog_message( $
            'Please choose one or more Variables within a single Dataset', $
            title='Dataset Variable Selection', /center, /error)
        return, 0
    endif

    datasetsSelected = 0
    varsSelected = 0
    selectedDatasetId = ''

    ; first count the number of selected datasets and variables

    for i = 0, n_elements(selectedItems) - 1 do begin

        if ~widget_info(selectedItems[i], /tree_folder) then begin

            widget_control, selectedItems[i], $
                get_uvalue=selectedDataset

            if selectedDataset.datasetId ne selectedDatasetId then begin

                datasetsSelected++
                selectedDatasetId = selectedDataset.datasetId
                varsSelected = 0
            endif

            varsSelected++
        endif
    endfor

    if datasetsSelected eq 1 then begin

        selectedVarNames = strarr(varsSelected)
        selectedVarIndex = 0

        ; now go back and get the selected variable names
        
        for i = 0, n_elements(selectedItems) - 1 do begin

            if ~widget_info(selectedItems[i], /tree_folder) then begin


                widget_control, selectedItems[i], $
                    get_uvalue=selectedDataset

                selectedVarNames[selectedVarIndex] = $
                    selectedDataset.varName
                selectedVarIndex++
            endif
        endfor
    endif else begin

        reply = dialog_message( $
            'Please choose one or more Variables within a single Dataset', $
            title='Dataset Variable Selection', /center, /error)
        return, 0
    endelse

    return, 1
end


;+
; Gets the specified time interval.
;
; @param startTimeWidget {in} {type=int}
;            id of start time widget.
; @param stopTimeWidget {in} {type=int}
;            id of stop time widget.
; @returns specifed time interval or null object reference if an
;              invalid value was specified.
;-
function spdfGetSpecifiedTime, $
    startTimeWidget, stopTimeWidget
    compile_opt idl2

    widget_control, startTimeWidget, get_value=startTime

    if ~spdfIsValidDate(startTime) then begin

        reply = dialog_message( $
            ['The Start Time must be set to a valid value', $
             'YYYY/MM/DD[ HH:MM:SS]'], $
            title='Invalid Date', /center, /error)
        return, obj_new()
    endif

    widget_control, stopTimeWidget, get_value=stopTime

    if ~spdfIsValidDate(stopTime) then begin

        reply = dialog_message( $
            ['The Stop Time must be set to a valid value', $
             'YYYY/MM/DD[ HH:MM:SS]'], $
            title='Invalid Date', /center, /error)
        return, obj_new()
    endif

    timeInterval = obj_new('SpdfTimeInterval', startTime, stopTime)

    if ~timeInterval->isStartLessThanStop() then begin

        reply = dialog_message( $
            'Start Time must be less than Stop Time value', $
            title='Invalid Time Interval', /center, /error)

        obj_destroy, timeInterval

        return, obj_new()
    endif

    return, timeInterval
end


;+
; Responds to a request to view the binning help.
;
; @param event {in} {type=widget event}
;            event triggering the execution of this procedure
;-
pro spdfBinHelp, $
    event
    compile_opt idl2

    catch, errorStatus
    if (errorStatus ne 0) then begin

        catch, /cancel

        reply = dialog_message( $
                    'Failed to get binning help.', $
                    title='Help Error', /center, /error)
        return
    endif

    widget_control, event.top, get_uvalue=state

    proxySettings = obj_new('SpdfHttpProxy')

    help = obj_new('IDLnetURL', $
                    proxy_authentication = $
                        proxySettings.getAuthentication(), $
                    proxy_hostname = proxySettings.getHostname(), $
                    proxy_port = proxySettings.getPort(), $
                    proxy_username = proxySettings.getUsername(), $
                    proxy_password = proxySettings.getPassword(), $
                    ssl_verify_peer=state.sslVerifyPeer)

    *state.localBinHelp = $
        help->get(filename='BinReadme.html', $
        url='https://cdaweb.gsfc.nasa.gov/CDAWeb_Binning_readme.html')

    online_help, book=*state.localBinHelp, /full_path

end


;+
; Responds to a request to view the spike removal help.
;
; @param event {in} {type=widget event}
;            event triggering the execution of this procedure
;-
pro spdfSpikeRemovalHelp, $
    event
    compile_opt idl2

    catch, errorStatus
    if (errorStatus ne 0) then begin

        catch, /cancel

        reply = dialog_message( $
                    'Failed to get spike removal help.', $
                    title='Help Error', /center, /error)
        return
    endif

    widget_control, event.top, get_uvalue=state

    proxySettings = obj_new('SpdfHttpProxy')

    help = obj_new('IDLnetURL', $
                    proxy_authentication = $
                        proxySettings.getAuthentication(), $
                    proxy_hostname = proxySettings.getHostname(), $
                    proxy_port = proxySettings.getPort(), $
                    proxy_username = proxySettings.getUsername(), $
                    proxy_password = proxySettings.getPassword(), $
                    ssl_verify_peer=state.sslVerifyPeer)

    *state.localSpikeRemovalHelp = $
        help->get(filename='CleanAlgorithm.pdf', $
        url='https://hpde.gsfc.nasa.gov/CleanAlgorithm.pdf')

    online_help, book=*state.localSpikeRemovalHelp, /full_path

end


;+
; Responds to a "bin data" button event.  The current selection value
; is saved in state.
;
; @param event {in} {type=widget event}
;            event triggering the execution of this function.
; @returns 0
;-
function spdfBinDataButton, $
    event
    compile_opt idl2

    widget_control, event.top, get_uvalue=state

    *state.binData = event.select

    return, 0
end


;+
; Responds to a "bin missing values" button event.  The current selection
; value is saved in state.
;
; @param event {in} {type=widget event}
;            event triggering the execution of this function.
; @returns 0
;-
function spdfBinMissingValues, $
    event
    compile_opt idl2

    widget_control, event.top, get_uvalue=state

    *state.binMissingValues = event.value

    return, 0
end


;+
; Responds to a "spike removal selected" event.  The current selection
; value is saved in state.
;
; @param event {in} {type=widget event}
;            widget_list event.
;-
pro spdfSpikeRemovalSelected, $
    event
    compile_opt idl2

    widget_control, event.top, get_uvalue=state

    *state.binSigmaMultiplier = event.index * 2
end


;+
; Responds to a "bin override default binning" button event.  The 
; current selection value is saved in state.
;
; @param event {in} {type=widget event}
;            event triggering the execution of this function.
; @returns 0
;-
function spdfBinOverrideDefaultBinning, $
    event
    compile_opt idl2

    widget_control, event.top, get_uvalue=state

    *state.binOverrideDefaultBinning = event.value

    return, 0
end


;+
; Gets the BinData value from the associated widgets.
;
; @param state {in} {type=struct}
;            widget program's state.
; @returns the current BinData value or a null object if binning
;     is not currently selected.  If the interval values is invalid
;     an error dialog is displayed and a null object is returned.
;-
function spdfGetBinData, $
    state
    compile_opt idl2

    if *state.binData eq 1 then begin

        return, obj_new('SpdfBinData', *state.binInterval, $
                        *state.binMissingValues, $
                        *state.binSigmaMultiplier, $
                        *state.binOverrideDefaultBinning)
    endif

    return, obj_new()
end


;+
; Responds to a close bin data window event.
;
; @param event {in} {type=widget_button}
;            event triggering the execution of this procedure.
;-
pro spdfCloseBinWindow, $
    event
    compile_opt idl2

    widget_control, event.top, get_uvalue=state

    widget_control, *state.binIntervalId, get_value=*state.binInterval

    widget_control, event.top, /destroy
end


;+
; Responds to a request to set data binning parameters.
;
; @param event {in} {type=widget event}
;            event triggering the execution of this procedure
;-
pro spdfBinDataWindow, $
    event

    widget_control, event.top, get_uvalue=state

    title = 'Compute uniformly spaced binned data'

    binWindow = $
        widget_base(title=title, /column, group_leader=event.top)

    binDataPanel = widget_base(binWindow, /row)
    binDataButton = cw_bgroup(binDataPanel, $
                    ['Compute uniformly spaced binned data'], $
                    /nonexclusive, /frame, $
                    event_funct='spdfBinDataButton', $
                    set_value=*state.binData)
    ; The following binHelpPanel exists to keep focus (black border)
    ; from the binHelpButton.  All other attempt to change focus (for
    ; example, widget_control, binDataButton, /input_focus) away from
    ; the binHelpButton failed.
    binHelpPanel = widget_base(binDataPanel, /row)
    binHelpButton = widget_button(binHelpPanel, /align_right, $
                      event_pro='spdfBinHelp', value='Help', $
                      tooltip='View explanation of computation')

    intervalLabel = widget_label(binWindow, /align_left, $
                        value='Interval (seconds)');
    *state.binIntervalId = cw_field(binWindow, $
                    title='', $
                    /float, value=*state.binInterval)
; 8.3                    /double, value=*state.binInterval)

    missingValues = cw_bgroup(binWindow, $
                    ['Use Fill Value', 'Interpolate'], $
                    /exclusive, /frame, $
                    label_top='Handle Missing Values', $
                    event_funct='spdfBinMissingValues', $
                    set_value=*state.binMissingValues)

    spikeRemovalLabel = widget_label(binWindow, /align_left, $
        value='Spike Removal');
    spikeRemovalPanel = widget_base(binWindow, /row)
    spikeRemovalOptions = ['None', $
        'Remove mild to moderate outliers', $
        'Remove moderate to extreme outliers', $
        'Remove extreme outliers only']
    binSpikeRemoval = widget_combobox(spikeRemovalPanel, $
                   event_pro='spdfSpikeRemovalSelected', $
                   value=spikeRemovalOptions)
    widget_control, binSpikeRemoval, $
        set_combobox_select=*state.binSigmaMultiplier / 2
    spikeHelpButton = widget_button(spikeRemovalPanel, /align_right, $
                      event_pro='spdfSpikeRemovalHelp', value='Help', $
                      tooltip='View explanation of algorithm')

    overrideDefaultBinning = cw_bgroup(binWindow, $
                    ['Use Default Selection', $
                     'Override Default Selection'], $
                    /exclusive, /frame, $
                    label_top='Binning Variable Selection', $
                    event_funct='spdfBinOverrideDefaultBinning', $
                    set_value=*state.binOverrideDefaultBinning)

    binButtonPanel = widget_base(binWindow, /row)

    closeButton = widget_button(binButtonPanel, /align_left, $
                      event_pro='spdfCloseBinWindow', $
                      value='Close', tooltip='Close data binning window')

    widget_control, binWindow, set_uvalue=state, /realize

    xmanager, 'SpdfBinDataWindow', binWindow

end


;+
; Creates IDL statements to read the specified data into an IDL 
; environment.  
;
; @param state {in} {type=struct}
;            widget program's state.
; @param timeInterval {in} {type=SpdfTimeInterval}
;            time range of data to get.  If this is not a valid
;            object reference, then the generated IDL statement
;            contains the variable names t_init and t_final instead
;            of actual time values.
; @param datasetId {in} {type=string}
;            dataset identifier.
; @param varNames {in} {type=strarr}
;            names of variables containing the desired data.
;-
pro spdfGetCdawebDataCmd, $
    state, timeInterval, datasetId, varNames
    compile_opt idl2

    widget_control, state.dataVarName, get_value=dataVarName

    datasetCmdTxt = "'" + datasetId + "'"

    varCmdTxt = "['" + varNames[0] + "'"
    for i = 1, n_elements(varNames) - 1 do begin
        varCmdTxt = varCmdTxt + ", '" + varNames[i] + "'"
    endfor
    varCmdTxt = varCmdTxt + "]"

    if obj_valid(timeInterval) then begin

        timeCmdTxt = "['" + timeInterval->getIso8601Start() + $
                     "', '" + timeInterval->getIso8601Stop() + "']"
    endif else begin

        timeCmdTxt = "[t_init, t_final]"

    endelse

    binTxt = ''
    binData = spdfGetBinData(state)

    if obj_valid(binData) then begin

        binTxt = ', binInterval=' + $
                 string(binData->getInterval(), format='(F5.1)') + $
                 ', binInterpolateMissingValues=' + $
                 string(binData->getInterpolateMissingValues(), $
                        format='(I1)') + $
                 ', binSigmaMultiplier=' + $
                 string(binData->getSigmaMultiplier(), format='(I1)') + $
                 ', binOverrideDefaultBinning=' + $
                 string(binData->getOverrideDefaultBinning(), $
                        format='(I1)')
    endif

    print, dataVarName, ' = spdfgetdata(', datasetCmdTxt, ', ', $
           varCmdTxt, ', ', timeCmdTxt, binTxt, ')'

    reply = dialog_message( $
        ['The specified data can be read into the IDL environment by', $
        'executing the IDL statements that were printed on the ', $
        'IDL console.'], $
                title='Get Data Operation', /center, /information)
end


; Eliminated the following when everyone is using CDF 3.5's better version.
;
function spdfCdfEpoch2Julday, $
    epoch
    compile_opt idl2

    julday = dblarr(n_elements(epoch), /nozero)

    for i = 0, n_elements(epoch) - 1 do begin

        cdf_epoch, epoch[i], /breakdown_epoch, $
            year, month, day, hour, minute, second, millisecond

        secondMillisecond = float(second) + float(millisecond) / 1000.0

        julday[i] = julday (month, day, year, hour, minute, secondMillisecond)
    endfor

    return, julday
end


;+
; Executes the the operation to retrieve the specified data into this
; IDL environment.
;
; @param state {in} {type=struct}
;            widget program's state.
; @param timeInterval {in} {type=SpdfTimeInterval}
;            time range of data to get.
; @param datasetId {in} {type=string}
;            dataset identifier.
; @param varNames {in} {type=strarr}
;            names of variables containing the desired data.
;-
pro spdfGetCdawebDataExec, $
    state, timeInterval, datasetId, varNames
    compile_opt idl2

    widget_control, state.dataVarName, get_value=dataVarName

    if dataVarName eq '' then begin

        reply = dialog_message( $
                    'A name for the result variable must be set.', $
                    title='Missing Variable Name', /center, /error)
        return
    endif

    widget_control, /hourglass

    dataResults = $
        state.cdas->getCdfData($
            timeInterval, datasetId, varNames, $
            dataview=*state.selectedDataview, $
            binData=spdfGetBinData(state), $
            authenticator=state.authenticator, $
            httpErrorReporter=state.errorDialog)

    if ~obj_valid(dataResults) then begin

        reply = dialog_message( $
                    'Error requesting data from CDAS web services.', $
                    title='CDAS Web Service Error', /center, /error)
        return
    endif

    fileDescriptions = dataResults->getFileDescriptions()

    if obj_valid(fileDescriptions) and $
       n_elements(fileDescriptions) gt 0 then begin

        localCdfNames = strarr(n_elements(fileDescriptions))

        for i = 0, n_elements(fileDescriptions) - 1 do begin

            localCdfNames[i] = $
              fileDescriptions[i]->getFile(sslVerifyPeer=state.sslVerifyPeer)
        endfor

        ; make a copy of localCdfNames that spd_cdawlib_read_mycdf can alter
        localCdfNames2 = localCdfNames
        cdfData = spd_cdawlib_read_mycdf(varNames, localCdfNames2) 

        (scope_varFetch(dataVarName, /enter, level=1)) = $
            spd_cdawlib_hsave_struct(cdfData, /nosave)

        resultMsgLines = n_elements(varNames) + 1

        if *state.saveData then begin

            resultMsgLines += n_elements(localCdfNames) + 1
        endif 

        resultMsg = strarr(resultMsgLines)
        resultMsg[0] = $
            'The selected data is now available in'

        for i = 0, n_elements(varNames) - 1 do begin

            resultMsg[1 + i] = $
                '    ' + dataVarName + '.' + varNames[i] + '.dat'
        endfor

        if *state.saveData then begin

            resultMsgIndex = n_elements(varNames)

            resultMsg[resultMsgIndex++] = $
                'Also, the downloaded data has been saved in'

            for i = 0, n_elements(localCdfNames) - 1 do begin

                resultMsg[resultMsgIndex + i] = $
                    '    ' + localCdfNames[i]
            endfor
        endif else begin

            file_delete, localCdfNames
        endelse
    endif else begin

        resultMsg = 'No data found with the specified parameters.'
    endelse

    obj_destroy, fileDescriptions
    obj_destroy, dataResults

    reply = dialog_message(resultMsg, $
                title='Get Data Operation', /center, /information)
end


;+
; Responds to an event requesting the retrieval of the data
; specified by the users previous selections.
;
; @param event {in} {type=widget_button}
;            event triggering the execution of this procedure.
;-
pro spdfGetCdawebData, $
    event
    compile_opt idl2

    widget_control, event.top, get_uvalue=state

    if spdfGetDatasetSelection(state.datasetTree, $
           selectedDatasetId, selectedVarNames) ne 1 then return

    timeInterval = spdfGetSpecifiedTime(state.startTime, state.stopTime)

    if ~obj_valid(timeInterval) then return

    spdfGetCdawebDataExec, $
        state, timeInterval, selectedDatasetId, selectedVarNames

    obj_destroy, timeInterval
end


;+
; Responds to an event requesting the IDL code to retrieve the data
; specified by the users previous selections.
;
; @param event {in} {type=widget_button}
;            event triggering the execution of this procedure.
;-
pro spdfGetCdawebDataIdl, $
    event
    compile_opt idl2

    widget_control, event.top, get_uvalue=state

    if spdfGetDatasetSelection(state.datasetTree, $
           selectedDatasetId, selectedVarNames) ne 1 then return

    widget_control, state.startTime, get_value=startTime
    widget_control, state.stopTime, get_value=stopTime

    if startTime eq '' and stopTime eq '' then begin

        timeInterval = obj_new()

    endif else begin

        timeInterval = $
            spdfGetSpecifiedTime(state.startTime, state.stopTime)

        if ~obj_valid(timeInterval) then return

    endelse

    spdfGetCdawebDataCmd, $
        state, timeInterval, selectedDatasetId, selectedVarNames

    obj_destroy, timeInterval
end


;+
; Responds to an event requesting that data be read from a local
; file.
;
; @param event {in} {type=widget_button}
;            event triggering the execution of this procedure.
;-
pro spdfReadLocalData, $
    event
    compile_opt idl2

    widget_control, event.top, get_uvalue=state
    widget_control, state.dataVarName, get_value=dataVarName

    files = dialog_pickfile(default_extension='cdf', $
                filter=['*.cdf'], /fix_filter, $
                /multiple_files, /must_exist, /read)

    widget_control, /hourglass

    if files ne '' then begin

        cdfData = spd_cdawlib_read_mycdf(varNames, files, all=1)

        (scope_varFetch(dataVarName, /enter, level=1)) = $
            spd_cdawlib_hsave_struct(cdfData, /nosave)

        reply = dialog_message($
                'Data from the selected file(s) is now available in ' $
                + dataVarName + '.dat', $
                    title='Read Data Operation', /center, /information)
    endif
end


;+
; Responds to an event requesting the CDAWlib's plot of the retrieved
; data specified by the users previous selections.
;
; @param event {in} {type=widget_button}
;            event triggering the execution of this procedure.
;-
pro spdfGetCdawebPlot, $
    event
    compile_opt idl2

    widget_control, event.top, get_uvalue=state

    widget_control, state.dataVarName, get_value=dataVarName

    if n_elements((scope_varFetch(dataVarName, /enter, level=1))) then begin

        ; make a copy of the data for spd_cdawlib_plotmaster to destroy
        copyOfData = scope_varFetch(dataVarName, level=1)

        s = spd_cdawlib_plotmaster(copyOfData, xsize=600, /AUTO, /CDAWEB, /SMOOTH, /SLOW)

    endif else begin

        reply = dialog_message( $
                    'Data must be retrieved before plotting it', $
                    title='Get Plot Operation', /center, /error)
    endelse
end


;+
; Responds to an event requesting the CDAWlib's listing of the retrieved
; data specified by the users previous selections.
;
; @param event {in} {type=widget_button}
;            event triggering the execution of this procedure.
;-
pro spdfCreateCdawebListing, $
    event
    compile_opt idl2

    widget_control, event.top, get_uvalue=state

    widget_control, state.dataVarName, get_value=dataVarName

    if n_elements((scope_varFetch(dataVarName, /enter, level=1))) then begin

        filename = dialog_pickfile(default_extension='txt', $
                       filter=['*.txt', '*.asci'], $
                       /overwrite_prompt, /write)

        if filename ne '' then begin

            ; make a copy of the data for spd_cdawlib_list_mystruct to destroy
            copyOfData = scope_varFetch(dataVarName, level=1)

            listing = spd_cdawlib_list_mystruct(copyOfData, filename=filename[0])

            xdisplayfile, filename[0], group=event.top

        endif

    endif else begin

        reply = dialog_message( $
                    'Data must be read before listing it', $
                    title='Get Plot Operation', /center, /error)
    endelse
end


;+
; Responds to an event that initiates a termination of this program.
;
; @param event {in} {type=widget_button}
;            event triggering the execution of this procedure.
;-
pro spdfExit, $
    event
    compile_opt idl2

    widget_control, event.top, get_uvalue=state

    obj_destroy, state.errorDialog
    obj_destroy, state.authenticator
    obj_destroy, state.cdas
    obj_destroy, state.dataviews
    ptr_free, state.selectedDataview
    ptr_free, state.observatoryGroups
    ptr_free, state.instrumentTypes
    if n_elements(*state.datasets) then obj_destroy, *state.datasets
    ptr_free, state.datasets
    ptr_free, state.selectedDataset
    ptr_free, state.saveData
    ptr_free, state.binData
    ptr_free, state.binInterval
    ptr_free, state.binMissingValues
    ptr_free, state.binSigmaMultiplier
    ptr_free, state.binOverrideDefaultBinning

; file_delete Notes?.html files

    if strlen(*state.localBinHelp) gt 0 then begin

        file_delete, *state.localBinHelp, /quiet
    endif
    ptr_free, state.localBinHelp

    if strlen(*state.localSpikeRemovalHelp) gt 0 then begin

        file_delete, *state.localSpikeRemovalHelp, /quiet
    endif
    ptr_free, state.localSpikeRemovalHelp

heap_gc

    widget_control, event.top, /destroy
end


;+
; Display information about this software.
;
; @param event {in} {type=widget_button}
;            event triggering the execution of this procedure.
;-
pro spdfAbout, $
    event
    compile_opt idl2

    widget_control, event.top, get_uvalue=state

    reply = dialog_message([ $
        'NASA/Goddard Space Flight Center (GSFC)',$
        'Space Physics Data Facility (SPDF)', $
        'https://spdf.gsfc.nasa.gov/', $
        '', $
        'Current CDAWlib version: ' + spd_cdawlib_version(), $
        'Current SpdfCdas version: ' + state.cdas->getVersion()], $
        title='About', /center, /information)
end


;+
; Gets all dataview descriptions.
;
; @private
;
; @param cdas {in} {required} {type=SpdfCdas}
;            the Coordinated Data Analysis System.
; @returns objarr containing SpdfDataviewDescription objects for all
;              cdaweb dataviews.
;-
function spdfGetDataviews, $
    cdas
    compile_opt idl2

    if ~(cdas->isUpToDate()) then begin

        reply = dialog_message([ $
            'Current CDAWlib version: ' + spd_cdawlib_version(), $
            'Current SpdfCdas version: ' + cdas->getVersion(), $
            'Available SpdfCdas version: ' + cdas->getCurrentVersion(), $
            'There is a newer version of the SpdfCdas library available.'], $
            title='Version Warning', /center, /information)
    endif

    dataviews = cdas->getDataviews()

    extraDataviewIds = getenv('SPDF_EXTRA_DATAVIEWS')

    if strlen(extraDataviewIds) eq 0 then begin

        return, dataviews
    endif

    extraDataviewIds = strsplit(extraDataviewIds, ':', /extract)
    numExtraDataviews = 0

    for i = 0, n_elements(extraDataviewIds) - 1 do begin

        datasets = cdas->getDatasets(dataview = extraDataviewIds[i])

        if n_elements(datasets) eq 1 && $
           ~obj_valid(datasets[0]) then begin

            print, extraDataviewIds[i], $
                ' does not appear to be a valid dataview.', $
                ' It will be ignored.'
            extraDataviewIds[i] = ''
        endif else begin

            numExtraDataviews = numExtraDataviews + 1
        endelse
    endfor

    if numExtraDataviews gt 0 then begin

        expandedDataviews = $
            objarr(n_elements(dataviews) + numExtraDataviews, /nozero)

        for i = 0, n_elements(dataviews) - 1 do begin

            expandedDataviews[i] = dataviews[i]
        endfor

        endpointAddress = dataviews[0]->getEndpointAddress()
        endpointAddress = $
            strsplit(endpointAddress, dataviews[0]->getId(), $
                     /regex, /extract)

        j = 0
        for i = n_elements(dataviews), $
            n_elements(expandedDataviews) - 1 do begin

            while extraDataviewIds[j] eq '' do j = j + 1

            expandedDataviews[i] = $
                obj_new('SpdfDataviewDescription', $
                    extraDataviewIds[j], endpointAddress, $
                    extraDataviewIds[j], '', '', 1b, '', 0b)

            j = j + 1
        endfor

        return, expandedDataviews
    endif

    return, dataviews
end


;+
; Provides a GUI for choosing and retrieving data from 
; <a href="https://cdaweb.gsfc.nasa.gov/">CDAWeb</a>.
; 
; If access to the Internet is through an HTTP proxy, the caller 
; should ensure that the HTTP_PROXY environment is correctly set 
; before this procedure is called.  The HTTP_PROXY value should be of 
; the form 
; http://username:password@hostname:port/.
;
; @keyword endpoint {in} {optional} {type=string}
;              {default=SpdfCdas->getDefaultEndpoint()}
;              URL of CDAS web service.
; @keyword sslVerifyPeer {in} {optional} {type=int} 
;              {default=spdfGetDefaultSslVerify()}
;              Specifies whether the authenticity of the peer's SSL
;              certificate should be verified.  When 0, the connection
;              succeeds regardless of what the peer SSL certificate
;              contains.
; @keyword GROUP_LEADER {in} {optional} {type=int}
;              The widget ID of the group leader for this window.  If
;              no value is provided, the resulting window will not
;              belong to a group and will be non-blocking.
;-
pro spdfCdawebChooser, $
    endpoint = endpoint, $
    sslVerifyPeer = sslVerifyPeer, $
    GROUP_LEADER = groupLeaderWidgetId
    compile_opt idl2

    RESOLVE_ROUTINE, 'spd_cdawlib_virtual_funcs', /COMPILE_FULL_FILE
    
    cd, current=cwd
    if ~file_test(cwd, /write) then begin

        print, 'Error: The current working directory (', $
                       cwd, ') is not writable.'
        print, 'Downloaded files are saved in the current working directory.'
        print, 'Use the IDL CD procedure to change the current working'
        print, 'directory to one that is writable and then re-run'
        print, 'SpdfCdawebChooser.'
        return
    endif

; This is suppose to help with getting windows placed better in a
; multi-monitor environment but it doesn't seem to for an 
; "extended desktop".
;    monitorInfo = obj_new('IDLsysMonitorInfo')


    if keyword_set(groupLeaderWidgetId) then begin

        tlb = widget_base(title='CDAWeb Data Chooser', /column, $
                          GROUP_LEADER=groupLeaderWidgetId)
        defaultSaveCdfOption = 1
    endif else begin

        tlb = widget_base(title='CDAWeb Data Chooser', /column)
        defaultSaveCdfOption = 0
    endelse

    if (n_elements(sslVerifyPeer) eq 0) then sslVerifyPeer = 1

    cdas = $
        obj_new('SpdfCdas', $
        endpoint=endpoint, $
        sslVerifyPeer=sslVerifyPeer, $
        userAgent='CdawebChooser')

    dataviews = spdfGetDataviews(cdas)

    if ~obj_valid(dataviews[0]) then begin

        reply = dialog_message( $
                    ['Could not connect to CDAWeb.', $
                     'Please check Internet connectivity to ', $
                     cdas->getEndpoint(), $
                     'On older versions of IDL, you may have ',$
                     'to update ca-bundle.crt or start ', $
                     'CdawebChooser with sslVerifyPeer=0. ', $
                     'And if access to the Internet is through ', $
                     'an HTTP proxy, ensure that the HTTP_PROXY ', $
                     'environment variable is correctly set. '], $
                    title='Network Error', /center, /error)
        return
    endif

    initialDvSelection = 0

    dvTitles = strarr(n_elements(dataviews))
    for i = 0, n_elements(dataviews) - 1 do begin
        dvTitles[i] = dataviews[i]->getTitle()
        if dataviews[i]->getId() eq 'sp_phys' then begin
            initialDvSelection = i
        endif
    endfor
  
    ;
    ; Dataview selection panel
    ;
    dvBase = widget_base(tlb, frame=3, /column)
    dvLabel = widget_label(dvBase, /align_left, $
                  value='Dataview Selection: ')
    dvList = widget_combobox(dvBase, event_pro='spdfDataviewSelected', $
                 value=dvTitles)

    ;
    ; Dataset selection panel
    ;
    datasetSelectionPanel = widget_base(tlb, frame=3, /column)
    
    datasetSelectionLabel = $
        widget_label(datasetSelectionPanel, /align_left, $
            value='Dataset Selection:')

    miPanel = widget_base(datasetSelectionPanel, /row)

    missionGroups = ['']
    missionGroupsPanel = widget_base(miPanel, /column)
    mLabel = widget_label(missionGroupsPanel, value='Mission Groups')
    mList = widget_list(missionGroupsPanel, $
                event_pro='spdfIgnoreSelected', $
                value=missionGroups, xsize=50, ysize=7, /multiple)

    instrumentTypes = ['']
    instrumentTypesPanel = widget_base(miPanel, /column)
    iLabel = widget_label(instrumentTypesPanel, $
                 value='Instrument Types')
    iList = widget_list(instrumentTypesPanel, $
                event_pro='spdfIgnoreSelected', $
                value=instrumentTypes, xsize=50, ysize=7, /multiple)

    dsButton = widget_button(datasetSelectionPanel, $
                   /align_left, $
                   event_pro='spdfFindDatasets', $
                   value='Find Datasets', $
                   tooltip='Find datasets for the specified mission groups and instrument types')


    ;
    ; Data selection panel
    ;
    dataSelectionPanel = widget_base(tlb, frame=3, /column)

    dataSelectionLabel = $
        widget_label(dataSelectionPanel, /align_left, $
            value='Data Selection:')

    dsLabel = widget_label(dataSelectionPanel, $
                  value='Datasets/Variables')
    dsTree = widget_tree(dataSelectionPanel, /multiple, $
                 /context_events, event_pro='spdfDatasetTreeEvent', $
                 xsize=600, ysize=240)

    dsContextMenu = widget_base(tlb, /context_menu)

    viewNotesButton = widget_button(dsContextMenu, $
                          event_pro='spdfViewNotes', $
                          value="View Notes")
    viewDoiButton = widget_button(dsContextMenu, $
                          event_pro='spdfViewDoi', $
                          value="View DOI/SPASE")
    viewInventoryButton = widget_button(dsContextMenu, $
                      event_pro='spdfViewInventory', $
                      value="View Inventory")


    timePanel = widget_base(dataSelectionPanel, /row)

    defaultTimeButton = widget_button(timePanel, $
                      event_pro='spdfDefaultTime', $
                      value="Set Default Time")
    startTime = cw_field(timePanel, title='Start Time')
    stopTime = cw_field(timePanel, title='Stop Time')
    timeFormatLabel = widget_label(timePanel, $
                          value='Format YYYY/MM/DD[ HH:MM:SS]')

    ;
    ; Data operation panel
    ;
    dataOperationPanel = widget_base(tlb, frame=3, /column)

    dataOperationLabel = $
        widget_label(dataOperationPanel, /align_left, $
            value='Data Operation:')

    optionPanel = widget_base(dataOperationPanel, /row)

    dataVarName = cw_field(optionPanel, title='Variable Name', $
                      value='data')
    saveCdfOption = cw_bgroup(optionPanel, $
                    ['Save local CDF files'], $
                    /nonexclusive, /frame, $
                    label_left='File Option', $
                    event_funct='spdfSaveDataButton', $
                    set_value=[defaultSaveCdfOption])
    binDataButton = widget_button(optionPanel, $
                     event_pro='spdfBinDataWindow', $
                     value='Bin Data Options...', $
                     tooltip='Compute uniformly spaced binned data')

    actionPanel = widget_base(dataOperationPanel, /row)
    dataButton = widget_button(actionPanel, $
                     event_pro='spdfGetCdawebData', $
                     value='Get CDAWeb Data', $
                     tooltip='Read CDAWeb data into IDL environment')
    idlButton = widget_button(actionPanel, $
                     event_pro='spdfGetCdawebDataIdl', $
                     value='Show Get Data IDL', $
                     tooltip='Display IDL code to get data')
    fileButton = widget_button(actionPanel, $
                     event_pro='spdfReadLocalData', $
                     value='Read Local CDF', $
                     tooltip='Read data from local file into IDL environment')
    plotButton = widget_button(actionPanel, $
                     event_pro='spdfGetCdawebPlot', $
                     value='Show CDAWlib Plot', $
                     tooltip="Display CDAWlib's plot of the data")
    listButton = widget_button(actionPanel, $
                     event_pro='spdfCreateCdawebListing', $
                     value='Create CDAWlib Listing', $
                     tooltip="Create CDAWlib's listing of the data")

    bottomPanel = widget_base(tlb, frame=3, row=1)
    ;
    ; Program control panel
    ;
    programControlPanel = widget_base(bottomPanel, /column)

    programControlLabel = $
        widget_label(programControlPanel, /align_left, $
            value='Window Control:')

    exitButton = widget_button(programControlPanel, /align_left, $
                     event_pro='spdfExit', $
                     value='Close', tooltip='Close this window')

; this button needs to be positioned better
;    aboutButton = widget_button(bottomPanel, /align_right, $
;                     event_pro='spdfAbout', $
;                     value='About', tooltip='About this software')

    state = { $
        errorDialog:obj_new('SpdfHttpErrorDialog'), $
        authenticator:obj_new('SpdfCdawebChooserAuthenticator', tlb), $
        cdas:cdas, $
        selectedDataview:ptr_new(/allocate_heap), $
        dataviews:dataviews, $
        dataviewList:dvList, $
        observatoryGroups:ptr_new(/allocate_heap), $
        observatoryGroupList:mList, $
        instrumentTypes:ptr_new(/allocate_heap), $
        instrumentTypeList:iList, $
        datasets:ptr_new(/allocate_heap), $
        datasetTree:dsTree, $
        selectedDataset:ptr_new(/allocate_heap), $
        datasetContextMenu:dsContextMenu, $
        startTime:startTime, $
        stopTime:stopTime, $
        dataVarName:dataVarName, $
        saveData:ptr_new(defaultSaveCdfOption), $
        binData:ptr_new(0), $
        binIntervalId:ptr_new(0), $
        binInterval:ptr_new(60.0), $
        binMissingValues:ptr_new(0), $
        binSigmaMultiplier:ptr_new(0), $
        binOverrideDefaultBinning:ptr_new(0), $
        localBinHelp:ptr_new(''), $
        localSpikeRemovalHelp:ptr_new(''), $
        sslVerifyPeer:sslVerifyPeer $
    }

    widget_control, tlb, set_uvalue=state, /realize

    if keyword_set(groupLeaderWidgetId) then begin

        xmanager, 'SpdfCdawebChooser', tlb, $
            group_leader=groupLeaderWidgetId

    endif else begin

        xmanager, 'SpdfCdawebChooser', tlb, /no_block

    endelse

    spdfSelectDataview, tlb, dvList, initialDvSelection

end
