; $Id: dm_filesel__define.pro,v 1.37 2008/02/19 18:39:44 ymqiu Exp $
;#######################################################################
;
; NAME:
;  dm_filesel
;
; PURPOSE:
;  This program defines a file selction object tailored for dcs2spe program.                                                         *
;
; CATEGORY:
;  general
;
; AUTHOR:
;  Yiming Qiu
;  NIST Center for Neutron Research
;  100 Bureau Drive, Gaithersburg, MD 20899-6102
;  United States
;  yiming.qiu@nist.gov
;  February, 2008
;
; LICENSE:
;  The software in this file is written by an employee of
;  National Institute of Standards and Technology
;  as part of the DAVE software project.
;
;  The DAVE software package is not subject to copyright protection
;  and is in the public domain. It should be considered as an
;  experimental neutron scattering data reduction, visualization, and
;  analysis system. As such, the authors assume no responsibility
;  whatsoever for its use, and make no guarantees, expressed or
;  implied, about its quality, reliability, or any other
;  characteristic. The use of certain trade names or commercial
;  products does not imply any endorsement of a particular product,
;  nor does it imply that the named product is necessarily the best
;  product for the stated purpose. We would appreciate acknowledgment
;  if the DAVE software is used or if the code in this file is
;  included in another product.
;
;#######################################################################

; Usage:  filesel=obj_new('dm_filesel',parent=parent,path=path,$   *
;           multi=multi,write=write,filter=filter,frame=frame,$    *
;           ysize=ysize,ypad=ypad,xsize=xsize,group_leader=group_leader)                     *

pro dm_filesel_Exit,tlb
    ;leave the job to obj_destroy
end


;*******************************************************************
;standard error handling functionality
;copied from Fanning
;*******************************************************************
function dm_filesel_error_message,theMessage,Error=error,Informational=information,Traceback=traceback,NoName=noname,$
    Title=title,_Extra=extra

    On_Error, 2
    ; Check for presence and type of message.
    IF N_Elements(theMessage) EQ 0 THEN theMessage = !Error_State.Msg
    s = Size(theMessage)
    messageType = s[s[0]+1]
    IF messageType NE 7 THEN BEGIN
       Message, "The message parameter must be a string.",_Extra=extra
    ENDIF
    ; Get the call stack and the calling routine's name.
    Help, Calls=callStack
    IF Float(!Version.Release) GE 5.2 THEN $
       callingRoutine = (StrSplit(StrCompress(callStack[1])," ",/Extract))[0] $
    ELSE $
       callingRoutine = (Str_Sep(StrCompress(callStack[1])," "))[0]
    ; Are widgets supported?
    widgetsSupported = ((!D.Flags AND 65536L) NE 0)
    IF widgetsSupported THEN BEGIN
    ;If this is an error produced with the MESSAGE command,
    ;it is a trapped error and will have the name "IDL_M_USER_ERR".
       IF !ERROR_STATE.NAME EQ "IDL_M_USER_ERR" THEN BEGIN
          IF N_Elements(title) EQ 0 THEN title = 'Trapped Error'
          ; If the message has the name of the calling routine in it,
          ; it should be stripped out.
          ;Can you find a colon in the string?
          colon = StrPos(theMessage, ":")
          IF colon NE -1 THEN BEGIN
             ; Extract the text up to the colon. Is this the same as
             ; the callingRoutine? If so, strip it.
             IF StrMid(theMessage, 0, colon) EQ callingRoutine THEN $
                theMessage = StrMid(theMessage, colon+1)
          ENDIF
          ; Add the calling routine's name, unless NONAME is set.
          IF Keyword_Set(noname) THEN BEGIN
             answer = Dialog_Message(theMessage, Title=title, $
                _Extra=extra, Error=error, Information=information)
          ENDIF ELSE BEGIN
             answer = Dialog_Message(StrUpCase(callingRoutine) + ": " $
                + theMessage, Title=title, _Extra=extra, Error=error, Information=information)
          ENDELSE
       ENDIF ELSE BEGIN
          ; Otherwise, this is an IDL system error.
          IF N_Elements(title) EQ 0 THEN title = 'System Error'
          IF StrUpCase(callingRoutine) EQ "$MAIN$" THEN $
             answer = Dialog_Message(theMessage, _Extra=extra,Title=title, Error=error, $
                Information=information) $
          ELSE $
             IF Keyword_Set(noname) THEN BEGIN
                answer=Dialog_Message(theMessage, _Extra=extra, $
                    Title=title,Error=error, Information=information)
             ENDIF ELSE BEGIN
                answer = Dialog_Message(StrUpCase(callingRoutine) + $
                    "--> " + theMessage, _Extra=extra, Title=title, $
                    Error=error, Information=information)
             ENDELSE
       ENDELSE
    ENDIF ELSE BEGIN
       Message, theMessage, /Continue, /NoPrint, /NoName,/NoPrefix, _Extra=extra
       Print, '%' + callingRoutine + ': ' + theMessage
       answer = 'OK'
    ENDELSE

    ; Provide traceback information if requested.
    IF Keyword_Set(traceback) THEN BEGIN
       Help, /Last_Message, Output=traceback
       Print,''
       Print, 'Traceback Report from ' +StrUpCase(callingRoutine) + ':'
       Print, ''
       FOR j=0,N_Elements(traceback)-1 DO Print, "     " + traceback[j]
    ENDIF
    RETURN, answer
