; $Id: dm_dialog_input.pro,v 1.29 2008/03/19 20:46:50 ymqiu Exp $
;#######################################################################
;
; NAME:
;  dm_dialog_input
;
; PURPOSE:
;  a multipurpose dialog input utility
;
; CATEGORY:
;  general
;
; AUTHOR:
;  Yiming Qiu
;  NIST Center for Neutron Research
;  100 Bureau Drive, Gaithersburg, MD 20899-6102
;  United States
;  yiming.qiu@nist.gov
;  March, 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.
;
;#######################################################################

pro dm_dialog_input_Exit,tlb,tmp=tmp
    ;do nothing, leave the job to obj_destroy
end

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

;object event handler
pro dm_dialog_input::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 = dialog_message(!error_state.msg,/error)
       return
    end
    ;make the dialog window always on top of the screen
    if tag_names(event, /STRUCTURE_NAME) eq 'WIDGET_KBRD_FOCUS' then begin
       if event.id eq self.tlb then self->totop
    endif
    eventname = widget_info(event.id,/uname)
    eventname = strsplit(eventname,'_',/extract)
    if n_elements(eventname) eq 2 then id=dm_to_number(eventname[1],/int)
    eventname=strlowcase(eventname[0])
    case eventname of
       'ok':begin
            widget_control,self.tlb,/destroy
            return
            end
       'clear':begin
            for i=0,self.n_item-1 do begin
                wid = widget_info(self.tlb,find_by_uname='input_'+dm_to_string(i,/int))
                widget_type = widget_info(wid,/type)
                if widget_type eq 3 then begin
                   widget_control,wid,set_value=''
                   (*self.value)[i] = ''
                endif else begin
                   dm_set_droplist,wid,select=0,get_uvalue=content
                   if self.dl_ret_number then (*self.value)[i]=0 $
                   else (*self.value)[i]=content[0]
                endelse
            endfor
            if obj_valid(self.oWin) then self->updatecbar
            end
       'apply':begin
            ;set up the property keywords
            self->getproperty,value=value
            extra = create_struct((*self.property)[0],value[0])
            for i=1,n_elements(value)-1 do $
                extra  = create_struct((*self.property)[i],value[i])
            if ptr_valid(self.extra) then $     ;add extra keywords
               extra=create_struct(extra,*self.extra)
            call_method,self.parenthandler,self.parentobj,_extra=extra
            self.apply = (1b)
            end
       'input':begin
            if self.focusid ne 0 then begin
               if self.focusid ne event.id then widget_control,self.focusid,set_text_select=0
               self.focusid = 0
            endif
            widget_control,event.id,get_uvalue=value
            widget_type = widget_info(event.id,/type)
            case widget_type of
                 3: begin     ;text box
                    widget_control,event.id,get_value=value
                    (*self.value)[id]=strtrim(value,2)
                    if event.type eq 0 then $
                       if event.ch eq 10B then begin ;return
                          if id eq self.n_item-1 then begin
                             widget_control,self.tlb,/destroy
                             return
                          endif else begin
                             widget_control,event.id,set_text_select=strlen((*self.value)[id])
                             id  = (id+1) mod self.n_item
                             wid = widget_info(self.tlb,find_by_uname='input_'+dm_to_string(id,/int))
                             if widget_info(wid,/type) eq 3 then begin  ;text
                                self.focusid = wid
                                widget_control,wid,/input_focus,set_text_select=[0,strlen((*self.value)[id])]
                             endif
                          endelse
                       endif
                    if self.elambda eq 1 then begin             ;e2lambda keyword set
                       tmp0 = '9.0/sqrt(Ei) '
                       tmp1 = dm_to_number((*self.value)[0])
                       if finite(tmp1) then begin
                          if tmp1 ne 0 then $
                             tmp0 = dm_to_string(sqrt(81.8042/tmp1))+' '
                       endif
                       widget_control,widget_info(self.tlb,find_by_uname='info'),set_value='wavelength='+tmp0+string('c5'XB)
                    endif else if self.elambda eq 2 then begin  ;lambda2e keyword set
                       tmp0 = '81.8/lambda^2'
                       tmp1 = dm_to_number((*self.value)[0])
                       if finite(tmp1) then begin
                          if tmp1 ne 0 then $
                             tmp0 = dm_to_string(81.8042/(tmp1^2))+' meV'
                       endif
                       widget_control,widget_info(self.tlb,find_by_uname='info'),set_value='energy='+tmp0
                    endif
                    end
                 8: begin     ;droplist
                    tmp=widget_info(event.id,/droplist_select)
                    if self.dl_ret_number then (*self.value)[id]=tmp $
                    else (*self.value)[id]=value[tmp]
                    end
                12: begin    ;combobox
                    tmp=event.index
                    if self.dl_ret_number then (*self.value)[id]=tmp $
                    else (*self.value)[id]=value[tmp]
                    end
              else:
            endcase
            if obj_valid(self.oWin) then self->updatecbar
            end
       'browse':begin ;browse button
            widget_control,event.id,get_uvalue=id
            if ptr_valid(self.f_ptr) then begin
               type = strsplit((*self.f_ptr)[id],'.',/extract)
               type = type[n_elements(type)-1]
            endif else $
               type = ''
            file = dm_choose_file(type,dialog_parent=event.handler,/read)
            if strlen(file) ne 0 then begin
               (*self.value)[id]=file
               wid = widget_info(self.tlb,find_by_uname='input_'+dm_to_string(id,/int))
               widget_control,wid,set_value=file
            endif
            end
       'colorbar':begin ;expose event in some unix system for the color bar
            if obj_valid(self.oWin) then self->updatecbar
            end
       else:
    endcase
