Viewing contents of file '../idllib/idl_5.2/lib/ascii_template.pro'
; $Id: ascii_template.pro,v 1.18.4.1 1999/01/16 16:37:11 scottm Exp $
;
; Copyright (c) 1996-1999, Research Systems, Inc.  All rights reserved.
;       Unauthorized reproduction prohibited.
;+
; NAME:
;       ASCII_TEMPLATE
;
; PURPOSE:
;       Generate a template that defines an ASCII file format.
;
; CATEGORY:
;       Input/Output.
;
; CALLING SEQUENCE:
;       template = ASCII_TEMPLATE( [file] )
;
; INPUTS:
;       file              - Name of file to base the template on.
;                           Default = use DIALOG_PICKFILE to get the file.
;
; INPUT KEYWORD PARAMETERS:
;       browse_lines      - Number of lines to read in at a time via the
;                           GUI's browse button.  Default = 50.
;
; OUTPUT KEYWORD PARAMETERS:
;       cancel            - Boolean indicating if the user canceled
;                           out of the interface (1B = canceled).
;
; OUTPUTS:
;       The function returns a template (structure) defining ASCII files
;       of the input file's format.  Such templates may be used as inputs
;       to function READ_ASCII.  (0 is returned if the user canceled.)
;
; COMMON BLOCKS:
;       None.
;
; SIDE EFFECTS:
;       None.
;
; RESTRICTIONS:
;       See DESCRIPTION.
;
; DESCRIPTION:
;       This routine presents a graphical user interface (GUI) that assists
;       the user in defining a template.
;
;       ASCII files handled by this routine consist of an optional header
;       of a fixed number of lines, followed by columnar data.  Files may
;       also contain comments, which exist between a user-specified comment
;       string and the corresponding end-of-line.
;
;       One or more rows of data constitute a "record."  Each data element
;       within a record is considered to be in a different column, or "field."
;       Adjacent fields may be "grouped" into multi-column fields.
;       The data in one field must be of, or promotable to, a single
;       type (e.g., FLOAT).
;
; EXAMPLES:
;       ; Generating a template to be used later, maybe on a set of files.
;       template = ASCII_TEMPLATE()
;
;       ; Same as above, but immediately specifying which file to use.
;       template = ASCII_TEMPLATE(file)
;
;       ; Same as above, but returning flag if the user canceled.
;       template = ASCII_TEMPLATE(file, CANCEL=cancel)
;
;       ; Generating and using a template in place for reading data.
;       data = READ_ASCII(file, TEMPLATE=ASCII_TEMPLATE(file))
;
; DEVELOPMENT NOTES:
;       - see ???,!!!,xxx in the code
;       - errors preserving state when switch pages with 'back/next'
;       - make NaN default missing value as in reader ?
;
; MODIFICATION HISTORY:
;       AL & RPM, 8/96 - Written.
;
;-
;
; ROUTINES:
;       fun at_build_templ      - build a template
;       pro at_delete_template  - delete a template
;       fun at_get_lines        - read lines from file
;       fun at_build_template   - build a template (calls at_build_templ)
;       fun at_num_fields       - return number of fields/line for record lines
;       fun at_default_delimit  - guess delimiter based on first data line
;       fun at_default_groups   - (not currently used : grouping by default)
;       fun at_which_field      - return the line and column of a given field
;       fun at_str_to_val       - convert a string to a long or float
;       fun at_list_to_str      - convert a vector to comma-separated string
;       fun at_str_to_list      - convert a comma-separated string to vector
;       pro at_remove_tabs      - replace tabs in string array with 4 spaces
;       pro at_display_text     - display data in the main table widget
;       pro at_resize_table     - reset number of rows/cols in table widget
;       pro at_sample_record    - display sample record in 3rd step
;       pro at_set_list         - display field-specification in 3rd step
;       pro at_update           - update the 3rd step page
;       pro at_3_event          - handle events for 3rd page of GUI
;       pro at_2_event          - handle events for 2nd page of GUI
;       pro at_1_event          - handle events for 1st page of GUI
;       pro at_set_state        - set page widget for initial, Next, and Back
;       pro at_widget_cleanup   - destroy widgets and free heap
;       pro at_widget_event     - handle buttons at the bottom of the dialog
;       fun at_widget           - create the widgets
;       fun at_check_file       - check validity of input file
;       fun ascii_template      - the main routine

; -----------------------------------------------------------------------------
;
;  Purpose:  Build an ASCII file template, using defaults as necessary.
;
function at_build_templ, $
    num_fields=num_fields, $
    field_types=field_types, $
    field_names=field_names, $
    field_locations=field_locations, $
    record_start_loc=record_start_loc, $
    delimiter=delimiter, $
    groups=groups, $
    missing_value=missing_value, $
    comment_symbol=comment_symbol

  if (n_elements(num_fields) eq 0) then num_fields = 1
  tot_num_fields = total(num_fields)

  if (n_elements(field_types) eq 0) then field_types = 4L
  if (n_elements(field_locations) eq 0) then $
    field_locations = lonarr(tot_num_fields)
  if (n_elements(field_names) eq 0) then begin
    digits_str = string(strlen(strtrim(string(fix(tot_num_fields)),2)))
    fstr = '(i' + strtrim(digits_str,2) + '.' + strtrim(digits_str,2) + ')'
    field_names = 'field' + string(indgen(tot_num_fields)+1,format=fstr)
  endif

  if (n_elements(record_start_loc) eq 0) then record_start_loc = 0L
  if (n_elements(delimiter) eq 0) then delimiter = 0B
  if (n_elements(missing_value) eq 0) then missing_value = 0.0
  if (n_elements(groups) eq 0) then groups = indgen(tot_num_fields)
  if (n_elements(comment_symbol) eq 0) then comment_symbol = ''

  ; define the ASCII template structure
  ;
  fields = replicate({ascii_field_struct, name:'', type:0, loc:0L}, $
    tot_num_fields)
  if (tot_num_fields eq 1) then begin
    fields.name = field_names[0]
    fields.type = field_types[0]
    fields.loc  = field_locations[0]
  endif else begin
    fields.name = field_names
    fields.type = field_types
    fields.loc  = field_locations
  endelse

  template = { $
    record_start_loc: record_start_loc, $
    delimit: delimiter[0], $
    missing_value: float(missing_value), $      ; Why FLOAT() ???
    comment_symbol: comment_symbol, $
    p_num_fields: ptr_new(num_fields), $
    p_fields: ptr_new(fields), $
    p_groups: ptr_new(groups) $
    }

  return, template

end                     ; at_build_templ

; -----------------------------------------------------------------------------
;
;  Purpose:  Delete an ASCII file template.
;
pro at_delete_template, template

  if (size(template,/type) ne 8) then return

  if (ptr_valid(template.p_num_fields)) then ptr_free, template.p_num_fields
  if (ptr_valid(template.p_fields)) then ptr_free, template.p_fields
  if (ptr_valid(template.p_groups)) then ptr_free, template.p_groups