end

;widget event handler
pro dm_filesel_event,event
    widget_control,event.handler,get_uvalue=self
    if obj_valid(self) then self->event,event
end

; Check if at top of directory, copied from cw_filesel.pro
function dm_filesel::atTop, path
    compile_opt hidden, strictarr

    CASE !version.os_family OF
        'Windows': atTop = (STRMID(path, STRLEN(path)-2) EQ ':\')
        else:  atTop = (path EQ '/')
    ENDCASE
    RETURN, atTop
end

;*******************************************************************
;clear the file selection
;syntax:
;   filesel->clear_file
;arguments:
;   None
;keywords:
;   bottom: if set, go to the bottom of the file after clearing the selection
;*******************************************************************
pro dm_filesel::clear_file,bottom=bottom
    if ptr_valid(self.file) then begin
       ptr_free,self.file
       widget_control,self.fileList,set_list_select=-1
       if self.write then $
          self->my_widget_control,'saveFile',set_value=''
    endif
    if keyword_set(bottom) then begin
       if ptr_valid(self.dFiles) then nFiles=n_elements(*self.dFiles)    $
       else nFiles=0
       geom = widget_info(self.fileList,/geom)
       widget_control,self.fileList,set_list_top=nFiles-geom.ysize+1
    endif
    cd,self.root
end


;read dcs file header info
pro dm_filesel::dcsfileheader,name
    catch, anyerror
    if anyerror ne 0 then begin
       catch,/cancel
       ok = dm_filesel_error_message(traceback=1,/error)
       if ptr_valid(davePtr) then heap_free,davePtr
       return
    end
    files   = strtrim(*self.file,2)
    index   = where(strpos(files,'dcs') ge 0,count)
    if count eq 0 then return $
    else files = self.path+self.pathsep+files[index]
    davePtr = dm_define_pointer() & omithisto=2 & printinfo=0 & filetype='binary'
    shutter = ['unknown','closed','open']
    resl    = ['','low','medium','high']
    for i=0, (30<(n_elements(files)-1)) do begin
        line = (*self.file)[i]+' : '
        dcs_readdatafile,files[i],omithisto,printinfo,filetype,davePtr=davePtr
        this = dm_locate_datastrptr(davePtr)
        if name eq 'temperature' then begin
           temp_sample  = (*(*this).specificPtr).temp_sample
           temp_control = (*(*this).specificPtr).temp_control
           postemp = where(temp_sample gt 0,count)
           if count ne 0 then begin
              temp_sample  = temp_sample[postemp]
              temp_control = temp_control[postemp]
           endif
        endif
        case name of
            'comment':        line = line+strtrim((*(*this).specificPtr).comments,2)
            'wavelength':     line = line+string((*(*this).specificPtr).ch_wl,format='(f5.2)')+' '+string('c5'XB)+' '+$
                                 resl[(*(*this).specificPtr).ch_res]+' '+$
                                 dm_to_string((*(*this).specificPtr).ch_ms)+' '+$
                                 dm_to_string((*(*this).specificPtr).ch_srmode eq 1?1:((*(*this).specificPtr).ch_srdenom-1))+'/'+$
                                 dm_to_string((*(*this).specificPtr).ch_srdenom)+' tsdmin='+$
                                 dm_to_string((*(*this).specificPtr).tsdmin)
            'duration':       line = line+string((*(*this).specificPtr).duration/3600.,format='(f6.2," hour")')
            'temperature':    line = line+' [sample] '+dm_to_string(fix(mean(temp_sample)*1000+0.5,type=3)/1000.)+' K  '+$
                                 '[control] '+dm_to_string(fix(mean(temp_control)*1000.+0.5,type=3)/1000.)+' K  '+$
                                 '[setpoint] '+dm_to_string((*(*this).specificPtr).temp_setpoint)+' K'
            'shutter state':  line = line+shutter[(*(*this).specificPtr).shutter_stat+1]
            'sample position':line = line+'A2='+dm_to_string((*(*this).specificPtr).motor_pos[1])+string('b0'XB)+'  x='+$
                                 dm_to_string((*(*this).specificPtr).motor_pos[3])+' mm  y='+$
                                 dm_to_string((*(*this).specificPtr).motor_pos[4])+' mm  z='+$
                                 dm_to_string((*(*this).specificPtr).motor_pos[2])+' mm  xtilt='+$
                                 dm_to_string((*(*this).specificPtr).motor_pos[5])+string('b0'xb)+' ytilt='+$
                                 dm_to_string((*(*this).specificPtr).motor_pos[6])+string('b0'xb)
            else:
        endcase
        if n_elements(info) eq 0 then info=line else info=[info,line]
    endfor
    if i lt n_elements(files) then info=[info,'      ...','      ...','      ...']
    heap_free,davePtr
    ok = dialog_message(info,/info,dialog_parent=self.tlb,title='DCS file info: ['+name+']')
end

;read inx file header info
pro dm_filesel::inxfileheader,name
    catch, anyerror
    if anyerror ne 0 then begin
       catch,/cancel
       ok = dm_filesel_error_message(traceback=1,/error)
       return
    end
    files   = strtrim(*self.file,2)
    index   = where(strpos(files,'inx') ge 0,count)
    if count eq 0 then return $
    else files = self.path+self.pathsep+files[index]
    for i=0, (30<(n_elements(files)-1)) do begin
        line = (*self.file)[i]+' : '
        openr,unit,files[i],/get_lun,error=fileerror
        if fileerror ne 0 then begin
           ok = dialog_message("Can't open "+file,/error,dialog_parent=self.tlb)
           return
        endif
        dm_load_inx,unit,ei=ei,comment=comment,temperature=temperature,tchannel=tchannel,/headeronly
        case name of
            'comment':         line = line+strtrim(comment,2)
            'incident energy': line = line+dm_to_string(ei)+' meV'
            'mean temperature':line = line+string(temperature,format='(f7.2)')+' K'
            'time channel':    line = line+' [total] '+dm_to_string(tchannel[0])+' [actual] '+$
                                      dm_to_string(tchannel[1])
            else:
        endcase
        if n_elements(info) eq 0 then info=line else info=[info,line]
    endfor
    if i lt n_elements(files) then info=[info,'      ...','      ...','      ...']
    ok = dialog_message(info,/info,dialog_parent=self.tlb,title='INX file info: ['+name+']')
end

;*******************************************************************
;object event handler
;*******************************************************************
pro dm_filesel::event,event
    compile_opt IDL2    ;,strictarrsubs
                        ;idl2=defint32,strictarr
                        ;strictarr:   [] to index array
                        ;strictarrsubs: error when out-of-range indices,IDL5.6 or after
    widget_control,/hourglass
    ;error handling
    catch, anyerror
    if anyerror ne 0 then begin
       catch,/cancel
       ok = dm_filesel_error_message(traceback=1,/error)
       return
    end
    eventname = widget_info(event.id,/uname)
    type = tag_names(event,/structure)
    case strlowcase(type) of
        'widget_context': eventname='fileInfo'
        else:
    endcase
    case strlowcase(eventname) of
       'dripull': begin
            drives = get_drive_list()
            if (drives[0] ne '') and (self -> isDir(drives[event.index],/showmesg)) then begin
               cd,drives[event.index]
               cd,current=currdir
               self->set_path,currdir
               self->clear_file
            endif
          end
       'upbut':begin
            cd,self.path
            cd,self.upDir
            cd,current=current
            self->set_path,current
            self->clear_file
          end
       'refreshbut':begin
            self->set_path
            self->clear_file,/bottom
          end
       'pathlab':begin
            widget_control,event.id,get_value=newPath
            self->set_path,newPath[0]
            self->clear_file
          end
       'limitfrom':begin
            widget_control,event.id,get_value=tmp
            tmp = strtrim(tmp,2)
            if self.limitfrom ne tmp then begin
               self.limitfrom = tmp
               self->set_path
            endif
          end
       'limitto':begin
            widget_control,event.id,get_value=tmp
            tmp = strtrim(tmp,2)
            if self.limitto ne tmp then begin
               self.limitto = tmp
               self->set_path
            endif
          end
       'filelist':begin
            dFiles   = *self.dFiles
            selected = widget_info(event.id,/list_select) & tmp=where(selected ge 0,n_item)
            if n_item eq 0 then break  ;nothing selected
            isDir = self->isDir(dFiles[selected],/fast)
            case(event.clicks) of
                1:  begin ;single click
                    self->set_file  ;clear file pointer
                    index = where(isDir eq 0,count)
                    if count ne 0 then self->set_file,selected[index]
                    end
                2:  begin ;double click
                    if isDir[0] then begin
                       cd,self->getproperty(/path)
                       cd,dFiles[event.index]
                       cd,current=currDir
                       self->set_path,currDir
                       self->clear_file
                    endif else self->set_file,event.index
                    end
                else:
            endcase
          end
       'fileinfo':begin
            self->getproperty,file=file
            if n_elements(file) eq 0 then begin
               menuid = widget_info(event.id, find_by_uname='contextMenu2')
            endif else begin
               if (strlowcase(self.filter) ne '.dcs') and (strlowcase(self.filter) ne '.inx') then break
               nofile = 1b
               for i=0,n_elements(file)-1 do $
                   if strpos(strlowcase(file[i]),strlowcase(self.filter)) ne -1 then begin
                      nofile = 0b
                      break
                   endif
               if nofile then break
               menuid = widget_info(event.id, find_by_uname='contextMenu1')
            endelse
            widget_displaycontextmenu,event.id,event.x,event.y,menuid
          end
       'newfilter': begin
            newfilter = dm_dialog_input('New Filter:',default=self.filter,title='New File Type',$
               info='e.g., .dcs and .*',dialog_parent=self.group_leader)
            if newfilter ne '' then begin
               if strupcase(newfilter) ne strupcase(self.filter) then $
                  self->set_filter,newfilter
            endif
          end
       'dcs_comment':       self->dcsfileheader,'comment'
       'dcs_wavelength':    self->dcsfileheader,'wavelength'
       'dcs_duration':      self->dcsfileheader,'duration'
       'dcs_temperature':   self->dcsfileheader,'temperature'
       'dcs_shutterstate':  self->dcsfileheader,'shutter state'
       'dcs_sample':        self->dcsfileheader,'sample position'
       'inx_comment':       self->inxfileheader,'comment'
       'inx_ei':            self->inxfileheader,'incident energy'
       'inx_temperature':   self->inxfileheader,'mean temperature'
       'inx_tchannel':      self->inxfileheader,'time channel'
       'savefile':begin
            widget_control,event.id,get_value=newfile
            self->set_file,filename=newfile[0],/noupdate
          end
       'okbut':begin
            if ptr_valid(self.file) then $
               widget_control,self.tlb,/destroy
          end
       'cancelbut':begin
            self->clear_file
            widget_control,self.tlb,/destroy
          end
       else:
    endcase
end

;modified from IDL6.0 cw_filesel.pro
function dm_filesel::GetDirs,filter=filter
    compile_opt hidden, strictarr
    WIDGET_CONTROL,/HOURGLASS
    if !version.os_family eq 'unix' then $
       count = 0 $
    else $
       tmp = where((routine_info(/system,/function) eq 'FINDFILE'),count)
    if count eq 0 then $
       allFiles = call_function('File_SEARCH',COUNT=cnt,/mark_directory,/match_initial_dot,/nosort) $
    else $
       allFiles = call_function('FINDFILE',COUNT=cnt)    ;much faster, though obslete after IDL6.1
    dFiles = -1
    dirs   = self->isDir(allFiles,/fast)
    i_dirs = where(dirs,n_dir,complement=i_file,ncomplement=n_file)
    if n_dir gt 0 then begin
       dFiles = allFiles[i_dirs]
       if count ne 0 then begin    ;get rid of the . and .. directory returned by findfile
          dotdir = ['.','..']+self.pathsep
          notdotDir = where((dFiles ne dotdir[0]) and (dFiles ne dotdir[1]),nnotdot)
          if nnotdot gt 0 then dFiles = dFiles[notdotDir] else dFiles = -1
       endif
    endif
    if n_file gt 0 then begin
       files = allFiles[i_file]
       if n_elements(filter) eq 1 then begin
          if filter ne 'All Files' then begin
             if (filter ne 'Image Files') then begin
                mfilter = (STRPOS(filter, ',') ne -1) ? STRTOK(filter, ',', /EXTRACT) : filter
                oldFiles = files
                files = ''
                mfilter = STRUPCASE(STRTRIM(mfilter, 2))
                for i=0,N_ELEMENTS(mfilter)-1 do begin
                    test = WHERE(STRPOS(STRUPCASE(oldFiles), mfilter[i]) ge 0)
                    if (test[0] ne -1) then $
                       files = [files, oldFiles[test]]
                endfor
                files = (N_ELEMENTS(files) gt 1) ? files[1:*] : -1
             endif else begin
                ; This needs to query the image types
                for i = 0,N_ELEMENTS(files)-1 do begin
                    CATCH, errorStatus
                    if (errorStatus ne 0) then continue
                    if (~QUERY_IMAGE(files[i])) then $
                       files[i] = ''
                endfor
                CATCH, /CANCEL
                test = WHERE(files ne '')
                files = (test[0] ne -1) ? files[test] : -1
             endelse
          endif
       endif
       if size(files,/type) eq 7 then begin
          if size(dFiles,/type) eq 7 then dFiles=[dFiles,files] $
          else dFiles = files
       endif
    endif
    return,dFiles
end

;*******************************************************************
;get the vairous properties
;syntax:
;   file=filesel->getproperty(/file)    ;returns '' if nonexistent
;arguments:
;   None
;keywords:
;   dFiles:     return list of files in the selection window
;   file:       return selected files
;   limitfrom:  return from limit
;   limitto:    return to limit
;   path:       return path of selected files
;   sep:        return path separator
;*******************************************************************
function dm_filesel::getproperty,dFiles=dFiles,file=file,limitfrom,limitto,path=path,sep=sep
    if keyword_set(file) then begin
       ;the following codes is to prevent the misbehaviour of widget_list when selection is not in focus
       selected = widget_info(self.fileList,/list_select) & tmp=where(selected ge 0,n_item)
       if n_item gt 0 then begin
          isDir = self->isDir(self.path+self.pathsep+(*self.dFiles)[selected])
          self->set_file   ;clear file pointer
          index = where(isDir eq 0,count)
          if count ne 0 then self->set_file,selected[index]
       endif

       if ptr_valid(self.file) then  begin
          result = strtrim(*self.file,2)
          if n_elements(result) eq 1 then result=result[0]
          return,result
       endif
    endif
    if keyword_set(path) then       return, strtrim(self.path,2)
    if keyword_set(dFiles) then $
       if ptr_valid(self.dFiles) then begin
          result = strtrim(*self.dFiles,2)
          if n_elements(result) eq 1 then result=result[0]
          return,result
       endif
    if keyword_set(sep) then        return,self.pathsep
    if keyword_set(limitfrom) then  return,self.limitfrom
    if keyword_set(limitto) then    return,self.limitto
    return,''
end

;*******************************************************************
;get the vairous properties
;syntax:
;   filesel->getproperty,file=file,path=path,dFiles=dFiles,sep=sep
;arguments:
;   None
;keywords:
;   dFiles:     return list of files in the selection window
;   file:       return selected files
;   limitfrom:  return from limit
;   limitto:    return to limit
;   path:       return path of selected files
;   sep:        return path separator
;*******************************************************************
pro dm_filesel::getproperty,dFiles=dFiles,file=file,limitfrom=limitfrom,limitto=limitto,path=path,sep=sep
    if arg_present(file) then begin
       if widget_info(self.tlb,/valid_id) then begin
          ;the following codes is to prevent the misbehaviour of widget_list when selection is not in focus
          selected = widget_info(self.fileList,/list_select) & tmp=where(selected ge 0,n_item)
          if n_item gt 0 then begin
             isDir = self->isDir(self.path+self.pathsep+(*self.dFiles)[selected])
             self->set_file   ;clear file pointer
             index = where(isDir eq 0,count)
             if count ne 0 then self->set_file,selected[index]
          endif
       endif
       if ptr_valid(self.file) then begin
          file = strtrim(*self.file,2)
          if n_elements(file) eq 1 then file = file[0]
       endif
    endif
    if arg_present(path) then path = strtrim(self.path,2)
    if arg_present(dFiles) then $
       if ptr_valid(self.dFiles) then begin
          dFiles   = strtrim(*self.dFiles,2)
          if n_elements(dFiles) eq 1 then dFiles=dFiles[0]
       endif
    if arg_present(sep) then       sep       = self.pathsep
    if arg_present(limitfrom) then limitfrom = self.limitfrom
    if arg_present(limitto) then   limitto   = self.limitto
end

;*******************************************************************
;check whether a path is a valid directory
;syntax:
;   result=filesel->isDir(path,/showmesg)
;arguments:
;   path: a string of the path to be checked
;keywords:
;   nomesg: if set the error message is not displayed
;*******************************************************************
function dm_filesel::isDir,path,showmesg=showmesg, fast=fast
    nitem  = n_elements(path)
    if nitem eq 0 then return,0b
    if keyword_set(fast) then return,strpos(path,self.pathsep) ne -1
    result = file_test(path,/directory)
    if keyword_set(showmesg) then begin
       index = where(result eq 0,count)
       if count ne 0 then begin
          mesg = path[index]+' is not a valid directory.'
          if widget_info(self.tlb,/valid_id) then $
             ok = dialog_message(mesg,dialog_parent=self.tlb) $
          else $
             ok = dialog_message(mesg)
       endif
    endif
    return,(nitem eq 1)?result[0]:result
end

;a widget_control use uname instead of id as identifier
pro dm_filesel::my_widget_control,uname,base,_extra=extra
    if n_elements(base) eq 0 then base=self.tlb
    if n_elements(extra) ne 0 then begin
       for i=0,n_elements(uname)-1 do begin
           wid = widget_info(base,find_by_uname=uname[i])
           if wid ne 0 then widget_control,wid,_extra=extra
       endfor
    endif
end

;*******************************************************************
;setting self.file , not supposed to be called by outside user
;syntax:
;   filesel->set_file,[selected],filename=filename
;arguments:
;   selected:   an array of index
;keywords:
;   filename:   a string
;   noupdate:   if not set and if self.write eq 1 then (*self.file) will be shown in saveas textbox
;*******************************************************************
pro dm_filesel::set_file,selected,filename=filename,noupdate=noupdate,highlight=highlight
    ptr_free,self.file
    if n_elements(selected) eq 0 then begin
       if n_elements(filename) eq 1 then begin
          filename = strtrim(filename,2)
          if strlen(filename) ne 0 then begin
             tmp = where(['all files','*.*','.*'] eq strlowcase(self.filter),count)
             if count eq 0 then begin
                tmp1 = strsplit(self.filter,'.',/extract)
                tmp1 = strlowcase(tmp1[n_elements(tmp1)-1])    ;file type
                tmp2 = strsplit(filename,'.',/extract) & count = n_elements(tmp2)
                if (count le 1) and (strlen(tmp2[0]) ne 0) then filename = tmp2[0]+'.'+tmp1
             endif
             self.file = ptr_new(filename)
             widget_control,self.fileList,set_list_select=-1
          endif
       endif
    endif else begin
       if ptr_valid(self.dFiles) then begin
          self.file = ptr_new((*self.dFiles)[selected])
          if keyword_set(highlight) then begin
             widget_control,self.fileList,set_list_select=selected
          endif
       endif
    endelse
    if self.write and (not keyword_set(noupdate)) then begin
       if ptr_valid(self.file) then tmp=*self.file else tmp=''
       self->my_widget_control,'saveFile',set_value=tmp
    endif
end

;set filter
pro dm_filesel::set_filter,filter
    if size(filter,/type) ne 7 then return
    self.filter = strtrim(filter[0],2)
    if (self.filter eq '.*') or (self.filter eq '*.*') or (self.filter eq '*') or $
       (strlen(self.filter) eq 0) then $
       self.filter='All Files'
    if strupcase(self.filter) ne 'ALL FILES' and strupcase(self.filter) ne 'IMAGE FILES' then begin
       tmp = strpos(self.filter,'.')
       if tmp eq -1 then self.filter='.'+self.filter
    endif

    contextMenu1 = widget_info(self.fileList,find_by_uname='contextMenu1')
    if contextMenu1 ne 0 then widget_control,contextMenu1,/destroy
    contextMenu1 = widget_base(self.fileList,/CONTEXT_MENU,UNAME='contextMenu1')
    ;context sensitive menu
    case strlowcase(filter) of
        '.dcs': begin
                void = widget_button(contextMenu1,value='Comment',         uname='dcs_comment')
                void = widget_button(contextMenu1,value='Wavelength',      uname='dcs_wavelength')
                void = widget_button(contextMenu1,value='Duration',        uname='dcs_duration')
                void = widget_button(contextMenu1,value='Temperature',     uname='dcs_temperature')
                void = widget_button(contextMenu1,value='Shutter State',   uname='dcs_shutterstate')
                void = widget_button(contextMenu1,value='Sample Position', uname='dcs_sample')
                end
        '.inx': begin
                void = widget_button(contextMenu1,value='Comment',         uname='inx_comment')
                void = widget_button(contextMenu1,value='Incident Energy', uname='inx_ei')
                void = widget_button(contextMenu1,value='Mean Temperature',uname='inx_temperature')
                void = widget_button(contextMenu1,value='Time Channel',    uname='inx_tchannel')
                end
        else:
    endcase
    contextMenu2 = widget_info(self.fileList,find_by_uname='contextMenu2')
    if contextMenu2 eq 0 then begin
       contextMenu2 = widget_base(self.fileList,/CONTEXT_MENU,UNAME='contextMenu2')
       void = widget_button(contextMenu2,value='Change File Type',uname='newfilter')
    endif
    self->set_path
    self->clear_file
end

;*******************************************************************
;go to a specified path
;syntax:
;   filesel->set_path,newpath
;arguments:
;   newapth: string of the new path to be set
;keywords:
;   None
;*******************************************************************
pro dm_filesel::set_path,newpath
    if n_elements(newpath) eq 0 then newpath=''
    if (strlen(newpath[0]) ne 0) and (newpath[0] ne self.path) then $
       if (self->isDir(newPath[0],/showmesg)) then self.path=newpath[0]
    cd,self.path
    self->my_widget_control,'pathLab',set_value=self.path
    atTop = self->atTop(self.path)
    self->my_widget_control,'upBut',sensitive=1-atTop
    ptr_free,self.dFiles
    dFiles = self->GetDirs(filter=self.filter)
    if (size(dFiles,/type) ne 7) then $  ;empty directory
       widget_control,self.fileList,sensitive=0,set_value='' $
    else begin
       dFiles   = dFiles(sort(dFiles))
       isDir    = self->isDir(dFiles,/fast)
       ind_file = where(isDir eq 0,nfile,complement=ind_dir,ncomplement=ndir)
       if ndir ne 0 then    dir = dFiles[ind_dir]   ;directories
       if nfile ne 0 then files = dFiles[ind_file]
       if self.limitfile then begin
          if nfile ne 0 then begin
             if self.limitfrom ne '' then begin
                ind=where(files ge self.limitfrom,count)
                if count ne 0 then files=files[ind] else tmp=temporary(files)
             endif
             if (self.limitto ne '') and (n_elements(files) ne 0) then begin
                ind=where(files le self.limitto+'~',count)
                if count ne 0 then files=files[ind] else tmp=temporary(files)
             endif
          endif
       endif
       if n_elements(files) ne 0 then $
          if n_elements(dir) ne 0 then dir=[dir,files] else dir=files
       if n_elements(dir) ne 0 then dFiles=dir else tmp=temporary(dFiles)
       if n_elements(dFiles) ne 0 then begin
          self.dFiles = ptr_new(dFiles)
          widget_control,self.fileList,sensitive=1,set_value=dFiles
       endif else begin
          widget_control,self.fileList,sensitive=0,set_value=''
       endelse
    endelse
    driPull = widget_info(self.tlb,find_by_uname='driPull')
    if driPull ne 0 then begin
       drives = get_drive_list()
       case !version.os_family of
        'Windows':drive_loc = (WHERE(STRUPCASE(STRMID(self.path,0,STRPOS(self.path,'\')+1)) eq STRUPCASE(drives)) > 0)[0]
        'MacOS'  :drive_loc = (WHERE(STRMID(self.path,0,STRPOS(self.path,':')+1) eq drives) > 0)[0]
       endcase
       widget_control,driPull,set_droplist_select=drive_loc
    endif
end

;*******************************************************************
;destroy the object and widget hierarchy, life cycle method
;syntax:
;   filesel->destroy
;arguments:
;   None
;keywords:
;   None
;*******************************************************************
pro dm_filesel::Cleanup
    cd,self.root
    if widget_info(self.extbase,/valid_id) then widget_control,self.extbase,/destroy
    if widget_info(self.tlb,/valid_id) then widget_control,self.tlb,/destroy
    ptr_free,self.dFiles,self.file
end

;*******************************************************************
;class constructor, life cycle method
;
;syntax:object constructor,called automatically
;parameter:
;   None
;keywords:
;   groupleader:    groupleader id
;   parent:         parent ID of this object, if present, it looks like a widget component
;   path:           a string of starting path, default is current directory
;   multi:          if set, allow multiple file choice, only in opening files, default is 1
;   write:          if set, the fileselction is for saving files, defalut is 0
;   filter:         file selection filter, default is 'All Files'
;   frame:          if set, there is a frame around the widget
;   xsize:          xsize of the list widget
;   ysize:          row number of the filelist widget
;   limitfile:      if set, allow restrictions on start and end of file
;   limitfrom:      limit from
;   limitto:        limit to
;*******************************************************************
function dm_filesel::Init,parent=parent,group_leader=group_leader,path=path,multi=multi,write=write,$
    filter=filter,frame=frame,ysize=ysize,ypad=ypad,xpad=xpad,xsize=xsize,limitfile=limitfile,$
    limitfrom=limitfrom,limitto=limitto
    ;error handling
    catch, anyerror
    if anyerror ne 0 then begin
       catch,/cancel
       ok=dm_filesel_error_message(traceback=1,/error)
       return,0
    end
    if n_elements(multi) eq 0 then self.multi=1 else self.multi=0>(multi)<1
    if n_elements(write) eq 0 then self.write=0 else self.write=0>(write)<1
    if n_elements(limitfile) eq 0 then self.limitfile=0 else self.limitfile=0>(limitfile)<1
    if self.write then begin
       self.multi = 0
       self.limitfile = 0
    endif
    if n_elements(filter) eq 0 then self.filter='All Files' else self.filter=filter
    if n_elements(ysize) eq 0 then ysize=8
    if n_elements(xsize) eq 0 then xsize=240
    if n_elements(ypad)  eq 0 then ypad=1
    if n_elements(xpad)  eq 0 then xpad=3
    if n_elements(space) eq 0 then space=2
    if n_elements(limitfrom) ne 0 then self.limitfrom=strtrim(limitfrom[0],2)
    if n_elements(limitto) ne 0 then self.limitto=strtrim(limitto[0],2)
    cd,current=currdir
    self.path=currdir
    self.root=currdir
    if keyword_set(path)  then $
       if self->isDir(path) then self.path=path

    if n_elements(frame) eq 0 then frame=0

    if n_elements(parent) eq 0 then begin
       self.extbase = widget_base(group_leader=group_leader,map=0)
       self.tlb= widget_base(group_leader=self.extbase,/column,frame=frame,event_pro='dm_filesel_event',uname='filesel',$
            title='Please Select '+((self.multi eq 1)?'Files':'a File'),/modal,xpad=xpad,ypad=ypad,space=space)
    endif else begin
       if n_elements(group_leader) ne 0 then self.group_leader = group_leader
       self.tlb= widget_base(parent,/column,frame=frame,event_pro='dm_filesel_event',uname='filesel',xpad=xpad,ypad=ypad,space=space)
    endelse
    self.upDir = '..'
    ;check operation systems
    case !VERSION.OS_FAMILY of
    'Windows': begin
       driveVol     = 'Drive:'
       drives       = get_drive_list()
       self.pathsep = '\'
       end
    'MacOS': begin
       self.upDir   = '::'
       driveVol     = 'Volume:'
       drives       = get_drive_list()
       self.pathsep = ':'
       end
    'vms': begin
       self.upDir   = '[-]'
       driveVol     = ''
       self.pathsep = '/'
       end
    else:begin
       driveVol     = ''
       self.pathsep = '/'
       end
    endcase
    space    = (widget_info(self.tlb,/geometry)).space
    if (driveVol ne '') then begin
       row   = widget_base(self.tlb,/row,xpad=xpad,ypad=ypad,space=space)
       label = widget_label(row,value=driveVol)
       void  = widget_droplist(row,value=drives,uname='driPull',xsize=100)
    endif
    row = widget_base(self.tlb,/row,xpad=xpad,ypad=ypad,space=space)
    defsysv,'!DAVE_AUXILIARY_DIR',exists=exists
    if exists then bitmap_filename = !DAVE_AUXILIARY_DIR+'refresh.bmp' $
    else bitmap_filename = file_which('refresh.bmp',/include_current_dir)
    refreshBut = widget_button(row,value=bitmap_filename,uname='refreshBut',/bitmap,/dynamic_resize)
    geom1 = widget_info(refreshBut,/geometry)
    if exists then bitmap_filename = !DAVE_AUXILIARY_DIR+'up1lvl.bmp' $
    else bitmap_filename = file_which('up1lvl.bmp',/include_current_dir)
    upBut = widget_button(row,value=bitmap_filename,uname='upBut',/bitmap,/dynamic_resize)
    geom  = widget_info(upBut,/geometry)
    if float(!version.release) ge 5.6 then begin
       widget_control,refreshBut,tooltip='Refresh'
       widget_control,upBut,tooltip='Up One Level'
    endif
    tmp  = geom.scr_xsize+geom1.scr_xsize+2*geom.margin+3*(xpad+space)+space
    void = widget_text(row,value=self.path,scr_xsize=xsize-tmp,ysize=1,/editable,uname='pathLab')
    if self.limitfile then begin
       row   = widget_base(self.tlb,/row,xpad=xpad,ypad=ypad,space=space)
       label = widget_label(row,value='from:  ')
       geom  = widget_info(label,/geometry)
       tmp   = geom.scr_xsize+2.*geom.margin+(xpad+space)+space
       void  = widget_text(row,value=self.limitfrom,scr_xsize=xsize-tmp,ysize=1,/editable,uname='limitFrom',/all_events)
       row   = widget_base(self.tlb,/row,xpad=xpad,ypad=ypad,space=space)
       label = widget_label(row,value=' to:   ',scr_xsize=geom.scr_xsize)
       void  = widget_text(row,value=self.limitto,scr_xsize=xsize-tmp,ysize=1,/editable,uname='limitTo',/all_events)
    end
    self.fileList = widget_list(self.tlb,uname='fileList',multiple=self.multi,scr_xsize=xsize,/context_events)

    self->set_filter, self.filter

    if self.write then begin
       row   = widget_base(self.tlb,/row,xpad=xpad,ypad=ypad,space=space)
       label = widget_label(row,value='save as:  ')
       geom  = widget_info(label,/geometry)
       tmp   = geom.scr_xsize+xpad+2*space+2*geom.margin
       void  = widget_text(row,scr_xsize=xsize-tmp,ysize=1,/editable,uname='saveFile',/all_events)
    endif
    widget_control,self.fileList,ysize=ysize
    geom = widget_info(self.fileList,/geometry)
    if n_elements(parent) eq 0 then begin
       row   = widget_base(self.tlb,/row,/align_center,xpad=xpad,ypad=ypad,space=space)
       void  = widget_button(row,value=(self.write eq 1)?'Save':'Open',uname='okBut')
       void  = widget_label(row,value='',xsize=15)
       void  = widget_button(row,value='Cancel',uname='cancelBut')
       geom  = widget_info(row,/geom)
       dm_center_kid,self.tlb,group_leader
       widget_control,self.tlb,/realize
    end
    widget_control,self.tlb,set_uvalue=self
    if n_elements(parent) eq 0 then $
       xmanager,'dm_filesel',self.tlb,cleanup='dm_filesel_Exit'
    return,1
end

;*******************************************************************
;filesel class definition
;*******************************************************************
pro dm_filesel__define
    void={dm_filesel          ,$    ;the object class name
       group_leader:        0L,$    ;group_leader or 0L for the modal widget
       extbase:             0L,$    ;only used in modal widget
       tlb:                 0L,$    ;identifier of top level base
       fileList:            0L,$    ;save the base ID for the context sensitive menus
       dFiles:       PTR_NEW(),$    ;diretory listing
       file:         PTR_NEW(),$    ;selected files
       path:                '',$    ;current directory
       root:                '',$    ;starting directory
       pathsep:             '',$    ;path separator
       filter:              '',$    ;filter
       upDir:               '',$    ;up directory
       multi:               0s,$    ;multiple file selection flag,not allowed if write is set
       write:               0s,$    ;for saving files
       limitfile:           0s,$    ;for limitting file selection range
       limitfrom:           '',$    ;limit file selection
       limitto:             '' $    ;limit file selection
    }
end