end

;get properties
pro dm_dialog_input::getproperty,value=value,apply=apply
    if arg_present(apply) then apply=self.apply
    if arg_present(value) then begin
        case self.type of
            0:    value = dm_to_number(*self.value,/int)    ;int
            1:    value = dm_to_number(*self.value,/float)  ;float
            else: value = *self.value
        endcase
        if self.n_item eq 1 then value=value[0]
    endif
end

pro dm_dialog_input::totop
    widget_control,self.tlb,kbrd_focus_event=0
    widget_control,self.tlb,/show,iconify=0
    wid = widget_info(self.tlb,find_by_uname='ok')
    if wid gt 0 then widget_control,wid,/input_focus
    widget_control,self.tlb,/kbrd_focus_event
end

;update colorbar
pro dm_dialog_input::updatecbar
    if obj_valid(self.oWin) eq 0 then return
    self.oPal->loadct,0>dm_to_number((*self.value)[0],/int)<40
    self.oWin->draw,self.oView
end

;destroy the object,life cycle method
pro dm_dialog_input::Cleanup
    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.extra,self.value,self.f_ptr,self.property
    obj_destroy,[self.oWin,self.oView,self.oPal]
end

;constructor,lifecycle method
;syntax:
;   obj=obj_new('dm_dialog_input','color',default=1,droplist_content=['red','blue','green'],is_droplist=[1],/return_number)
;parameter:
;   name:               a string array of names
;keyword:
;   browse:             if set, a browse button will appear after each text widget
;   coltable:           if set, a color bar will show on the top of the widget
;   default:            default value, or rather starting value
;   dialog_parent:      dialog parent, tlb will be centered on dialog parent
;   droplist_content:   a pointer or pointer arrary points to string array(s) containing the droplist values
;                       pointers will be destroyed by the program
;   e2lambda:           if set, calculate wavelength from energy and show it in the first line of info
;   lambda2e:           if set, calculate energy from wavelength and show it in the first line of info
;   filter:             filters for browse dialog
;   float:              if set, all values will be float
;   info:               informations to be shown at the bottom of tlb
;   int:                if set, all values will be int
;   is_droplist:        an array of 0 and 1 to show which quest is a droplist
;   parenthandler:      parent event handler name, default is 'setproperty'
;   parentobject:       parent object
;   property:           properties to be set in calling parentobject->parenthandler, default is name
;   return_number:      return index number of droplist instead of string values
;   title:              title of tlb
;   xsize:              xsize of text widget
;   _extra:             extra keywords used in calling parentobject->parenthandler,name=value
function dm_dialog_input::Init,name,browse=browse,coltable=coltable,default=default,dialog_parent=dialog_parent,$
    droplist_content=droplist_content,e2lambda=e2lambda,lambda2e=lambda2e,filter=filter,float=float,info=info,$
    int=int,is_droplist=is_droplist,parenthandler=parenthandler,parentobject=parentobject,property=property,$
    return_number=return_number,title=title,xsize=xsize,_extra=extra

    self.type=2     ;default type is string
    if n_elements(name)  eq 0 then name=''
    self.n_item =n_elements(name)
    if n_elements(title) eq 0 then title='Input:'
    if n_elements(default) ne 0 then begin
        type=size(default,/type)
        switch type of
          1:
          2:
          3:begin
            mydefault = dm_to_string(default,/int)  ;integer
            self.type = 0
            break
            end
          4:
          5:begin
            mydefault = dm_to_string(default)       ;float
            self.type = 1
            break
            end
          else:begin
            mydefault = dm_to_string(default)
            end
        endswitch
    endif else mydefault=''
    self.elambda = 0
    if keyword_set(e2lambda) then self.elambda=1
    if keyword_set(lambda2e) then self.elambda=2
    if keyword_set(int)      then self.type=0
    if keyword_set(float)    then self.type=1
    ;exception for drolist
    if n_elements(is_droplist) ne 0 then begin
       if keyword_set(return_number) eq 0 then self.type=2
    endif
    if n_elements(extra) ne 0         then self.extra=ptr_new(extra)
    if n_elements(parenthandler) ne 0 then self.parenthandler=parenthandler else self.parenthandler='setproperty'
    if n_elements(parentobject) ne 0  then self.parentobj=parentobject
    n_prop  = n_elements(property)
    if n_prop ne 0 then begin
       tmp = strarr(self.n_item)
       for i=0,self.n_item-1 do tmp[i]=property[i mod n_prop]
       self.property=ptr_new(tmp,/no_copy)
    endif else self.property=ptr_new(name)
    if keyword_set(return_number)     then self.dl_ret_number=1
    if n_elements(xsize) eq 0         then xsize=60
    n_default  = n_elements(mydefault)
    self.value = ptr_new(strarr(self.n_item))
    isdroplist = intarr(self.n_item)
    if (n_elements(is_droplist) ne 0) and (n_elements(droplist_content) ne 0) then begin
       isdroplist[0:((n_elements(is_droplist)<(self.n_item)-1))]=is_droplist[0:((n_elements(is_droplist)<(self.n_item)-1))]
       ;droplist_content=strtrim(droplist_content,2)
    endif
    for i=0,self.n_item-1 do (*self.value)[i]=mydefault[i mod n_default]
    n_filt = n_elements(filter)
    if n_filt ne 0 then begin
       f_val = strarr(self.n_item)
       for i=0,self.n_item-1 do f_val[i]=filter[i mod n_filt]
       self.f_ptr = ptr_new(f_val,/no_copy)
    endif
    self.extbase = widget_base(group_leader=dialog_parent,map=0)
    tlb = widget_base(title=title,/col,group_leader=self.extbase,/modal,/align_center)
    if keyword_set(coltable) then begin
       draw = widget_draw(tlb,graphics_level=2,scr_ysize=20,renderer=1,retain=0,/expose_events,uname='colorbar')
       self.oView = obj_new('IDLgrView',location=[0,0],viewplane_rect=[0,0,1,1])
       self.oPal  = obj_new('IDLgrPalette')
       self.oPal->loadct,dm_to_number(mydefault[0],/int)
       model      = obj_new('IDLgrModel')
       cbar       = obj_new('IDLgrColorbar',palette=self.oPal)
       self.oView->add,model
       model->add,cbar
    endif
    col   = widget_base(tlb,/col,/align_center)
    label = lonarr(1>(self.n_item))
    for i=0,self.n_item-1 do begin
        line = widget_base(col,/row,/align_center)
        label[i] = widget_label(line,value=name[i],/align_center)
        if isdroplist[i] then begin
           value = *droplist_content[(total(isdroplist[0:i])-1) mod n_elements(droplist_content)]
           if !version.os_family eq 'Windows' then usedroplist=0 else usedroplist=1   ;force to use droplist for unix
           void  = dm_widget_droplist(line,value=value,scr_xsize=xsize,uname='input_'+dm_to_string(i,/int),$
                  select=(*self.value)[i],uvalue=value,usedroplist=usedroplist)
           if not keyword_set(return_number) then $
              (*self.value)[i]=value[(*self.value)[i]]
        endif else begin
           void = widget_text(line,value=(*self.value)[i],/editable,/all_events,scr_xsize=xsize,uname='input_'+dm_to_string(i,/int),$
                  /align_center,uvalue=i)
           if i eq 0 then begin
              self.focusid = void
              text_select  = [0,strlen((*self.value)[0])]
           endif
           if keyword_set(browse) then browse_but=widget_button(line,value='Browse',uname='browse',uvalue=i)
        endelse
    endfor
    if self.n_item gt 0 then begin
       geom1 = widget_info(col,/geometry)
       geom2 = widget_info(void,/geometry)
       tmp   = geom1.scr_xsize-geom2.scr_xsize
       if n_elements(browse_but) ne 0 then begin
          geom3 = widget_info(browse_but,/geometry)
          tmp   = tmp-geom3.scr_xsize
       endif
       for i=0,self.n_item-1 do widget_control,label[i],scr_xsize=tmp+5
    endif
    if n_elements(droplist_content) ne 0 then ptr_free,droplist_content
    n_info=n_elements(info)
    if (n_info ne 0) or (self.elambda gt 0) then begin
       col=widget_base(tlb,/col,/align_center)
       if self.elambda eq 1 then begin
          tmp0  = '9.0/sqrt(Ei) '
          label = widget_label(col,value='wavelength='+tmp0+string('c5'XB),/align_left,uname='info')
          geom  = widget_info(label,/geometry)
          tmp1  = dm_to_number(mydefault[0])
          if finite(tmp1) then begin
             if tmp1 ne 0 then begin
                tmp0 = dm_to_string(sqrt(81.8042/tmp1))+' '
                widget_control,label,scr_xsize=geom.scr_xsize,set_value='wavelength='+tmp0+string('c5'XB)
             endif
          endif
       endif else if self.elambda eq 2 then begin
          tmp0  = '81.8/lambda^2 '
          label = widget_label(col,value='energy='+tmp0+' meV',/align_left,uname='info')
          geom  = widget_info(label,/geometry)
          tmp1  = dm_to_number(mydefault[0])
          if finite(tmp1) then begin
             if tmp1 ne 0 then begin
                tmp0 = dm_to_string(81.8042/(tmp1^2))+' meV'
                widget_control,label,scr_xsize=geom.scr_xsize,set_value='energy='+tmp0
             endif
          endif
       endif
       for i=0,n_info-1 do $
           label = widget_label(col,value=info[i],/align_left)
    endif
    row  = widget_base(tlb,/row,/align_center)
    void = widget_button(row,value='Clear',uname='clear')
    void = widget_label(row,value='',xsize=15)
    if n_elements(parentobject) ne 0 then begin
       void  = widget_button(row,value='Apply',uname='apply')
       space = widget_label(row,value='',xsize=15)
    endif
    void = widget_button(row,value='OK',uname='ok')
    dm_center_kid,tlb,dialog_parent   ;centering over the dialog_parent widget
    if keyword_set(coltable) then begin
       geom  = widget_info(tlb,/geometry)
       xsize = geom.scr_xsize-(geom.space+geom.xpad+geom.margin)*2
       widget_control,draw,scr_xsize=xsize
    endif
    widget_control,tlb,/realize
    if self.focusid ne 0 then widget_control,self.focusid,/input_focus,set_text_select=text_select
    if keyword_set(coltable) then begin
       widget_control,draw,get_value=oWin
       oWin->getproperty,dimension=dim
       self.oWin = oWin
       cbar->setproperty,dimension=dim,xcoord_conv=norm_coord([0,dim[0]]),ycoord_conv=norm_coord([0,dim[1]])
       self->updatecbar
    endif
    self.tlb=tlb
    widget_control,tlb,set_uvalue=self,/kbrd_focus_event
    xmanager,'dm_dialog_input',tlb,cleanup='dm_dialog_input_Exit';,no_block=lmgr(/runtime)
    return,1