end                     ; at_delete_template

; -----------------------------------------------------------------------------
;
;  Purpose:  Return a requested number of lines from the ASCII file.
;            If skip is set, then skip the first skip number of lines before
;            starting the read.
;            Return the last_pos to resume reading from a given point and
;            notify when the end of file has been reached.
;
function at_get_lines, name, num_lines, last_pos=last_pos, $
  end_reached=end_reached, skip=skip

  catch, error_status
  if (error_status ne 0) then begin
    end_reached = 1
    if (n_elements(unit) gt 0) then free_lun, unit
    return, ''
  endif

  if (n_elements(last_pos) eq 0) then last_pos = 0
  if (n_elements(skip) eq 0) then skip = 0
  lines = strarr(num_lines)
  line = ''
  count = 0L
  openr, unit, name, /get_lun
  point_lun, unit, last_pos
  for i=0, skip-1 do readf, unit, line
  while (not eof(unit) and count lt num_lines) do begin
    readf, unit, line
    lines[count] = line
    count = count + 1
  endwhile

  end_reached = (count lt num_lines)
  point_lun, -unit, last_pos

  free_lun, unit
  if (count eq 0) then              return, '' $
  else if (count lt num_lines) then return, lines[0:count-1] $
  else                              return, lines

end                     ; at_get_lines

; -----------------------------------------------------------------------------
;
;  Purpose:  Build a default template structure.
;
function at_build_template, data, missing_value

  ; Use the first record from the file (ignoring comment strings
  ; and blank lines).
  ;
  lines = (*data.p_rlines)[0:n_elements(*data.p_num_fields)-1]

  ; lut is used to determine if a given field is integer,
  ; floating point, or a string.
  ;
  lut = bytarr(256) + 1b
  lut[0:32] = 0b
  lut[48:57] = 0b
  lut[byte('e')] = 0
  lut[byte('E')] = 0
  lut[byte('+')] = 0
  lut[byte('-')] = 0
  lut[byte('.')] = 0

  ; scan through a sample record and determine a default column
  ; position and IDL type for each field.
  ;
  tot_num_fields  = long(total(*data.p_num_fields))
  field_locations = lonarr(tot_num_fields)
  field_types     = intarr(tot_num_fields)
  fpos = 0L
  for i=0, n_elements(*data.p_num_fields)-1 do begin
    bline = [byte(lines[i]), 32b]

    if (data.delimit eq 32b) then begin       ; delimiter is 'space'
      nptr = where(bline ne 32b and bline ne 9b, ncount)
      fptr = nptr[0]
      for j=1, ncount-1 do $
        if (nptr[j] gt nptr[j-1]+1) then fptr = [fptr, nptr[j]]
      fptr = [fptr, n_elements(bline)]
      add = [0,1]

    endif else begin
      nptr = where(bline eq data.delimit, ncount)
      if (ncount eq 0) then fptr = [-1, n_elements(bline)] $
      else                  fptr = [-1, nptr, n_elements(bline)]
      add = [1,1]
    endelse

    for j=0, (n_elements(fptr)-2)<((*data.p_num_fields)[i]-1) do begin
      field_locations[fpos] = fptr[j] + add[0]
      bsub = bline[fptr[j]+add[0]:(fptr[j+1]-add[1])>(fptr[j]+add[0])]
      is_string = (total(lut[bsub]) gt 0)
      if (is_string eq 0) then begin
        if (total(bsub eq 46b) gt 0 or total(bsub eq 101b) gt 0 or $
                 total(bsub eq 69b) gt 0) then field_types[fpos] = 4 $
        else $
          field_types[fpos] = 3
      endif else $
        field_types[fpos] = 7
      fpos = fpos + 1
    endfor
  endfor

  s_delimit = ([0b, data.delimit])[data.mode]

  template = at_build_templ(num_fields=*data.p_num_fields, $
    field_types=field_types, field_locations=field_locations, $
    record_start_loc=data.data_start, delimiter=s_delimit, $
    missing_value=missing_value, comment_symbol=data.comment)

  return, template

end                     ; at_build_template

; -----------------------------------------------------------------------------
;
;  Purpose:  Return an array containing the number of fields/line
;            for each line of a record (based upon the delimiters found).
;
function at_num_fields, lines, delimit, comment

  for i=0, n_elements(lines)-1 do begin
    bline = byte(lines[i])

    if (delimit eq 32b) then begin       ; delimiter is 'space'

      nptr = where(bline ne 32b and bline ne 9b, count)
      fptr = nptr[0]

      for j=1, count-1 do $
        if (nptr[j] gt nptr[j-1]+1) then fptr = [fptr, nptr[j]]

    endif else begin                     ; delimiter is not 'space'
      nptr = where(bline eq delimit, count)
      fptr = bytarr(count+1)
    endelse

    if (n_elements(num_fields) eq 0) then $
        num_fields = n_elements(fptr) $
    else begin
        if (n_elements(fptr) eq num_fields[0]) then return, num_fields
        num_fields = [num_fields, n_elements(fptr)]
    endelse

  endfor

  if (n_elements(num_fields) gt 0) then return, num_fields $
  else                                  return, 1

end                     ; at_num_fields

; -----------------------------------------------------------------------------
;
;  Purpose:  Given the first line of data make a guess as to the delimiter
;            in use.
;
function at_default_delimit, line, comment

  if (comment ne '') then begin
    pos = strpos(line, comment, 0)
    if (pos[0] ge 0) then line = strmid(line, 0, pos[0]-1)
  endif

  pos = strpos(line, ',')
  if (pos[0] ge 0) then return, 44B

  pos = strpos(line, ';')
  if (pos[0] ge 0) then return, 59B

; don't assume that the delimiter can be a colon...
;  pos = strpos(line, ':')
;  if (pos[0] ge 0) then return, 58B

  ; Default return 'space' as the delimiter.
  ;
  return, 32B

end                     ; at_default_delimit

; -----------------------------------------------------------------------------
;
; THIS FUNCTION IS NOT CURRENTLY USED !!!!!
;
;  Purpose:  Return a best guess of the groupings of data base upon initial
;            data type.
;
;function at_default_groups, data
;
;  types = (*data.p_fields).type
;  groups = intarr(n_elements(types))
;  cur_group = 0
;
;  for i=1, n_elements(types)-1 do begin
;    if (types[i] eq types[i-1]) then groups[i] = groups[i-1] $
;    else begin
;      cur_group = cur_group + 1
;      groups[i] = cur_group
;    endelse
;  endfor
;
;  return, groups
;
;end                     ; at_default_groups