end

;class definition
pro dm_dialog_input__define
    void={dm_dialog_input        , $    ;thie object class name
        extbase:               0L, $    ;group leader of the modal tlb
        tlb:                   0L, $    ;top level base
        parentobj:      obj_new(), $    ;parent object
        parenthandler:         '', $    ;event handler of parent object,default is 'event'
        n_item:                0s, $    ;number of items
        type:                  0s, $    ;data type 0:int 1:float 2:string
        value:          ptr_new(), $    ;save the reults
        f_ptr:          ptr_new(), $    ;save the filter values
        extra:          ptr_new(), $    ;extra keyword used in calling parentobj->parenthandler,name=value
        property:       ptr_new(), $    ;property names,default is name
        oWin:           obj_new(), $    ;window object
        oView:          obj_new(), $    ;view object
        oPal:           obj_new(), $    ;palette object
        dl_ret_number:         0b, $    ;if 1 then return droplist choice by number
        elambda:               0s, $    ;if 1 then calculate lambda and show it in the info label
                                   $    ;if 2 then calculate energy from lambda and show it in the info label
        focusid:               0L, $    ;focusing text id
        apply:                 0b  $    ;flag to show if apply button has been pressed
    }
end

;example
;   result=dm_dialog_input('color',default=1,droplist_content=ptr_new(['red','blue','green']),is_droplist=[1],/return_number)
function dm_dialog_input,name,_ref_extra=extra
    tmp=obj_new('dm_dialog_input',name,_extra=extra)
    tmp->getproperty,value=value,_extra=extra
    obj_destroy,tmp
    if n_elements(value) ne 0 then return,value
end