; -----------------------------------------------------------------------------
;
;  Purpose:  Given a position between 0 and tot_num_fields-1 determine
;            the line and location on the line of the given field.
;
function at_which_field, $
    num_fields, $  ; IN: 
    pos, $         ; IN: the sequential field to check
    col            ; OUT: the corresponding column that the field is in

  count = 0

  for i=0, n_elements(num_fields)-1 do begin
    if (pos lt count+num_fields[i]) then begin
      col = pos - count

      ;  Return the sequential line that the field is in.
      ;
      return, i

    endif
    count = count + num_fields[i]
  endfor

end                     ; at_which_field

; -----------------------------------------------------------------------------
;
;  Purpose:  Convert a scalar string into a long or floating point value;
;            return 0 for any problems.
;
function at_str_to_val, str, floating=floating

  catch, error_status
  if (error_status ne 0) then return, 0

  if (keyword_set(floating)) then temp = 0. $
  else                            temp = 0L

  reads, str[0], temp

  return, temp

end                     ; at_str_to_val

; -----------------------------------------------------------------------------
;
;  Purpose:  Convert an array of numbers into a string of comma separated
;            values.
;
function at_list_to_str, vals

  str = strtrim(string(vals[0]),2)

  for i=1, n_elements(vals)-1 do $
    str = str + ',' + strtrim(string(vals[i]),2)

  return, str

end                     ; at_list_to_str

; -----------------------------------------------------------------------------
;
;  Purpose:  Given a string of comma separated values, convert into an array
;            of values.
;
function at_str_to_list, str

  len = strlen(str[0])
  ptr = where(byte(str[0]) eq 44b, count)
  sub = (strmid(str[0],len-1,1) eq ',')
  count = count - sub
  vals = lonarr(count+1)
  vals[0] = at_str_to_val(str[0])

  for i=0, count-1 do $
    vals[i+1] = at_str_to_val(strmid(str[0],ptr[i]+1,len))

  return, vals

end                     ; at_str_to_list

; -----------------------------------------------------------------------------
;
;  Purpose:  Scan through the string array and replace any instance of
;            tab (9b) with four white spaces.
;
pro at_remove_tabs, lines

  s_tab = string(9b)

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

    loc = 0l
    len = strlen(lines[i])

    repeat begin
      pos = strpos(lines[i], s_tab, loc)
      if (pos ge 0) then begin
        lines[i] = strmid(lines[i], 0, pos) + '    ' + $
                   strmid(lines[i], pos+1, len)
        loc = pos + 1
      endif
    endrep until (pos eq -1)

  endfor

end                     ; at_remove_tabs

; -----------------------------------------------------------------------------
;
;  Purpose:  Display the appropriate text into the main table widget.
;
pro at_display_text, data, first=first

  if (keyword_set(first)) then begin

    top_pos = widget_info(data.tw[6], /table_view)

    num_lines = n_elements(*data.p_lines)
    rstr = strtrim(string(indgen(num_lines)+1),2)

    ;  remove any tabs
    if (total(byte(*data.p_lines) eq 9b) gt 0) then begin
      lines = *data.p_lines
      at_remove_tabs, lines
      lines = reform(lines, 1, num_lines, /overwrite)
    endif else $
      lines = reform(*data.p_lines, 1, num_lines)

    widget_control, data.tw[6], set_table_view=top_pos, set_value=lines, $
      row_labels=rstr, set_table_select=[0,data.data_start<(data.twm[1]-1), $
                                         0,data.data_start<(data.twm[1]-1)]
  endif else begin

    num_lines = n_elements(*data.p_rlines)
    rstr = strtrim(string(indgen(num_lines)+1),2)

    ;  remove any tabs
    if (total(byte(*data.p_rlines) eq 9b) gt 0) then begin
      lines = *data.p_rlines
      at_remove_tabs, lines
      lines = reform(lines, 1, num_lines, /overwrite)
    endif else $
      lines = reform(*data.p_rlines, 1, num_lines)

    widget_control, data.tw[6], set_table_view=[0,0], set_value=lines, $
      row_labels=rstr, set_table_select=[-1,-1,-1,-1]

  endelse

end                     ; at_display_text

; -----------------------------------------------------------------------------
;
;  Purpose:  Resize a given table widget to a new number of row / col cells.
;
pro at_resize_table, tw, prev_size, new_size, text_table=text_table, last=last

; There are two different table widgets used in this widget. If the text_table
; keyword is set, then this refers to the table widget on the bottom which
; displays the text in STEP 1, the data in STEP 2 and a sample record in
; STEP 3. If not set, then this refers to the table wdiget in STEP 3 in the
; upper left corner which displays the field information.

  if (prev_size[0] eq new_size[0] and prev_size[1] eq new_size[1]) then return

  col_diff = prev_size[0] - new_size[0]
  row_diff = prev_size[1] - new_size[1]

  if (row_diff gt 0) then begin
      widget_control, tw, delete_rows=row_diff, $
        use_table_select=[0,new_size[1],prev_size[0]-1,prev_size[1]-1]
  endif else if (row_diff lt 0) then begin
      widget_control, tw, insert_rows=-row_diff
  endif

  if (col_diff gt 0) then begin
      widget_control, tw, delete_columns=col_diff, $
        use_table_select=[new_size[0],0,prev_size[0]-1,new_size[1]-1]
  endif else if (col_diff lt 0) then begin
      widget_control, tw, insert_columns=-col_diff
  endif

  if (keyword_set(text_table)) then begin

    ; the text table has a different look in STEP 3 than in STEP 1 & 2...
    ;
    if (keyword_set(last)) then $
;      widget_control, tw, column_widths=.75, units=1 $
      widget_control, tw, column_widths=90 $
    else begin
      widget_control, tw, column_widths=450, column_labels=['Text']
      widget_control, tw, column_widths=40, use_table_select=[-1,0,-1,0]
    endelse
  endif

end                     ; at_resize_table

; -----------------------------------------------------------------------------
;
;  Purpose:  Organize and display a sample record of n lines to demonstrate
;            how the current defined template interprets the ASCII file.
;
pro at_sample_record, data

  num_fields = *data.p_num_fields
  lines = (*data.p_rlines)[0:n_elements(num_fields)-1]

  new_twm = [max(num_fields), n_elements(num_fields)]

  at_resize_table, data.tw[6], data.twm, new_twm, /text_table, /last

  data.twm = new_twm
  str = strarr(new_twm[0], new_twm[1])
  fpos = 0

  ;  Loop for each field.
  ;
  for i=0, n_elements(num_fields)-1 do begin
    for j=0, num_fields[i]-1 do begin

      if (j eq num_fields[i]-1) then $        ; last field
        len = strlen(lines[i]) - (*data.p_fields)[fpos].loc $
      else $                                  ; not last field
        len = (*data.p_fields)[fpos+1].loc - (*data.p_fields)[fpos].loc - 1

      str[j,i] = strtrim(strmid(lines[i], (*data.p_fields)[fpos].loc, len),2)
      fpos = fpos + 1
    endfor
  endfor

  widget_control, data.tw[6], set_value=str

end                     ; at_sample_record

; -----------------------------------------------------------------------------
;
;  Purpose:  Organize and display the field specifications into a table widget.
;
pro at_set_list, data, just_highlight=just_highlight

  groups = *data.p_groups

  ;  If NOT just highlighting...
  ;
  if (keyword_set(just_highlight) eq 0) then begin

    dstr = ['Skip', 'Byte', 'Integer', 'Long', 'Floating', 'Double', $
            'Complex', 'String']

    ;  Set new table widget size.
    ;
    new_tws = [ ([3,2])[data.mode], long(total(*data.p_num_fields)) ]
    at_resize_table, data.tw[8], data.tws, new_tws
    data.tws = new_tws

    ;  String array, set to [3,1] in at_widget.
    ;  [0,0] =
    ;  [1,0] =
    ;  [2,0] =
    ;
    str = strarr(data.tws[0], data.tws[1])
    gptr = 0

    ;  Loop for each group.
    ;
    for i=0, n_elements(groups)-1 do begin

      if (i eq 0) then new_group = 1 $
      else             new_group = (groups[i] ne groups[i-1])

      ptr = where(groups eq groups[i], count)

      if (new_group) then begin
        gptr = i

        if (count eq 1) then str[0,i] = (*data.p_fields)[i].name $
        else                 str[0,i] = '{1} ' + (*data.p_fields)[i].name

        str[1,i] = dstr[(*data.p_fields)[i].type]

        if (data.mode eq 0) then $
          str[2,i] = strtrim(string((*data.p_fields)[i].loc),2)

      endif else begin

        str[0,i] = '{' + strtrim(string(i-gptr+1),2) + '}'

        if (data.mode eq 0) then $
          str[2,i] = strtrim(string((*data.p_fields)[i].loc),2)

      endelse

    endfor

    ;  Set column labels in main table widget.
    ;
    if (n_elements(*data.p_num_fields) eq 1) then $
      c_str = reform(str[0,*]) $
    else $
      c_str = strarr(data.twm[0]) + ' '
    widget_control, data.tw[6], column_label=c_str

    ;  Set value and row labels in upper-left table widget.
    ;
    rstr = strtrim(string(indgen(data.tws[1])+1),2)
    widget_control, data.tw[8], set_value=str, row_labels=rstr, alignment=0

  endif

  ;  Set table selection in upper-left table widget.
  ;
  widget_control, data.tw[8], set_table_select= $
    [0, data.lptr, data.tws[0]-1, data.lptr]

  ;  Set selection in main table widget corresponding to the field selected.
  ;
  lpos = at_which_field(*data.p_num_fields, data.lptr, col)
  widget_control, data.tw[6], set_table_select=[col,lpos,col,lpos]

  ;  Set sensitivity of Type droplist and Ungroup button.
  ;    (For Type, is sensitive if on the first entry of the group.)
  ;    (For Ungroup, is sensitive if on the first entry of the group,
  ;     and more than one entry in the group.)
  ;
  ptr = where(groups eq groups[data.lptr], count)
  widget_control, data.dl, sensitive=(ptr[0] eq data.lptr)
  widget_control, data.buts[14], sensitive=(ptr[0] eq data.lptr and count gt 1)

end                     ; at_set_list

; -----------------------------------------------------------------------------
;
;  Purpose:  Update the 3rd step page.
;
pro at_update, data, new_lptr, change=change, new=new

  if (n_elements(change) eq 0) then change = 0

  if (keyword_set(new) eq 0) then begin

    ;  Get Name.
    ;  Get Column number (used for Fixed Width data organization).
    ;
    widget_control, data.tw[4], get_value=name
    widget_control, data.tw[5], get_value=col

    ;  Check if name or column was changed.
    ;
    change = change or (name[0] ne (*data.p_fields)[data.lptr].name or $
                        long(col[0]) ne (*data.p_fields)[data.lptr].loc)

    (*data.p_fields)[data.lptr].name = name[0]
    (*data.p_fields)[data.lptr].loc = long(col[0])
  endif

  data.lptr = new_lptr

  ;  Update field table widget (just highlight if not changing values).
  ;
  at_set_list, data, just_highlight=(change eq 0)

  ;  Fill in Name in text widget.
  ;
  widget_control, data.tw[4], set_value=(*data.p_fields)[new_lptr].name

  ;  Set the Type droplist selection.
  ;
  widget_control, data.dl, set_droplist_select= $
    (*data.p_fields)[new_lptr].type

  ;  Set Column number (used for Fixed Width data organization).
  ;
  widget_control, data.tw[5], set_value= $
    strtrim(string((*data.p_fields)[new_lptr].loc),2)

end                     ; at_update

; -----------------------------------------------------------------------------
;
;  Purpose:  Handle events for third page of GUI.
;
pro at_3_event, ev

  widget_control, ev.id, get_uvalue=uvalue
  widget_control, ev.top, get_uvalue=data, /no_copy

  if (uvalue eq 'group') then begin

    ;  (Return selection is in form [left, top, right, bottom].)
    ;
    sel = widget_info(data.tw[8], /table_select)
    if (sel[1] ne sel[3]) then begin

      types = (*data.p_fields)[sel[1]:sel[3]].type
      is_string = (total(types eq 7) gt 0)
      if (is_string) then $
        (*data.p_fields)[sel[1]:sel[3]].type = (7 * (types ne 0)) $
      else $
        (*data.p_fields)[sel[1]:sel[3]].type = (max(types) * (types ne 0))
      (*data.p_groups)[sel[1]:sel[3]] = (*data.p_groups)[sel[1]]
      at_update, data, data.lptr, /change, /new
    endif

  endif

  if (uvalue eq 'ungroup') then begin
    ptr = where(*data.p_groups eq (*data.p_groups)[data.lptr])
    (*data.p_groups)[ptr] = (indgen(n_elements(*data.p_fields)))[ptr]
    (*data.p_fields)[ptr].type = (*data.p_types)[ptr]
    at_set_list, data
  endif

  if (uvalue eq 'ungroup all') then begin
    *data.p_groups = indgen(n_elements(*data.p_fields))
    (*data.p_fields).type = *data.p_types
    at_set_list, data
  endif

  if (uvalue eq 'ltable') then begin
    ; if selection event...
    if (ev.type eq 4) then $
      if (ev.sel_top eq ev.sel_bottom and ev.sel_top ne -1) then $
        at_update, data, ev.sel_top
  endif

  if (uvalue eq 'table') then begin
    ; if selection event...
    if (ev.type eq 4) then $
      if (ev.sel_top eq ev.sel_bottom and ev.sel_top ne -1) then begin
        tot_num_fields = long(total(*data.p_num_fields))
        if (ev.sel_top eq 0) then add = 0 $
        else add = long(total((*data.p_num_fields)[0:ev.sel_top-1]))
        new_ptr = (ev.sel_left + add) < (tot_num_fields-1)
        at_update, data, new_ptr, /change
      endif
  endif

  if (uvalue eq 'list') then begin
    if (ev.index lt n_elements(*data.p_fields)) then $
      at_update, data, ev.index
  endif

  if (uvalue eq 'name') then begin
    if (ev.type eq 0) then begin
      bad = (ev.ch lt 48 or (ev.ch gt 57 and ev.ch lt 65) or $
            (ev.ch gt 90 and ev.ch lt 97 and ev.ch ne 95) or ev.ch gt 122l)
      if (bad) then begin
        widget_control, ev.id, set_value=(*data.p_fields)[data.lptr].name
        widget_control, ev.id, set_text_select= $
          strlen((*data.p_fields)[data.lptr].name)
      endif
    endif
    if (keyword_set(bad) eq 0) then begin
      widget_control, ev.id, get_value=name
      if (name[0] ne (*data.p_fields)[data.lptr].name and $
          name[0] ne '') then begin
        (*data.p_fields)[data.lptr].name = name[0]
        at_set_list, data
      endif
    endif
  endif

  if (uvalue eq 'type') then begin
    ptr = where(*data.p_groups eq (*data.p_groups)[data.lptr], count)
    if (count eq 1) then begin
      (*data.p_fields)[data.lptr].type = ev.index
      (*data.p_types)[data.lptr] = ev.index
    endif else $
      (*data.p_fields)[ptr].type = (ev.index * ((*data.p_types)[ptr] ne 0))
    at_set_list, data
  endif

  if (uvalue eq 'location') then begin

    widget_control, ev.id, get_value=str
    (*data.p_fields)[data.lptr].loc = at_str_to_val(str)
    at_set_list, data

    ; Organize and display a sample record of n lines to demonstrate
    ; how the current defined template interprets the ASCII file.
    ;
    at_sample_record, data

  endif

  if (strmid(uvalue,0,4) eq 'miss') then begin
    data.miss_type = fix(strmid(uvalue,4,1))
    widget_control, data.mb[4], sensitive=data.miss_type
    if (data.miss_type eq 0) then $
      (*data.p_template).missing_value = !values.f_nan $
    else begin
      widget_control, data.tw[0], get_value=str
      (*data.p_template).missing_value = at_str_to_val(str, /floating)
    endelse
  endif

  if (uvalue eq 'value') then begin
    widget_control, ev.id, get_value=str
    (*data.p_template).missing_value = at_str_to_val(str, /floating)
  endif

  widget_control, ev.top, set_uvalue=data, /no_copy

end                     ; at_3_event

; -----------------------------------------------------------------------------
;
;  Purpose:  Handle events for second page of GUI.
;
pro at_2_event, ev

  widget_control, ev.id, get_uvalue=uvalue
  widget_control, ev.top, get_uvalue=data, /no_copy

  if (uvalue eq 'user' or strmid(uvalue,0,7) eq 'delimit') then begin

    if (uvalue eq 'user') then type = 5 $
    else                       type = fix(strmid(uvalue,7,1))
    data.delimit = ([9,59,32,44,58,32])[type]
    widget_control, data.mb[5], sensitive=(type eq 5)
    if (type eq 5) then begin
      widget_control, data.tw[1], get_value=str
      data.delimit = (byte(strmid(str[0],0,1)))[0]
    endif

    ;  Get the number of fields/line for each line in a record.
    ;
    num_fields = at_num_fields(*data.p_rlines, data.delimit, data.comment)
    widget_control, data.tw[2], set_value=at_list_to_str(num_fields)
    *data.p_num_fields = temporary(num_fields)

    data.change = 1
  endif

  if (uvalue eq 'fields') then begin

    ;  Get the number of fields/line for each line in a record.
    ;
    widget_control, ev.id, get_value=str
    if (strtrim(strcompress(str[0]),2) eq '') then str = '1'
    *data.p_num_fields = at_str_to_list(str)

    data.change = 1
  endif

  widget_control, ev.top, set_uvalue=data, /no_copy

end                     ; at_2_event

; -----------------------------------------------------------------------------
;
;  Purpose:  Handle events for first page of GUI.
;
pro at_1_event, ev

  widget_control, ev.id, get_uvalue=uvalue
  widget_control, ev.top, get_uvalue=data, /no_copy

  if (uvalue eq 'text') then begin
    widget_control, ev.id, get_value=str
    ds = (at_str_to_val(str)-1)
    if (ds ne data.data_start) then begin
      data.data_start = ds
      widget_control, data.tw[6], set_table_select= $
        [0,data.data_start<(data.twm[1]-1),0,data.data_start<(data.twm[1]-1)]
      data.change = 1
    endif
  endif

  if (uvalue eq 'comment') then begin
    widget_control, ev.id, get_value=str
    data.comment = str[0]
    data.change = 1
  endif

  if (strmid(uvalue,0,4) eq 'mode') then begin
    data.mode = fix(strmid(uvalue,4,1))
    data.change = 1
  endif

  ;  Handle table events.
  ;
  if (uvalue eq 'table') then begin
    ; if selection event...
    if (ev.type eq 4) then $
      if (ev.sel_left eq 0) then begin
        widget_control, data.tw[7], set_value= $
        strtrim(string(ev.sel_top+1),2)
        data.change = 1
      endif

  ;  Handle "Next n Lines" button event.
  ;
  endif else if (uvalue eq 'next set') then begin
    widget_control, /hourglass
    last_pos = data.last_pos
    new_lines = at_get_lines(data.name, data.browseLines, $
      last_pos=last_pos, end_reached=end_reached)
    data.last_pos = last_pos
    data.end_reached = end_reached
    widget_control, data.mb[7], sensitive=(end_reached eq 0)
    if (n_elements(new_lines) gt 1 or new_lines[0] ne '') then begin
      *data.p_lines = [*data.p_lines, new_lines]
      widget_control, data.tw[6], insert_rows=n_elements(new_lines)
      at_display_text, data, /first
    endif
  endif

  widget_control, ev.top, set_uvalue=data, /no_copy

end                     ; at_1_event

; -----------------------------------------------------------------------------
;
;  Purpose:  Control the setting of the three progessive states of the
;            template definition (sets all of the widgets and pointers
;            involved and allows movement both forward and backward).
;
pro at_set_state, $
    data, $             ; IN: 
    forward=forward, $  ; IN: (opt)
    back=back           ; IN: (opt) [currently not used]

  case (data.step) of

    ; ----------------------------------------
    0: begin        ; STEP 1
    ; ----------------------------------------

      title = 'STEP 1 of 3:  Define Data Type / Range'

      widget_control, data.base, tlb_set_title=title
      widget_control, data.buts[9], sensitive=0
      widget_control, data.buts[0], set_button=(data.mode eq 0)
      widget_control, data.buts[1], set_button=data.mode
      widget_control, data.buts[13], sensitive=0

      widget_control, data.buts[2], set_button=(data.miss_type eq 0)
      widget_control, data.buts[3], set_button=data.miss_type

      widget_control, data.mb[4], sensitive=data.miss_type

      widget_control, data.slab, set_value='Selected Text File'
      widget_control, data.mb[7], sensitive=(data.end_reached eq 0), map=1

      widget_control, data.tw[7], set_value= $
        strtrim(string(data.data_start+1),2)
      widget_control, data.tw[6], event_pro='at_1_event'
      at_display_text, data, /first

      data.change = 0

    end

    ; ----------------------------------------
    1: begin        ; STEP 2
    ; ----------------------------------------

      ;  Retreive the data_start value.
      ;
      widget_control, data.tw[7], get_value=str
      data.data_start =(at_str_to_val(str)-1) > 0

      ;  Scan through lines and remove comments and blank lines for
      ;  steps two and three (after the start of the data).
      ;
      if (keyword_set(forward)) then begin
        lines = *data.p_lines
        rlines = strarr(n_elements(lines))
        count = 0
        for i=data.data_start, n_elements(lines)-1 do begin
          line = lines[i]
          if (data.comment ne '') then begin
            pos = strpos(line, data.comment)
            if (pos[0] ne -1) then line = strmid(line, 0, pos[0]-1)
          endif
          if (strtrim(line,2) ne '') then begin
            rlines[count] = line
            count = count + 1
          endif
        endfor
        if (count gt 0) then  *data.p_rlines = rlines[0:count-1] $
        else                  *data.p_rlines = ''
      endif

      widget_control, data.mb[3], map=data.mode
      widget_control, data.buts[9], sensitive=1
      widget_control, data.buts[10], sensitive=1
      widget_control, data.buts[13], sensitive=0
      widget_control, data.mb[7], map=0

      widget_control, data.slab, set_value='Selected Text Records'
      if (data.mode eq 1) then begin
        if (keyword_set(forward)) then $
          data.delimit = at_default_delimit((*data.p_rlines)[0], data.comment)

        title = 'STEP 2 of 3:  Define Delimiter / Fields'

        widget_control, data.base, tlb_set_title=title
        user_defined = (total([59b,58b,32b,44b] eq data.delimit) eq 0)
        if (user_defined) then tstr = string(data.delimit) $
        else                   tstr = ''
        widget_control, data.tw[1], set_value=tstr
        widget_control, data.mb[5], sensitive=user_defined
        widget_control, data.buts[5], set_button=(data.delimit eq 59b)
        widget_control, data.buts[6], set_button=(data.delimit eq 32b)
        widget_control, data.buts[7], set_button=(data.delimit eq 44b)
        widget_control, data.buts[11],set_button=(data.delimit eq 9b)
        widget_control, data.buts[16], set_button=(data.delimit eq 58b)
        widget_control, data.buts[8], set_button=user_defined
      endif else begin

        title = 'STEP 2 of 3:  Define Fields'

        widget_control, data.base, tlb_set_title=title
      endelse

      if ((keyword_set(forward) and data.change) or $
          n_elements(*data.p_num_fields) eq 0) then $
        num_fields = at_num_fields(*data.p_rlines, data.delimit,data.comment) $
      else begin
        num_fields = *data.p_num_fields
        new_twm = [2, n_elements(*data.p_lines)]
        at_resize_table, data.tw[6], data.twm, new_twm, /text_table
        data.twm = new_twm
      endelse

      widget_control, data.tw[2], set_value=at_list_to_str(num_fields)
      *data.p_num_fields = temporary(num_fields)

      widget_control, data.tw[6], event_pro='at_2_event'
      at_display_text, data
      data.change = 0
    end

    ; ----------------------------------------
    2: begin        ; STEP 3
    ; ----------------------------------------

      title = 'STEP 3 of 3:  Field Specification'

      widget_control, data.base, tlb_set_title=title
      widget_control, data.buts[10], sensitive=0
      widget_control, data.mb[6], map=(data.mode eq 0)
      widget_control, data.buts[13], sensitive=1
      widget_control, data.slab, set_value='Sample Record'

      if (data.miss_type eq 0) then $
        miss_value = !values.f_nan $
      else begin
        widget_control, data.tw[0], get_value=str
        miss_value = at_str_to_val(str)
      endelse

      if (n_elements(*data.p_template) eq 0 or data.change) then begin

        ;  Build new template.
        ;  Delete previous template.
        ;  Save new template
        ;
        template = at_build_template(data, miss_value)
        at_delete_template, *data.p_template
        *data.p_template = temporary(template)

        ;  Assign a copy of pointers from the template to the data structure
        ;  for simpler access.
        ;
        data.p_fields = (*data.p_template).p_fields
        data.p_groups = (*data.p_template).p_groups
        *data.p_types = (*data.p_fields).type
      endif

      ;  Organize and display a sample record of n lines to demonstrate
      ;  how the current defined template interprets the ASCII file.
      ;
      at_sample_record, data

      widget_control, data.tw[6], event_pro='at_3_event'
      data.lptr = 0
      at_update, data, data.lptr, /change, /new
    end

  endcase

end                     ; at_set_state

; -----------------------------------------------------------------------------
;
;  Purpose:
;
pro at_widget_cleanup, base

  widget_control, base, get_uvalue=data, /no_copy
  widget_control, base, /destroy
  if (n_elements(data) eq 0) then return

  ptr_free, data.p_lines
  ptr_free, data.p_rlines
  ptr_free, data.p_num_fields
  ptr_free, data.p_template
  ptr_free, data.p_types

end                     ; at_widget_cleanup

; -----------------------------------------------------------------------------
;
;  Purpose:  Handle the buttons at the bottom of the dialog.
;
pro at_widget_event, ev

  if (tag_names(ev,/struct) eq 'WIDGET_KILL_REQUEST') then begin
    at_widget_cleanup, ev.top
    return
  endif

  widget_control, ev.id, get_uvalue=uvalue
  widget_control, ev.top, get_uvalue=data, /no_copy

  if (uvalue eq 'next') then begin
    widget_control, data.mb[data.step], map=0
    data.step = data.step + 1
    widget_control, data.mb[data.step], map=1
    at_set_state, data, /forward
  endif

  if (uvalue eq 'back') then begin
    widget_control, data.mb[data.step], map=0
    data.step = data.step - 1
    widget_control, data.mb[data.step], map=1
    at_set_state, data, /back
  endif

  if (uvalue eq 'finish') then begin
    *data.p_result = {accept:1, template:*data.p_template}
    widget_control, ev.top, set_uvalue=data, /no_copy
    at_widget_cleanup, ev.top
    return
  endif

  if (uvalue eq 'cancel') then begin
    at_delete_template, *data.p_template
    widget_control, ev.top, set_uvalue=data, /no_copy
    at_widget_cleanup, ev.top
    return
  endif

  widget_control, ev.top, set_uvalue=data, /no_copy

end                     ; at_widget_event

; -----------------------------------------------------------------------------
;
;  Purpose:  Create the widgets.
;
function at_widget, $
    name, $                    ; IN:
    GROUP=group, $             ; IN: (opt)
    CANCEL=cancel, $           ; OUT:
    BROWSE_LINES=browseLines   ; IN:

  ;  Set number of lines for browse button.
  ;
  if (N_ELEMENTS(browseLines) ne 0) then browseLinesUse = browseLines $
  else                                   browseLinesUse = 50

  ;  Check out the size of the screen and adjust the widget acordingly.
  ;
  device, get_screen_size=screen_size

  table_scr_ysize = ([110,210])[screen_size[1] gt 600]
  xoff = ((screen_size[0] - 600) / 2) > 0
  yoff = ((screen_size[1] - 600) / 2) > 0

  dt_str = ['Skip Field', 'Byte', 'Integer', 'Long Integer', $
    'Floating Point', 'Double Precision', 'Complex', 'String']

  lines = at_get_lines(name, browseLinesUse, last_pos=last_pos, $
    end_reached=end_reached)

  buts = lonarr(17)
  tw = lonarr(10)
  mb = lonarr(8)
  title = 'STEP 1 of 3:  Define Data Type / Range'

  ;  Create a group if one wasn't specified.
  ;
  if (N_ELEMENTS(group) eq 0) then begin
        group = widget_base()
        groupCreated = 1B
  endif else $
        groupCreated = 0B
  base = widget_base(title=title, /column, xoff=xoff, yoff=yoff, $
    /modal, group=group, /tlb_frame_attr)
  sb   = widget_base(base, /column)
  mbs  = widget_base(sb)

  ; -----------------------------------------------
  ;  STEP 1 of 3: Define Data Type / Range
  ; -----------------------------------------------

  mb[0]= widget_base(mbs, /column, event_pro='at_1_event')
  sb   = widget_base(mb[0], /column, /frame)
  lab  = widget_label(sb, $
    value='Choose the field type which best describes your data:')
  lab  = widget_label(sb, value='')
  sb1  = widget_base(sb, /column, /exclusive)
  buts[0]= widget_button(sb1, /no_release, uvalue='mode0', $
    value=' Fixed Width  (fields are aligned in columns)')
  buts[1]= widget_button(sb1, /no_release, uvalue='mode1', $
    value=' Delimited  (commas, whitespace, etc. separate each field)')

  sb   = widget_base(mb[0], /row, /frame)
  lab  = widget_label(sb, value=' Comment String to Ignore:  ')
  tw[9]= widget_text(sb, xs=10, ys=1, /edit, frame=0, /all_events, $
    uvalue='comment')

  sb   = widget_base(mb[0], /row, /frame)
  lab  = widget_label(sb, value=' Data Starts at Line:  ')
  tw[7]= widget_text(sb, xs=5, ys=1, /edit, frame=0, /all_events, $
    uvalue='text')

  ; -----------------------------------------------
  ;  STEP 2 of 3: Define Delimiter / Fields
  ; -----------------------------------------------

  mb[1]= widget_base(mbs, /column, map=0, event_pro='at_2_event')
  sb   = widget_base(mb[1], /column, /frame)
  sb1  = widget_base(sb, /row)
  lab = widget_label(sb1, value='Number of Fields Per Line:  ')
  tw[2]= widget_text(sb1, xs=10, ys=1, frame=0, /edit, /all_events, $
    uvalue='fields')

  mb[3]= widget_base(mb[1], /column, /frame)
  lab  = widget_label(mb[3], value='Delimiter Between Data Elements:')
  sb1  = widget_base(mb[3], /row)
  sb2  = widget_base(sb1, row=2, /exclusive)
  mb[5]= widget_base(sb1, /row, /align_bottom)
  tw[1]= widget_text(mb[5], xs=2, ys=1, /edit, frame=0, $
    /all_events, uvalue='user')
  buts[6] = widget_button(sb2, value='White Space', uvalue='delimit2', /no_rel)
  buts[7] = widget_button(sb2, value='Comma',       uvalue='delimit3', /no_rel)
  buts[16]= widget_button(sb2, value='Colon',       uvalue='delimit4', /no_rel)
  buts[5] = widget_button(sb2, value='Semicolon',   uvalue='delimit1', /no_rel)
  buts[11]= widget_button(sb2, value='Tab',         uvalue='delimit0', /no_rel)
  buts[8] = widget_button(sb2, value='Other:',      uvalue='delimit5', /no_rel)

  ; -----------------------------------------------
  ;  STEP 3 of 3: Field Specifications
  ; -----------------------------------------------

  mb[2]= widget_base(mbs, /column, map=0, event_pro='at_3_event')
  sb   = widget_base(mb[2], column=2)

  ;  Field information table widget (upper-left).
  ;
  sb1  = widget_base(sb, /column, /frame)
  tw[8]= widget_table(sb1, value=strarr(3,1), alignment=0, scr_xsize=225, $
    scr_ysize=150, uvalue='ltable', column_widths=[100,75,50], $
    column_labels=['Name','Data Type','Loc'], /ALL_EVENTS, /SCROLL, $
    /RESIZEABLE_COLUMNS)
  widget_control, tw[8], column_widths=25, use_table_select=[-1,0,-1,0]

  sb1  = widget_base(sb, /column, /frame)
  sb2  = widget_base(sb1, /row)
  lab  = widget_label(sb2, value='Name:  ')
  tw[4]= widget_text(sb2, xs=18, ys=1, frame=0, /edit, /all_events, $
    uvalue='name')
  sb2  = widget_base(sb1, /row)
  dl   = widget_droplist(sb2, Title='Type:  ', value=dt_str, uvalue='type')

  ; ("Column" is used for Fixed Width data organization.)
  mb[6]= widget_base(sb1, /row)
  lab  = widget_label(mb[6], value='Column:  ')
  tw[5]= widget_text(mb[6], xs=3, ys=1, frame=0, /edit, /all_events, $
    uvalue='location')

  sb2  = widget_base(sb1, /row)
  but  = widget_button(sb2, value=' Group ', uvalue='group')
  buts[14] = widget_button(sb2, value=' UnGroup ', uvalue='ungroup')
  but  = widget_button(sb2, value=' Ungroup All ', uvalue='ungroup all')

  sb   = widget_base(mb[2], /row, /frame)
  lab  = widget_label(sb, value=' Value to Assign to Missing Data:  ')
  sb1  = widget_base(sb, /row)
  sb2  = widget_base(sb1, /row, /exclusive)
  buts[2]= widget_button(sb2, value='IEEE NaN ', /no_rel, uvalue='miss0')
  buts[3]= widget_button(sb2, value='', /no_rel, uvalue='miss1')
  mb[4]  = widget_base(sb1, /row)
  tw[0]= widget_text(mb[4], xs=7, ys=1, frame=0, /edit, /all_events, $
    uvalue='value')

  ; -----------------------------------------------

  ; general text widget and <back, next> buttons
  ;
  sb   = widget_base(base, /column)
  sb1  = widget_base(sb, /row)
  slab = widget_label(sb1, value='Selected Text File       ')

  mb[7]= widget_base(sb1, /row)
  widget_control, mb[7], sensitive=(end_reached eq 0)

  value = '  Read in Next ' + STRTRIM(STRING(browseLinesUse),2) + ' Lines  '
  but  = widget_button(mb[7], value=value, uvalue='next set', $
    event_pro='at_1_event')
  str  = strarr(1,n_elements(lines))
  tw[6]= widget_table(sb, value=str, alignment=0, scr_xsize=500, $
    scr_ysize=table_scr_ysize, uvalue='table', column_labels=['Text'], $
    column_widths=450, /ALL_EVENTS, /SCROLL, /RESIZEABLE_COLUMNS)
  widget_control, tw[6], column_widths=40, use_table_select=[-1,0,-1,0]

  sb   = widget_base(base, /row, /align_right)
  sb1  = widget_base(sb, /row, /frame)
  but  = widget_button(sb1, value=' Cancel ', uvalue='cancel')
  buts[9] = widget_button(sb1, value=' < Back ', uvalue='back')
  buts[10]= widget_button(sb1, value=' Next > ', uvalue='next')
  buts[13]= widget_button(sb1, value=' Finish ', uvalue='finish')
  widget_control, base, /realize
  widget_control, tw[8], /input_focus

  p_result = ptr_new({accept:0})

  ;  Set up widget state information.
  ;
  data = { $
    base: base, $
    mb: mb, $
    tw: tw, $
    buts: buts, $
    dl: dl, $
    step: 0, $
    name: name, $
    p_lines: ptr_new(lines), $
    p_rlines: ptr_new(/allocate_heap), $
    p_num_fields: ptr_new(/allocate_heap), $
    p_template: ptr_new(/allocate_heap), $
    p_types: ptr_new(/allocate_heap), $
    p_fields: ptr_new(), $
    p_groups: ptr_new(), $
    p_result: p_result, $
    mode: 1, $
    data_start: 0L, $
    lptr: 0, $
    delimit: 32b, $
    miss_type: 0, $
    last_pos: last_pos, $
    change: 1, $
    end_reached: end_reached, $
    comment: '', $
    twm: [1,n_elements(lines)], $
    tws: [3,1], $
    slab: slab, $
    browseLines: browseLinesUse $
    }

  at_set_state, data
  widget_control, base, set_uvalue=data, /no_copy
  xmanager, 'at_widget', base

  if (groupCreated) then $
        widget_control, group, /destroy

  cancel = ((*p_result).accept eq 0)
  if ((*p_result).accept) then template = (*p_result).template $
  else                         template = 0
  ptr_free, p_result
  return, template

end                     ; at_widget

; -----------------------------------------------------------------------------
;
;  Purpose: Check that the input filename is a string, exists, and appears
;           to be ASCII...
;
function at_check_file, fname

  catch, error_status
  if (error_status ne 0) then begin
    if (n_elements(unit) gt 0) then free_lun, unit
    return, -3 ; unexpected error reading from file
  endif

  if (size(fname,/type) ne 7) then return, -1 ; filename isn't a string

  openr, unit, fname, error=error, /get_lun
  if (error eq 0) then begin
    finfo = fstat(unit)

    ; set non-ascii values in lookup table
    ;
    lut = bytarr(256) + 1b
    lut[7:13]   = 0b
    lut[32:127] = 0b

    data = bytarr(32767<finfo.size, /nozero)
    readu, unit, data
    free_lun, unit
    carriage_return = (total(data eq 10b) gt 0 or total(data eq 13b) gt 0)
    if (carriage_return eq 0) then return, -4 ; looks like a binary file
    non_printable   = (total(lut[data]) gt 0)

    if (non_printable) then return, -4 $ ; looks like a binary file
    else                    return, 0    ; everything is cool

  endif else $
    return, -2 ; unable to open file

end                     ; at_check_file

; -----------------------------------------------------------------------------
;
;  Purpose:  The main routine.
;
function ascii_template, $
    file, $                     ; IN: (opt)
    BROWSE_LINES=browseLines, $ ; IN: (opt)
    GROUP=group, $              ; IN: (opt)
    CANCEL=cancel               ; OUT: (opt)

  ;  Set to return to caller on error.
  ;
  ON_ERROR, 2
;  ON_ERROR, 0

  ;  Set some defaults.
  ;
  cancel = 0

  ;  Set number of lines for browse button.
  ;
  if (N_ELEMENTS(browseLines) ne 0) then browseLinesUse = browseLines $
                                    else browseLinesUse = 50

  ;  If no file specified, use DIALOG_PICKFILE.
  ;
  if (n_elements(file) eq 0) then begin
    file = DIALOG_PICKFILE(/MUST_EXIST, GROUP=group)
    if (file eq '') then RETURN, 0
  endif

  ; check that the file is readable and appears to be ASCII
  ;
  ret = at_check_file(file)
  case ret of
    -1: MESSAGE, 'File name must be a string.'
    -2: MESSAGE, 'File "' + file + '" not found.'
    -3: MESSAGE, 'Error Reading from file "' + file + '"
    -4: MESSAGE, 'File "' + file + '" is not an ASCII file.'
    else:
  endcase

  ;  Put up the GUI.
  ;
  templateWithPtrs = $
    at_widget(file, cancel=cancel, BROWSE_LINES=browseLinesUse, GROUP=group)

  ;  If user canceled, return 0.
  ;
  if (cancel) then RETURN, 0

  ;  Restructure template to eliminate pointers and return it.
  ;  (Include a version number for easier processing if modify
  ;  the template definition later.)
  ;
  template = { $
    version:            1.0, $
    dataStart:          templateWithPtrs.record_start_loc, $
    delimiter:          templateWithPtrs.delimit, $
    missingValue:       templateWithPtrs.missing_value, $
    commentSymbol:      templateWithPtrs.comment_symbol, $
    fieldCount:         *templateWithPtrs.p_num_fields, $
    fieldTypes:         (*templateWithPtrs.p_fields).type, $
    fieldNames:         (*templateWithPtrs.p_fields).name, $
    fieldLocations:     (*templateWithPtrs.p_fields).loc, $
    fieldGroups:        *templateWithPtrs.p_groups $
    }
  at_delete_template, templateWithPtrs
  RETURN, template

end                     ; ascii_template

; -----------------------------------------------------------------------------